/*****************************************************************************
FILE:               Oracle7Buffer.m
LANGUAGE:           Objective-C
SYSTEM:             None
USER-INTERFACE:     None
DESCRIPTION
    $Header: /cvsroot/gnustep/gnustep/dev-libs/db/eoadaptors/Oracle7/Oracle7Buffer.m,v 1.1 2000/02/22 16:27:14 pjb Exp $
    This is the implementation of the Oracle7Buffer class.

    The class Oracle7Buffer encapsulate an O2CI7Define and the buffer data
    passed to it. It's able to convert data obtained from a fetch into
    OpenStep instances of NSNumber, NSString, NSDate, NSData.

    The implementation of the class Oracle7Buffer xxxxxxx.

AUTHORS
    <PJB>  Pascal J. Bourguignon  <pjb@imaginet.fr>
MODIFICATIONS
    $Log: Oracle7Buffer.m,v $
    Revision 1.1  2000/02/22 16:27:14  pjb
    First release of the Oracle7Adaptor.

    1999/08/02 Creation.
BUGS
    - ...
LEGAL
    Written by:  Pascal J. Bourguignon <pjb@imaginet.fr>
    Funds from:  Orange Concept (http://www.orange-concept.com/)
    Date:        August 1999 - February 2000

    This file is part of the Oracle7 Adaptor, GNUstep Database Library.
    The Oracle7 Adaptor allows access to Oracle[678] databases from GDL.

    Copyright (C) 2000 Free Software Foundation, Inc.

    This  library is  free software;  you can  redistribute  it and/or
    modify it under the terms of the GNU Lesser 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
    Lesser General Public License for more details.

    You should have  received a copy of the  GNU Lesser General Public
    License  along  with this  library;  if  not,  write to  the  Free
    Software  Foundation, Inc.,  59 Temple  Place, Suite  330, Boston,
    MA 02111-1307 USA
*****************************************************************************/

#include <Oracle7Buffer.h>
#include <Oracle7Adaptor.h>
#include <Oracle7Utilities.h>
#include <O2CI7Column.h>
#include <O2CI7Cursor.h>
#include <oci.h>
#include <NSString-Oracle7Adaptor_LongLong.h>


#define INDICATOR_MAGIC (-5431) // anything less than -3 should do.


