/*
   ODBCChannel.m

   Copyright (c) 1996 NeXT Software, Inc.  All rights reserved.

   IMPORTANT:  This NeXT software is supplied to you in consideration of your agreement
   to the terms of the NeXT Software License Agreement stated in the file ODBC_LICENSE.rtf
   provided with the software.  Your use of the software is governed by such terms.
   Do not use, install, or make copies of the software if you do not agree to such terms.

   THIS SOFTWARE IS FURNISHED ON AN "AS-IS" BASIS. NeXT MAKES NO WARRANTIES OF ANY KIND,
   EITHER EXPRESS OR IMPLIED, AS TO ANY MATTER WHATSOEVER, INCLUDING WITHOUT LIMITATION
   THE CONDITION, MERCHANTABILITY, OR FITNESS FOR ANY PARTICULAR PURPOSE OF THIS SOFTWARE.
   NeXT DOES NOT ASSUME ANY LIABILITY REGARDING USE OF, OR ANY DEFECT IN, THIS SOFTWARE.
   IN NO EVENT SHALL NeXT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
   DAMAGES, EVEN IF IT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#import "ODBCPrivate.h"

@implementation ODBCChannel

- (void)_beginFetch
{
    [(ODBCContext *)_context channelWillBeginFetching];
    _flags.fetchInProgress = 1;
    _rowsProcessedCount = 0;
}

- (void)_endFetch
{

    if (_flags.fetchInProgress) {
        RETCODE result;

        if (_debug) NSLog (@" *** %@ %d rows processed\n", self, _rowsProcessedCount);
        _flags.fetchInProgress = NO;

        // Discard cursor and results
        result = SQLFreeStmt (_statement, SQL_UNBIND);
        if ( result != SQL_SUCCESS ) {
            [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLFreeStmt in -[ODBCChannel _endFetch]" raise: (result != SQL_SUCCESS_WITH_INFO)];
        }

        result = SQLFreeStmt (_statement, SQL_CLOSE);
        if ( result != SQL_SUCCESS ) {
            [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLFreeStmt in -[ODBCChannel _endFetch]" raise: (result != SQL_SUCCESS_WITH_INFO)];
        }

        [(ODBCContext *)_context channelDidEndFetching];

        if(_flags.beganTransaction) {
            [_context commitTransaction];
            _flags.beganTransaction = NO;
        }
        
    }
    [self setAttributesToFetch:nil];
}

- initWithAdaptorContext:(EOAdaptorContext *)context
{
    [super initWithAdaptorContext:context];
    return self;
}

- (void)dealloc
{
    // If statement is active then close channel
    if (_statement) [self closeChannel];

    // release attributes and columns
    [_attributes release];
    [_selectedColumns release];
    [super dealloc];
}

- (BOOL)isOpen
{
    return (_statement) ? YES : NO;
}

- (void)openChannel
{
    RETCODE result;
    HDBC    connection;
    HSTMT   hStmt = 0;

    //
    // If already opened then return
    //
    if ([self isOpen]) return;

    //
    // Get the connection from the context
    //
    [(ODBCContext *)_context channelWillOpen];
    connection = [(ODBCContext *)_context odbcDatabaseConnection];

    //
    // If not connected then raise exception
    //
    if (!connection) {
        [NSException raise:EOGeneralAdaptorException format:@"%@ -- %@ 0x%x: Unable to open channel.  Unable to logon.", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
    }

    //
    // Allocate statement with connection
    //
    result = SQLAllocStmt (connection, &hStmt);

    if ( result != SQL_SUCCESS ) {
        if(result != SQL_SUCCESS_WITH_INFO) {
            [(ODBCContext *)_context channelDidClose];
        }
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLAllocStmt in -[ODBCChannel openChannel]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    _statement = hStmt;
}

- (void)closeChannel
{
    RETCODE result;

    //
    // If not statement allocated then return
    //
    if (!_statement) return;

    //
    // If we are fetching then cancel fetch
    //
    if (_flags.fetchInProgress) [self cancelFetch];

    //
    // Free statement by dropping all resorces
    //
    result = SQLFreeStmt (_statement, SQL_DROP);
    _statement = 0;

    if ( result != SQL_SUCCESS ) {
        if(result != SQL_SUCCESS_WITH_INFO) {
            [(ODBCContext *)_context channelDidClose];
        }
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLFreeStmt in -[ODBCChannel closeChannel]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    // notify of channel closure
    [(ODBCContext *)_context channelDidClose];
}

- (void)insertRow:(NSDictionary *)row forEntity:(EOEntity *)entity
{
    [self evaluateExpression:[[[_context adaptor] expressionClass] insertStatementForRow:row entity:entity]];
}

- (unsigned)updateValues:(NSDictionary *)row inRowsDescribedByQualifier:(EOQualifier *)qualifier entity:(EOEntity *)entity
{
    [self evaluateExpression:[[[_context adaptor] expressionClass] updateStatementForRow:row qualifier:qualifier entity:entity]];

    return _rowsProcessedCount;
}

- (unsigned)deleteRowsDescribedByQualifier:(EOQualifier *)qualifier entity:(EOEntity *)entity
{
    [self evaluateExpression:[[[_context adaptor] expressionClass] deleteStatementWithQualifier:qualifier entity:entity]];

    return _rowsProcessedCount;
}

- (void)selectAttributes:(NSArray *)attributes fetchSpecification:(EOFetchSpecification *)fetchSpec lock:(BOOL)yn entity:(EOEntity *)entity
{
    NSString *selectString;
    EOSQLExpression *sqlExpression;

    if (_delegateRespondsTo.shouldSelectAttributes) {
        if (![_delegate adaptorChannel:self shouldSelectAttributes:attributes fetchSpecification:[[fetchSpec copy] autorelease] lock:yn entity:entity]) {
            return;
        }
    }

    selectString = [entity externalQuery];

    if (!selectString || [fetchSpec qualifier]) {
        sqlExpression = [[[_context adaptor] expressionClass] selectStatementForAttributes:attributes lock:yn fetchSpecification:fetchSpec entity:entity];
    } else {
        sqlExpression = [[[_context adaptor] expressionClass] expressionForString:selectString];
    }

    [self evaluateExpression:sqlExpression];

    [self setAttributesToFetch:attributes];

    if (_delegateRespondsTo.didSelectAttributes)
        [_delegate adaptorChannel:self didSelectAttributes:attributes fetchSpecification:fetchSpec lock:yn entity:entity];

    return;
}

- (void)_bindInputVariablesWithBindings:(NSArray *)bindings
{
    unsigned i,c;
    RETCODE result;

    result = SQLFreeStmt (_statement, SQL_RESET_PARAMS);
    if ( result != SQL_SUCCESS ) {
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLFreeStmt in -[ODBCChannel _bindInputVariablesWithBindings:]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    for(i = 0,c = [bindings count]; i < c; i++) {
        NSMutableDictionary *bindDict = [bindings objectAtIndex:i];
        EOAttribute *att = [bindDict objectForKey:EOBindVariableAttributeKey];
        ODBCColumn *col = [[ODBCColumn alloc] initWithAttribute:att channel:self];

        [bindDict setObject:col forKey:EOBindVariableColumnKey];
        [col release];

        [col bindAttribute:att forInputColumn:(i+1) ofStatement:_statement];
        [col takeInputValue:[bindDict objectForKey:EOBindVariableValueKey]];
    }
}

- (void)evaluateExpression:(EOSQLExpression *)expression
{
    RETCODE result;
    NSStringEncoding encoding = [[_context adaptor] databaseEncoding];
    NSData *data;
    NSString *sqlString;

    if (_flags.fetchInProgress) {
        [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: illegal attempt to evaluateExpression while a fetch is in progress", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
    }

    if (_delegateRespondsTo.shouldEvaluateExpression) {
        if (![_delegate adaptorChannel:self shouldEvaluateExpression:expression]) return;
    }

    sqlString = [expression statement];
    data = [sqlString dataUsingEncoding:encoding];

    // If there are characters in the string that can't be converted,
    // then the data will be nil, call report error and return FAIL.
    if (!data) {
        [NSException raise:NSInvalidArgumentException format:@"%@ -- %@ 0x%x: attempt to evaluate SQL statement that contains characters that can not be converted to target database encoding: %@", NSStringFromSelector(_cmd), NSStringFromClass([self class]), sqlString];
    }

    if (_debug) NSLog (@" *** [%@ evaluateExpression: %@]\n", self, expression);

    // Bind the variables
    [self _bindInputVariablesWithBindings:[expression bindVariableDictionaries]];

    //
    // We are not reusing bindings. Just drop them.
    //
    result = SQLFreeStmt (_statement, SQL_UNBIND);
    if ( result != SQL_SUCCESS ) {
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLFreeStmt in -[ODBCChannel evaluateExpression:]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    if (![_context transactionNestingLevel]) {
        [_context beginTransaction];
        _flags.beganTransaction = YES;
    } else {
        _flags.beganTransaction = NO;
    }

    _rowsProcessedCount = 0;

    // Execute SQL statement
    result = SQLExecDirect(_statement, (UCHAR*)[data bytes], SQL_NTS);

    if ( result != SQL_SUCCESS ) {
        if(result != SQL_SUCCESS_WITH_INFO) {
            if(_flags.beganTransaction) {
                [_context rollbackTransaction];
                _flags.beganTransaction = NO;
            }
        }
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLExecDirect in -[ODBCChannel evaluateExpression:]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    // If the SQL just executed was a select, mark the channel as
    // inFetchMode. The first call to fetchWithAttributes:withZone: will
    // actually do the describe (if needed), the define, execute, and fetch.
    //
    if ([sqlString hasPrefix: @"SELECT"])
        [self _beginFetch];

    if([sqlString hasPrefix:@"UPDATE"] || [sqlString hasPrefix:@"DELETE"] || [sqlString hasPrefix:@"INSERT"]) {
        SDWORD rowCount;
        result = SQLRowCount(_statement, &rowCount);

        if ( result != SQL_SUCCESS ) {
            if(result != SQL_SUCCESS_WITH_INFO) {
                if(_flags.beganTransaction) {
                    [_context rollbackTransaction];
                    _flags.beganTransaction = NO;
                }
            }
            [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLRowCount in -[ODBCChannel evaluateExpression:]" raise: (result != SQL_SUCCESS_WITH_INFO)];
        }

        _rowsProcessedCount = rowCount;
        if (_debug) NSLog (@" *** %@ %d rows processed\n", self, (int)_rowsProcessedCount);
    }

    if (_delegateRespondsTo.didEvaluateExpression)
        [_delegate adaptorChannel:self didEvaluateExpression:expression];

    if(!_flags.fetchInProgress && _flags.beganTransaction) {
        [_context commitTransaction];
        _flags.beganTransaction = NO;
    }

    return;
}

- (BOOL)isFetchInProgress
{
    return _flags.fetchInProgress;
}

- (NSArray *)describeResults
{
    NSMutableArray *attributes  = [NSMutableArray array];
    RETCODE result;
    SWORD column, columnCount;

    // Get number of columns in result set
    result = SQLNumResultCols (_statement, &columnCount);

    if ( result != SQL_SUCCESS ) {
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLNumResultCols in -[ODBCChannel describeResults]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    // Create and attribute for each column in result set
    for (column = 1; column <= columnCount; column++) {
        EOAttribute *att;
        static UCHAR columnName[SQL_MAX_MESSAGE_LENGTH]; // Column name
        SWORD columnNameLength, sqlType, scale, isNullable;
        UDWORD colDef;
        RETCODE result;
        NSString* name;

        // Get column description
        if ((result = SQLDescribeCol (_statement, column, columnName, sizeof(columnName), &columnNameLength, &sqlType, &colDef, &scale, &isNullable)) != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO)
            break;

        name = [[NSString stringWithCString:columnName length:columnNameLength] uppercaseString];
        att = [(ODBCAdaptor*)[_context adaptor] attributeWithName:name columnName:name externalType:@"" /* unknown type */ odbcType:sqlType length:colDef precision:colDef scale:scale nullable:(isNullable == SQL_NULLABLE) ? YES : NO];

            [attributes addObject:att];
    };

    return attributes;
}