#ifdef  DEBUG_MEMORY_CHECK

    static int memory_min_size=0;
    static int memory_max_size=0;

    static short memory_check(void* block,int size)
    {
        short result=1;
        int*  saze=(int*)(((char*)block)-(sizeof(int)+4));
        unsigned char* head=(unsigned char*)(saze+1);
        unsigned char* tail;
        unsigned char  byte=(((int)block)&0x3fc)>>2;

        if((*saze)<memory_min_size){
            NSLog(@"memory check: invalid stored size=%d < %d=minimum "
                  @"allocated size, for block at 0x%08x\n",
                  (*saze),memory_min_size,block);
            result=0;
        }
        if(memory_max_size<(*saze)){
            NSLog(@"memory check: invalid stored size=%d > %d=maximum "
                  @"allocated size, for block at 0x%08x\n",
                  (*saze),memory_max_size,block);
            result=0;
        }
        if(size<0){
            if((memory_min_size<=(*saze))&&((*saze)<=memory_max_size)){
                tail=((unsigned char*)block)+(*saze);
                if(!(  (head[0]==byte)
                     &&(head[1]==(unsigned char)(byte+1))
                     &&(head[2]==(unsigned char)(byte+2))
                     &&(head[3]==(unsigned char)(byte+3)))){
                    NSLog(@"memory check: the header have been overriden, "
                          @"for block 0x%08x\n",block);
                    result=0;
                }
                if(!(  (tail[0]==byte)
                     &&(tail[1]==(unsigned char)(byte+1))
                     &&(tail[2]==(unsigned char)(byte+2))
                     &&(tail[3]==(unsigned char)(byte+3)))){
                    NSLog(@"memory check: the trailer have been overriden, "
                          @"for block 0x%08x\n",block);
                    result=0;
                }
            }else{
                NSLog(@"memory check: the size and header have been "
                      @"overriden, for block 0x%08x\n",block);
                result=0;
            }
        }else{
            if(size<memory_min_size){
                NSLog(@"memory check: invalid user size=%d < %d=minimum "
                      @"allocated size, for block at 0x%08x\n",
                      size,memory_min_size,block);
                result=0;
            }
            if(memory_max_size<size){
                NSLog(@"memory check: invalid user size=%d > %d=maximum "
                      @"allocated size, for block at 0x%08x\n",
                      size,memory_max_size,block);
                result=0;
            }
            if(size==(*saze)){
                tail=((unsigned char*)block)+size;
                if(!(  (head[0]==byte)
                     &&(head[1]==(unsigned char)(byte+1))
                     &&(head[2]==(unsigned char)(byte+2))
                     &&(head[3]==(unsigned char)(byte+3)))){
                    NSLog(@"memory check: the header have been overriden, "
                          @"for block 0x%08x\n",block);
                    result=0;
                }
                if(!(  (tail[0]==byte)
                     &&(tail[1]==(unsigned char)(byte+1))
                     &&(tail[2]==(unsigned char)(byte+2))
                     &&(tail[3]==(unsigned char)(byte+3)))){
                    NSLog(@"memory check: the trailer have been overriden, "
                          @"for block 0x%08x\n",block);
                    result=0;
                }
             }else{
                NSLog(@"memory check: stored size=%d != %d=user size for "
                      @"block at 0x%08x\n",(*saze),size,block);

                if(!(  (head[0]==byte)
                     &&(head[1]==(unsigned char)(byte+1))
                     &&(head[2]==(unsigned char)(byte+2))
                     &&(head[3]==(unsigned char)(byte+3)))){
                    NSLog(@"memory check: the header have been overriden, "
                          @"for block 0x%08x\n",block);
                    result=0;
                }
    
                tail=((unsigned char*)block)+(*saze);
                if( (  (tail[0]==byte)
                     &&(tail[1]==(unsigned char)(byte+1))
                     &&(tail[2]==(unsigned char)(byte+2))
                     &&(tail[3]==(unsigned char)(byte+3)))){
                    NSLog(@"memory check: the right size seems to be %d, "
                          @"the stored size for block at 0x%08x\n",
                          (*saze),block);
                }

                tail=((unsigned char*)block)+size;
                if( (  (tail[0]==byte)
                     &&(tail[1]==(unsigned char)(byte+1))
                     &&(tail[2]==(unsigned char)(byte+2))
                     &&(tail[3]==(unsigned char)(byte+3)))){
                    NSLog(@"memory check: the right size seems to be %d, "
                          @"the user size for block at 0x%08x\n",size,block);
                }
            }
        }
        return(result);
    }//memory_check;


    static void* memory_allocate(int size)
    {
        //    (load "pjb-objc-mode.el")
        int*  saze=(int*)malloc(size+sizeof(int)+8);
        unsigned char* head=(unsigned char*)(saze+1);
        unsigned char* block=head+4;
        unsigned char* tail=block+size;
        unsigned char  byte=(((int)block)&0x3fc)>>2;
        if(size<=0){
            NSLog(@"memory_allocate: invalid requested size=%d\n",size);
            return(0);
        }
        *saze=size;
        if(size<memory_min_size){
            memory_min_size=size;
        }
        if(memory_max_size<size){
            memory_max_size=size;
        }
        *head++=*tail++=byte++;
        *head++=*tail++=byte++;
        *head++=*tail++=byte++;
        *head++=*tail++=byte++;
        NSCAssert(0!=memory_check(block,size),@"MEMORY CHECK");
        return(block);
    }//memory_allocate;



    static void memory_deallocate(void* block,int size)
    {
        int* saze=((int*)(((char*)block)-4))-1;
        NSCAssert(0!=memory_check(block,size),@"MEMORY CHECK");
        free(saze);
    }//memory_deallocate;


#else
#define memory_check(block,size)      (1)
#define memory_allocate(size)         (malloc(size))
#define memory_deallocate(block,size) (free(block))
#endif


@interface Oracle7Buffer(Private)

    -(void)releaseData;
        /*
            POST:       self is in mint condition.
        */

    -(void)getDataType:(ub2*)dt andDataLength:(ub2*)dl 
            forAttribute:(EOAttribute*)attribute
            andColumn:(O2CI7Column*)column;
        /*
            DO:         Determine the dataType and dataLength for the buffer.
                        This is done by choosing a dataType adapted to the 
                        attribute (in order to let the conversions be done by
                        OCI), or to the column in the last resort.

            POST:       dt and dl are set apropriately for the expected type
                        for the attribute, or by default, for the column.
        */



    -(BOOL)isNullOrWhat;
        /*
            PRE:        A fetch has been done ([statement fetchRow:1]).

            DO:         Checks the indicator.

            RAISE:      Oracle7Adaptor_eInternalError when the data has been 
                                                          truncated.

            RETURN:     column value is null.
        */


@end //Oracle7Buffer(Private)


// -------------------------------------------------------------------------- //

@implementation Oracle7Buffer(Private)

    -(void)releaseData
    {
        [attribute release]; attribute=nil;
        [column    release]; column=nil;
        if(buffer!=nil){
            memory_deallocate(buffer,-1); buffer=nil;
        }
    }//releaseData;


    -(void)getDataType:(ub2*)dt andDataLength:(ub2*)dl 
            forAttribute:(EOAttribute*)anAttribute
            andColumn:(O2CI7Column*)aColumn
    {
        // http://technet.oracle.com/doc/server.805/a58234/datatype.htm#431514
        // http://technet.oracle.com/doc/server73x/OCI73/ch3.htm#ch3_internal
        // http://technet.oracle.com/doc/server73x/OCI73/ch3.htm#ch3_external
        static struct { NSString* cn; NSString* st; ub2 dt; ub2 dl; } 
        mapping[]={
            {@"NSNumber",      @"d",SQLT_FLT,  sizeof(double)},
            {@"NSNumber",      @"i",SQLT_INT,  sizeof(long int)},
            {@"NSNumber",      @"s",SQLT_INT,  sizeof(short int)},
            {@"NSNumber",      @"c",SQLT_INT,  sizeof(char)},
            {@"NSNumber",      nil, SQLT_AVC,  30},
            {@"NSString",      nil, SQLT_STR,  2001},
            {@"NSCalendarDate",nil, SQLT_DAT,  7},
            {@"NSDate",        nil, SQLT_DAT,  7},
            {@"NSData",        nil, SQLT_BIN,  2001},
            {nil,              nil, 0,         0}};
        int i=0;
        NSString* vcn=[anAttribute valueClassName];
        NSString* vt=[anAttribute valueType];
        while(!((mapping[i].cn==nil)
            ||(([vcn isEqualToString:mapping[i].cn])
                &&((mapping[i].st==nil)
                    ||([vt isEqualToString:mapping[i].st]))))){
            i++;
        }
        if(mapping[i].cn==nil){
            (*dt)=[aColumn dataType];
            (*dl)=[aColumn dataSize];
        }else{
            (*dt)=mapping[i].dt;
            (*dl)=mapping[i].dl;
        }
    }//getDataType:andDataLength:forAttribute:andColumn:;





    -(BOOL)isNullOrWhat
    {
        // NSLog(@"isNullOrWhat: %@ indicator=%d returnedLength=%d (bufferSize=%d%@) returnCode=%d\n",[column name],indicator,dataLength,bufferSize,bufferSize<dataLength?@"***":@"",returnCode);

        if(column==nil){
            return(YES);
        }

        switch(indicator){
        case -2:
            [NSException raise:Oracle7Adaptor_eInternalError
                format:NSLocalizedStringFromTableInBundle(@"Truncated data for attribute %@, column=%@, allocated length=%d, value length is greater than 32767",@"Oracle7Adaptor",Oracle7Utilities_Bundle(),@"Exception raison when some big data has been truncated upon a fetch. [1]:attribute name. [2]:column name. [3]:allocated length (that was too small)."),
                        [attribute name],[attribute columnName],bufferSize];
            
        case -1:
            return(YES);

        case 0:
            return(NO);
        
        case INDICATOR_MAGIC:
            [NSException raise:Oracle7Adaptor_eInternalError
                    format:NSLocalizedStringFromTableInBundle(@"Did you do a fetch? You should have, before trying to get data from the buffer!",@"Oracle7Adaptor",Oracle7Utilities_Bundle(),@"Exception raison given when the client tries to get data from the buffer before fetching a row.")];
            
        default:
            if(indicator>0){
                [NSException raise:Oracle7Adaptor_eInternalError
                    format:NSLocalizedStringFromTableInBundle(@"Truncated data for attribute %@, column=%@, allocated length=%d, value length=%d",@"Oracle7Adaptor",Oracle7Utilities_Bundle(),@"Exception raison when some big data has been truncated upon a fetch. [1]:attribute name. [2]:column name. [3]:allocated length (that was too small). [4]:actuall data length."),
                        [attribute name],[attribute columnName],
                        bufferSize,indicator];
            }else{
                [NSException raise:Oracle7Adaptor_eInternalError
                    format:NSLocalizedStringFromTableInBundle(@"Unexpected indicator value (ind=%d<-2)",@"Oracle7Adaptor",Oracle7Utilities_Bundle(),@"Exception raison when an unexpected (and undocumented) indicator code is returned. [1]:the invalid indicator value"),indicator];
            }
        }
    }//isNullOrWhat;