- (NSMutableDictionary *)fetchRowWithZone:(NSZone *)zone
{
    RETCODE result;
    NSMutableDictionary *row = nil;

    if (![self isFetchInProgress])
        return nil; // Should be an error ?

    // Check if it's the first row, and in this case
    // verify that the attribute set is here
    if(!_rowsProcessedCount) {
        if(!_attributes) {
            [self setAttributesToFetch:[self describeResults]];
        }
    }

    if (_delegateRespondsTo.willFetchRow) {
        [_delegate adaptorChannelWillFetchRow:self];
    }

    result = SQLFetch (_statement);

    if(result == SQL_NO_DATA_FOUND) {
        [self _endFetch];
        return nil;
    }

    if ( result != SQL_SUCCESS ) {
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLFetch in -[ODBCChannel fetchRowWithZone:]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    {
        int i, columnCount;
        id valueBuffer[100];
        id *values, *sp, *ep;

        // Create array for values if 100 is too small
        columnCount = [_selectedColumns count];
        values = (columnCount > 100) ? valueBuffer : (id *)NSZoneMalloc(NULL, columnCount*sizeof(id));

        // Fetch values for row
        for (i = 0; i < columnCount; i++)
            values[i] = [[_selectedColumns objectAtIndex:i] fetchWithZone : zone];

        // construct a dictionary with the values
        if ((row = [self dictionaryWithObjects:values forAttributes:_attributes zone:zone]))
            _rowsProcessedCount++;

        // release all of the objects
        for(sp = values, ep = sp + columnCount; sp < ep; sp++)
            [*sp release];

        // relase values if we allocated a value buffer
        if (values != valueBuffer) NSZoneFree(NULL, values);

    }    

    if(row && _delegateRespondsTo.didFetchRow) {
        [_delegate adaptorChannel:self didFetchRow:row];
    }

    return row;
}

- (void)setAttributesToFetch:(NSArray *)attributes
{
    int i, count = 0;
    BOOL bind = YES;
    
    [_attributes autorelease];
    _attributes = [attributes retain];

    count = [_attributes count];
    if(_selectedColumns) {
        [_selectedColumns removeAllObjects];
    } else if(count) {
        _selectedColumns = [[NSMutableArray alloc] initWithCapacity:count];
    }

    // Create ODBCColumns and define fetch buffers for each of the attributes
    // in the select list.
    for (i = 0; i < count; i++) {
        EOAttribute *attribute;
        ODBCColumn *column;

        // Create column for selected attribute
        attribute = [_attributes objectAtIndex:i];
        column = [[ODBCColumn alloc] initWithAttribute:attribute channel:self];
        [_selectedColumns addObject: column];
        [column release];

        if(![column couldBind]) {
            bind = NO;
        }
        // bind column (columns start at 1)
        [column connectToColumn:(i+1) ofStatement:_statement useBinding:bind];
    }
}

- (NSArray *)attributesToFetch
{
    return _attributes;
}

- (void)cancelFetch
{
    if(_flags.fetchInProgress) {
        if (_debug) NSLog (@" !!! %@ fetch canceled\n", self);
        [self _endFetch];
    }
}

- (void *)odbcStatement
{
    return _statement;
}

- (NSDictionary *)odbcTypeInfo
{
    NSMutableDictionary *typeInfo = [NSMutableDictionary new];
    RETCODE result;
    NSDictionary *row;
    BOOL autoOpening = NO;

    if (_debug) NSLog (@" *** [%@ odbcTypeInfo]\n", self);

    if(![self isOpen]) {
        autoOpening = YES;
        [self openChannel];
    }

    // Execute SQL statement
    result = SQLGetTypeInfo(_statement, SQL_ALL_TYPES);

    if ( result != SQL_SUCCESS ) {
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLGetTypeInfo in -[ODBCChannel odbcTypeInfo]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    //
    // Do we need to disable the delegate here ?
    //
    [self _beginFetch];

    while(row = [self fetchRowWithZone:NULL]) {
        NSString *typeName = [row objectForKey:@"TYPE_NAME"];
        NSMutableDictionary *local;
        id value;

        if(local = [typeInfo objectForKey:typeName]) {
            value = [ODBCAdaptor stringRepresentationForOdbcType:[[row objectForKey:@"DATA_TYPE"] intValue]];
            [[local objectForKey:@"defaultODBCType"] addObject:value];
        } else {

            value = [row objectForKey:@"AUTO_INCREMENT"];
            if((value != [EONull null]) && [value intValue]) {
                continue; // Yuck, we do not support those.
            }
            
            local = [NSMutableDictionary dictionary];
            // Extract the datatype
            value = [ODBCAdaptor stringRepresentationForOdbcType:[[row objectForKey:@"DATA_TYPE"] intValue]];
            [local setObject:[NSMutableArray arrayWithObject:value] forKey:@"defaultODBCType"];
            // Extract the precision
            value = [row objectForKey:@"PRECISION"];
            if(value != [EONull null]) {
                [local setObject:[value stringValue] forKey:@"precision"];
            }
            // Extract the minscale
            value = [row objectForKey:@"MINIMUM_SCALE"];
            if(value != [EONull null]) {
                [local setObject:[value stringValue] forKey:@"minScale"];
            }
            // Extract the maxscale
            value = [row objectForKey:@"MAXIMUM_SCALE"];
            if(value != [EONull null]) {
                [local setObject:[value stringValue] forKey:@"maxScale"];
            }
            // Extract the unsigned status
            value = [row objectForKey:@"UNSIGNED_ATTRIBUTES"];
            if(value != [EONull null]) {
                [local setObject:[value intValue] ? @"YES":@"NO" forKey:@"isUnsigned"];
            }
            // Extract the nullable status
            value = [row objectForKey:@"NULLABLE"];
            if(value != [EONull null]) {
                [local setObject:[value intValue] ? @"YES":@"NO" forKey:@"isNullable"];
            }
            // Extract the searcheable status
            value = [row objectForKey:@"SEARCHABLE"];
            if(value != [EONull null]) {
                [local setObject:(([value intValue] == SQL_SEARCHABLE) || ([value intValue] == SQL_ALL_EXCEPT_LIKE)) ? @"YES":@"NO" forKey:@"isSearchable"];
            }
            // Extract the number of args in the type definition
            value = [row objectForKey:@"CREATE_PARAMS"];
            if(value != [EONull null]) {
                NSArray *array = [value componentsSeparatedByString:@","];
                [local setObject:[NSString stringWithFormat:@"%d",[array count]] forKey:@"createParams"];
            } else {
                [local setObject:@"0" forKey:@"createParams"];
            }
            [typeInfo setObject:local forKey:typeName];
        }
    }
    
    [self _endFetch];

    if(autoOpening) {
        [self closeChannel];
    }
    return typeInfo;
}

- (NSArray *)_entityGroupForEntity:(EOEntity *)entity
{
    NSMutableArray *result = [NSMutableArray array];
    NSMutableArray *tableNames = [NSMutableArray array];
    NSArray *subEntities = [entity subEntities];

    if(![entity isAbstractEntity] && [entity externalName]) {
        [result addObject:entity];
        [tableNames addObject:[entity externalName]];
    }

    if(subEntities) {
        unsigned i,c;

        for(i = 0, c = [subEntities count]; i < c; i++) {
            EOEntity *sub = [subEntities objectAtIndex:i];
            NSArray *merge = [self _entityGroupForEntity:sub];
            unsigned j,k;

            for(j = 0, k = [merge count]; j < k; j++) {
                unsigned index;
                EOEntity *ent = [merge objectAtIndex:j];

                index = [tableNames indexOfObject:[ent externalName]];
                if(index == NSNotFound) {
                    [result addObject:ent];
                    [tableNames addObject:[ent externalName]];

                }
            }

        }

    }
    return result;
}

- (NSDictionary *)primaryKeyForNewRowWithEntity:(EOEntity *)entity
{
    NSArray *primaryKeyAttributes = [entity primaryKeyAttributes];
    EOAttribute *primAttribute;
    Class expressionClass;
    EOSQLExpression *getRowExpr;
    EOSQLExpression *updateRowExpr;
    NSDictionary *row = nil;
    NSNumber *returnedValue, *newValue = nil;
    BOOL needToCreateTable = NO;

    if([primaryKeyAttributes count] != 1) {
        return nil; // We support only simple primary keys
    }

    primAttribute = [primaryKeyAttributes objectAtIndex:0];

    if([primAttribute adaptorValueType] != EOAdaptorNumberType) {
        return nil; // We support only number keys
    }

    expressionClass = [[[self adaptorContext] adaptor] expressionClass];
    getRowExpr = [expressionClass expressionForString:[NSString stringWithFormat:@"SELECT PK FROM EO_PK_TABLE WHERE NAME = '%@'", [entity name]]];

    //[[self adaptorContext] beginTransaction];
    NS_DURING
        //
        // Fetch the row
        //
        [self evaluateExpression:getRowExpr];
        row = [self fetchRowWithZone:NULL]; // fetch one row
        [self cancelFetch];
    NS_HANDLER
        [self cancelFetch];
        needToCreateTable = YES;
    NS_ENDHANDLER

    if(needToCreateTable) {
        EOSQLExpression *createTableExpr;
        NSString *textType;

        textType = [ODBCAdaptor externalTypeForOdbcType:SQL_CHAR model:[entity model]];

        createTableExpr = [expressionClass expressionForString:[NSString stringWithFormat:@"CREATE TABLE EO_PK_TABLE ( NAME %@(40), PK %@ )", textType, [getRowExpr columnTypeStringForAttribute:primAttribute]]];

        NS_DURING
            //
            // Create the table
            //
            [self evaluateExpression:createTableExpr];
        NS_HANDLER
            //[[self adaptorContext] rollbackTransaction];
            [localException raise];
        NS_ENDHANDLER
    }

    if(row) {
        returnedValue = [row objectForKey:@"PK"];
    } else {
        EOSQLExpression *insertRowExpr;

        insertRowExpr = [expressionClass expressionForString:[NSString stringWithFormat:@"INSERT INTO EO_PK_TABLE VALUES ( '%@', 0 )", [entity name]]];

        NS_DURING
            //
            // Insert a row with 0
            //
            [self evaluateExpression:insertRowExpr];
        NS_HANDLER
            //[[self adaptorContext] rollbackTransaction];
            [localException raise];
        NS_ENDHANDLER

        returnedValue = [NSNumber numberWithInt:0];
    }


    if((id)returnedValue == [EONull null] || ![returnedValue intValue]) {
        // Find the current maximum, and set it as the newValue...
        NSArray *entityGroup = [self _entityGroupForEntity:entity];
        unsigned i,c;
        
        newValue = [NSNumber numberWithInt:0];

        for(i = 0, c = [entityGroup count]; i < c; i++) {
            EOEntity *currentEntity = [entityGroup objectAtIndex:i];
            EOAttribute *att = [currentEntity attributeNamed:[primAttribute name]];
            NSDictionary *r;
            NSNumber *tempValue;
            EOSQLExpression *searchExpr = [expressionClass expressionForString:[NSString stringWithFormat:@"SELECT MAX(%@) FROM %@", [att columnName], [currentEntity externalName]]];

            NS_DURING
                //
                // Fetch the row
                //
                [self evaluateExpression:searchExpr];
                [self setAttributesToFetch:[NSArray arrayWithObject:att]];
                r = [self fetchRowWithZone:NULL]; // fetch one row
                [self cancelFetch];
            NS_HANDLER
                [self cancelFetch];
                [localException raise];
            NS_ENDHANDLER

            tempValue = [r objectForKey:[att name]];
            if(tempValue && ((id)tempValue != [EONull null]) && ([tempValue compare:newValue] == NSOrderedDescending))
                newValue = tempValue;
        }
    } else {
        newValue = returnedValue;
    }
    newValue = [NSNumber numberWithInt:[newValue intValue] + 1];

    updateRowExpr = [expressionClass expressionForString:[NSString stringWithFormat:@"UPDATE EO_PK_TABLE SET PK = %d WHERE NAME = '%@' AND PK = %d", [newValue intValue], [entity name], [returnedValue intValue]]];

    NS_DURING
        //
        // Update the table
        //
        [self evaluateExpression:updateRowExpr];
    NS_HANDLER
        //[[self adaptorContext] rollbackTransaction];
        [localException raise];
    NS_ENDHANDLER

    if(_rowsProcessedCount == 1) {
        ;//[[self adaptorContext] commitTransaction];
    } else {
        // Somebody did ask for a key at the same time !
        // Redo it ?
        //[[self adaptorContext] rollbackTransaction];
        return nil;
    }

    return [NSDictionary dictionaryWithObjectsAndKeys:newValue, [primAttribute name], nil];
}



// Table description
- (NSArray *)describeTableNames
    // Reads and returns an array of table names from the database.  This
    // method in conjunction with describeModelWithTableNames: is used for
    // building a default model in EOModeler. This methods may raise an
    // exception if an error occurs
{
    RETCODE result;
    NSDictionary *row;
    NSMutableArray *tables = [NSMutableArray array];

    if (_debug) NSLog (@" *** [%@ describeTableNames]\n", self);

    // Execute SQL statement
    result = SQLTables(_statement, NULL, 0, NULL, 0, NULL, 0, NULL, 0);

    if ( result != SQL_SUCCESS ) {
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLTables in -[ODBCChannel describeTableNames]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    //
    // Do we need to disable the delegate here ?
    //
    [self _beginFetch];

    while(row = [self fetchRowWithZone:NULL]) {
        NSString *tableName = [row objectForKey:@"TABLE_NAME"];
        [tables addObject:tableName];
    }

    [self _endFetch];

    return tables;
}

- (void)_getAttributesForEntity:(EOEntity *)entity
{
    RETCODE result;
    NSDictionary *row;
    const char *cStringTableName = [[entity name] cString];
    
    if (_debug) NSLog (@" *** [%@ describeTable:%s]\n", self, cStringTableName);

    result = SQLColumns(_statement, NULL, 0, NULL, 0, (char *)cStringTableName, strlen(cStringTableName), NULL, 0);

    if ( result != SQL_SUCCESS ) {
        [(ODBCContext *)_context odbcErrorWithChannel:self string:@"SQLColumns in -[ODBCChannel _getAttributesForEntity:]" raise: (result != SQL_SUCCESS_WITH_INFO)];
    }

    //
    // Do we need to disable the delegate here ?
    //
    [self _beginFetch];

    while(row = [self fetchRowWithZone:NULL]) {
        id value;
        EOAttribute *attribute;
        NSString *columnName = [row objectForKey:@"COLUMN_NAME"];
        NSString *externalType = [row objectForKey:@"TYPE_NAME"];
        int odbcType = [[row objectForKey:@"DATA_TYPE"] intValue];
        int precision, scale, length;
        BOOL nullable = YES;
        
        value = [row objectForKey:@"PRECISION"];
        if(value == [EONull null])
            precision = 0;
        else
            precision = [value intValue];

        value = [row objectForKey:@"SCALE"];
        if(value == [EONull null])
            scale = 0;
        else
            scale = [value intValue];

        value = [row objectForKey:@"SCALE"];
        if(value == [EONull null])
            scale = 0;
        else
            scale = [value intValue];

        value = [row objectForKey:@"LENGTH"];
        if(value == [EONull null])
            length = 0;
        else
            length = [value intValue];

        if([[row objectForKey:@"NULLABLE"] intValue] == SQL_NO_NULLS)
            nullable = NO;

        attribute = [(ODBCAdaptor *)[_context adaptor] attributeWithName:columnName columnName:columnName externalType:externalType odbcType:odbcType length:length precision:precision scale:scale nullable:nullable];

        [entity addAttribute:attribute];
    }

    [self _endFetch];

}


- (EOModel *)describeModelWithTableNames:(NSArray *)tableNames
    // Constructs a default model out of the database's meta data.  It also
    // put the adaptor name and connection dictionary in the new model.
    // This methods may raise an exception if an error occurs.
{
    EOModel *model = [[EOModel new] autorelease];
    EOAdaptor *adaptor = [[self adaptorContext] adaptor];
    unsigned i,c;
    
    [model setAdaptorName:[adaptor name]];
    [model setConnectionDictionary:[adaptor connectionDictionary]];

    for(i=0, c=[tableNames count]; i < c; i++) {
        NSString *table = [tableNames objectAtIndex:i];

        EOEntity *entity;

        entity = [[EOEntity allocWithZone:[model zone]] init];
        [entity setName:table];
        [entity setExternalName:table];
        [model addEntity:entity];
        [entity release];

        [self _getAttributesForEntity:entity];
        
    }
    
    return model;
}

@end

@implementation ODBCChannel(EOStoredProcedures)

#if 0
#warning TODO: Stored proc support
- (void)describeStoredProceduresForModel:(EOModel *)model;
    // This method may raise an exception if an error occcurs.


- (void)executeStoredProcedure:(EOStoredProcedure *)storedProcedure withValues:(NSDictionary *)values
{
    _EORequestConcreteImplementation (self, _cmd, [EOAdaptorChannel class]);
}
#endif
@end