@end //Oracle7Buffer(Private)

// -------------------------------------------------------------------------- //


@implementation Oracle7Buffer

    static const char* rcsid="$Id";

// NSObject methods:

    -(id)init
    {
        self=[super init];
        if(self!=nil){
            column=nil;
            buffer=nil;
            bufferSize=0;
            dataType=0;
            indicator=INDICATOR_MAGIC;
            dataLength=0;
            returnCode=0;
            attribute=nil;
        }
        return(self);
    }//init;


    -(void)dealloc
    {
        [self releaseData];
        [super dealloc];
    }//dealloc;



// NSObject methods:

    
// Oracle7Buffer methods:


    -(BOOL)defineForColumn:(O2CI7Column*)aColumn 
                atPosition:(ub4)position
                  inCursor:(O2CI7Cursor*)aCursor
               asAttribute:(EOAttribute*)anAttribute
    {
        ub2 dl;
        if(column!=nil){
            [self releaseData];
        }
        column=[aColumn retain];
        attribute=[anAttribute retain];
        bufferSize=[column dataSize];
        [self getDataType:&dataType andDataLength:&dl
                forAttribute:attribute andColumn:column];
        bufferSize=(ub4)dl;             
        buffer=(void*)memory_allocate(bufferSize);
        //NSLog(@"define name:%@ size=%d bufferSize=%d",
        //[column name],[column dataSize],bufferSize);
        indicator=INDICATOR_MAGIC;
        dataLength=0;
        returnCode=0;

        if([aCursor defineAttributeAtPosition:position
                    buffer:buffer size:bufferSize type:dataType
                    indicator:&indicator dataLength:&dataLength
                    returnCode:&returnCode]){
            return(YES);
        }else{
            [self releaseData];
            return(NO);
        }
    }//defineForColumn:atPosition:inCursor:asAttribute:;


    -(NSData*)asData
    {
        NSAssert(0!=memory_check(buffer,-1),@"MEMORY CHECK");
        if([self isNullOrWhat]){
            return([EONull null]);
        }else{
            return([NSData dataWithBytes:buffer length:dataLength]);
        }
    }//asData;


    -(NSNumber*)asNumber
    {
        NSAssert(0!=memory_check(buffer,-1),@"MEMORY CHECK");
        if([self isNullOrWhat]){
            return([EONull null]);
        }else{
            switch(dataType){
            case SQLT_INT:
                switch(dataLength){
                case 1:
                    return([NSNumber numberWithChar:*(char*)buffer]);
                case 2:
                    return([NSNumber numberWithShort:*(short*)buffer]);
                case 4:
                    return([NSNumber numberWithLong:*(long*)buffer]);
                default:
                }                   
                break;
            case SQLT_FLT:
                switch(dataLength){
                case 4:
                    return([NSNumber numberWithFloat:*(float*)buffer]);
                case 8:
                    return([NSNumber numberWithDouble:*(double*)buffer]);
                default:
                }
                break;
            case SQLT_AVC:
                return([NSNumber numberWithLongLong:[
                     [NSString stringWithCString:(char*)buffer]
                            oracle7AdaptorLongLongValue]]);
            default:
                break;
            }
        }
        [NSException raise:Oracle7Adaptor_eInternalError
            format:NSLocalizedStringFromTableInBundle(@"Unexpected dataType/dataSize for a NSNumber. type=%d  size=%d",@"Oracle7Adaptor",Oracle7Utilities_Bundle(),@"Exception raison given when data size for a number is not handled. [1]:the data type. [2]:the data size."),dataType,dataLength];
    }//asNumber;


    -(NSString*)asString
    {
        NSAssert(0!=memory_check(buffer,-1),@"MEMORY CHECK");
        if([self isNullOrWhat]){
            return([EONull null]);
        }else{
            return([NSString stringWithCString:buffer]);
        }
    }//asString;


    -(NSDate*)asDate
    {
        return([self asCalendarDate]);
    }//asDate;


    -(NSCalendarDate*)asCalendarDate
    {
        NSAssert(0!=memory_check(buffer,-1),@"MEMORY CHECK");
        if([self isNullOrWhat]){
            return([EONull null]);
        }else{
            static NSTimeZone* GMT=nil;
            signed char* db=(signed char*)buffer;
            if(GMT==nil){
                GMT=[NSTimeZone timeZoneWithName:@"GMT"];
            }
// NSLog(@"date: %3d %3d / %2d / %2d %2d : %2d : %2d\n",
// db[0],db[1],db[2],db[3],db[4],db[5],db[6]);
            return([NSCalendarDate dateWithYear:
                (((int)((unsigned char)db[0]))-100)*100
                +(((int)((unsigned char)(db[1])))-100)
                month:db[2] day:db[3] 
                hour:db[4]-1 minute:db[5]-1 second:db[6]-1 timeZone:GMT]);
        }
    }//asCalendarDate;



    -(void)fillRow:(NSMutableDictionary*)row
    {
        static struct { NSString* cn; SEL s; } mapping[]={
            {@"NSNumber",      @selector(asNumber)},
            {@"NSString",      @selector(asString)},
            {@"NSCalendarDate",@selector(asCalendarDate)},
            {@"NSDate",        @selector(asDate)},
            {@"NSData",        @selector(asData)},
            {nil,              @selector(asData)}};

        NSString* vcn;
        int i;
        if(column==nil){ /* == (attribute==nil) */
            return;
        }
        vcn=[attribute valueClassName];
        i=0;
        while((mapping[i].cn!=nil)&&(![vcn isEqualToString:mapping[i].cn])){
            i++;
        }
        [row setObject:[self performSelector:mapping[i].s]
                forKey:[attribute name]];
//NSLog(@"fillRow: key=%-20@ object=%@\n",[attribute name],[[row objectForKey:[attribute name]]description]);
    }//fillRow:;


@end //Oracle7Buffer;

/*** Oracle7Buffer.m                  -- 2000-02-21 07:11:21 -- PJB ***/
