home *** CD-ROM | disk | FTP | other *** search
- /*
- ODBCColumn.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"
-
- #define ODBC_MAX_RETLEN NSRoundDownToMultipleOfPageSize(65535)
-
- @implementation ODBCColumn
-
- static ODBCColumn *uniquePlaceHolderColumn = nil;
-
- + allocWithZone:(NSZone *)zone
- {
- if ([self class] != [ODBCColumn class]) {
- return [super allocWithZone:zone];
- }
-
- if (!uniquePlaceHolderColumn) {
- uniquePlaceHolderColumn = [[super allocWithZone:zone] init];
- }
-
- return uniquePlaceHolderColumn;
- }
-
- + (Class)columnClassForAttribute:(EOAttribute *)attribute
- {
- switch ([attribute adaptorValueType]) {
-
- case EOAdaptorNumberType:
- return [ODBCNumberColumn class];
-
- case EOAdaptorCharactersType:
- case EOAdaptorBytesType:
-
- if(![attribute width])
- return [ODBCLongByteColumn class];
-
- if([attribute width] > ODBC_MAX_RETLEN)
- return [ODBCLongByteColumn class];
-
- return [ODBCByteColumn class];
-
-
- case EOAdaptorDateType:
- return [ODBCDateColumn class];
- }
- return [ODBCByteColumn class];
- }
-
- - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
- {
- if (self == uniquePlaceHolderColumn) {
- Class realColumnClass = [ODBCColumn columnClassForAttribute:attribute];
-
- return [[realColumnClass alloc] initWithAttribute:attribute channel:channel];
- }
-
- //
- // Overide in subclass
- //
- _cType = SQL_C_CHAR;
- _valueLength = [attribute width];
- _returnedLength = SQL_NO_TOTAL;
-
- //
- // Used only if we are NOT binding
- //
- _column = 0;
- _statement = 0;
-
- //
- // Buffer to hold the data
- //
- _value = NULL;
-
- //
- // Cached info
- //
- _attribute = [attribute retain];
- _adaptorValueType = [attribute adaptorValueType];
- _encoding = [[[channel adaptorContext] adaptor] databaseEncoding];
- _channel = channel;
- return self;
- }
-
- - (void)dealloc
- {
- [_attribute release];
- [self freeValue];
- [super dealloc];
- }
-
- - (void)allocateValue
- {
- if(_value) {
- [NSException raise: EOGeneralAdaptorException format: @"-[ODBCColumn allocateValue]: memory already allocated"];
- }
-
- _value = NSZoneMalloc ([self zone], _valueLength + 1);
-
- if(!_value) {
- [NSException raise: EOGeneralAdaptorException format: @"-[ODBCColumn allocateValue]: Unable to allocate memory"];
- }
- }
-
- - (void)freeValue
- {
- if(!_value) {
- [NSException raise: EOGeneralAdaptorException format: @"-[ODBCColumn freeValue]: Nothing to free !"];
- }
-
- NSZoneFree ([self zone], _value);
- _value = NULL;
- }
-
- - (BOOL)couldBind
- {
- return YES;
- }
-
- - (void)connectToColumn:(unsigned)index ofStatement:(void *)statement useBinding:(BOOL)useBinding;
- {
- if(useBinding && [self couldBind]) {
- [self bindToColumn:index ofStatement:statement];
- } else {
- // Allocate memory to bind and keep track of column
- if(!_value) [self allocateValue];
- _column = index;
- _statement = statement;
- }
- }
-
-
- - (void)bindToColumn:(unsigned)column ofStatement:(void *)statement
- {
- RETCODE result;
-
- // Allocate memory to bind
- if(!_value) [self allocateValue];
-
- // bind memory to specified column
- result = SQLBindCol (statement, column, _cType, _value, _valueLength, &_returnedLength);
-
- if(result != SQL_SUCCESS) {
- if(result == SQL_SUCCESS_WITH_INFO) {
- [self freeValue];
- }
- [(ODBCContext *)[_channel adaptorContext] odbcErrorWithChannel:_channel string:@"SQLBindCol in -[ODBCColumn bindToColumn:ofStatement:]" raise:(result != SQL_SUCCESS_WITH_INFO)];
- }
- }
-
- - (id)buildValueFromSQLValue:(const void *)value length:(unsigned)length zone:(NSZone *)zone
- {
- [NSException raise:EOGeneralAdaptorException format:@"-[ODBCColumn buildValueFromSQLValue:length:zone:] should be implemented by the subclass"];
- return nil;
- }
-
- - (id)fetchWithZone:(NSZone *)zone
- {
- if(!_column) {
- // The column is bound to _value.
- return [self buildValueFromSQLValue:_value length:_returnedLength zone:zone];
- } else {
- RETCODE result;
- NSMutableData *data;
- id value;
-
- // Try to get first chunk of data
- _returnedLength = SQL_NO_TOTAL;
- result = SQLGetData(_statement, _column, _cType, _value, _valueLength, &_returnedLength);
- if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
- [(ODBCContext *)[_channel adaptorContext] odbcErrorWithChannel:_channel string:@"SQLGetData in -[ODBCLongByteColumn fetchWithZone:]" raise:YES];
- }
-
- // We need to make sure here, if we get SQL_SUCCESS_WITH_INFO,
- // that one of the reasons is REALLY 01004 (Data Truncated)
-
-
- if(_returnedLength == SQL_NULL_DATA) return [EONull null];
-
- // Check if we got the entire contents of the field on the first try
- if (result == SQL_SUCCESS) { // Test is bogus...
- return [self buildValueFromSQLValue:_value length:_returnedLength zone:zone];
- }
-
- // Allocate and initialize data with bytes
- data = [[NSMutableData alloc] initWithBytes:_value length:_returnedLength];
-
- // Keep on getting the rest of the data and appending it
- do {
- _returnedLength = SQL_NO_TOTAL;
- result = SQLGetData (_statement, _column, _cType, _value, _valueLength, &_returnedLength);
- if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO && result != SQL_NO_DATA_FOUND) {
- [data release];
- [(ODBCContext *)[_channel adaptorContext] odbcErrorWithChannel:_channel string:@"SQLGetData in -[ODBCLongByteColumn fetchWithZone:]" raise:YES];
- }
- [data appendBytes:_value length:_returnedLength];
- } while( result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND);
-
- value = [self buildValueFromSQLValue:[data bytes] length:[data length] zone:zone];
- [data release];
- return value;
- }
- }
-
-
- - (void)bindAttribute:(EOAttribute *)attribute forInputColumn:(unsigned)column ofStatement:(void *)statement
- {
- RETCODE result;
- int odbcType = [attribute odbcType];
- int precision = 0;
-
- switch(_adaptorValueType) {
- case EOAdaptorNumberType:
- precision = [attribute precision];
- break;
- case EOAdaptorCharactersType:
- case EOAdaptorBytesType:
- precision = [attribute width];
- break;
- case EOAdaptorDateType:
- // I have no clue !
- precision = 23; // whatever !
- }
-
- // Allocate memory to bind
- if(!_value) [self allocateValue];
-
- // bind memory to specified column
- result = SQLBindParameter(statement, column, SQL_PARAM_INPUT, _cType, odbcType , precision, [attribute scale], _value, _valueLength, &_returnedLength);
-
- if(result != SQL_SUCCESS) {
- if(result == SQL_SUCCESS_WITH_INFO) {
- [self freeValue];
- }
- [(ODBCContext *)[_channel adaptorContext] odbcErrorWithChannel:_channel string:@"SQLBindCol in -[ODBCColumn bindToColumn:ofStatement:]" raise: (result != SQL_SUCCESS_WITH_INFO)];
- }
- }
-
-
- - (void)takeInputValue:(id)value
- {
- [NSException raise:EOGeneralAdaptorException format:@"takeInputValue: should be implemented by the subclass"];
- }
-
-
- @end
-
- @implementation ODBCNumberColumn
-
- - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
- {
- [super initWithAttribute:attribute channel:channel];
-
- // Initialize value type and length
- if ([[attribute valueClassName] isEqualToString:@"NSDecimalNumber"]) {
- // we read decimal number as strings
- _cType = SQL_C_CHAR;
- _valueLength = (sizeof(char) * [attribute precision]) + 10; // sign, exponent, this kind of stuff...
- } else {
- switch ((unichar) [[attribute valueType] characterAtIndex:0]) {
- case 'c':
- case 's':
- //_cType = SQL_C_SSHORT;
- _cType = SQL_C_SHORT;
- //_odbcType = SQL_SMALLINT;
- _valueLength = sizeof (short);
- break;
- case 'i':
- case 'l':
- //_cType = SQL_C_SLONG;
- _cType = SQL_C_LONG;
- //_odbcType = SQL_BIGINT;
- _valueLength = sizeof (long);
- break;
- case 'C':
- case 'S':
- //_cType = SQL_C_USHORT;
- _cType = SQL_C_SHORT;
- //_odbcType = SQL_SMALLINT;
- _valueLength = sizeof (unsigned short);
- break;
- case 'I':
- case 'L':
- //_cType = SQL_C_ULONG;
- _cType = SQL_C_LONG;
- //_odbcType = SQL_BIGINT;
- _valueLength = sizeof (unsigned long);
- break;
- case 'f':
- _cType = SQL_C_FLOAT;
- //_odbcType = SQL_FLOAT;
- _valueLength = sizeof (float);
- break;
- case 'd':
- _cType = SQL_C_DOUBLE;
- //_odbcType = SQL_DOUBLE;
- _valueLength = sizeof (double);
- break;
- default:
- [NSException raise:NSInternalInconsistencyException
- format:@"*** -[%@ %s] unknown number type: \"%@\"",
- NSStringFromClass([self class]), sel_getName(_cmd),
- [attribute valueType]];
- }
- }
- return self;
- }
-
- - (id)buildValueFromSQLValue:(const void *)value length:(unsigned)length zone:(NSZone *)zone
- {
- // Check for nulls
- if (length == SQL_NULL_DATA) return [EONull null];
-
- // Check against _cType to determine how to construct the number.
- switch (_cType) {
- case SQL_C_SHORT :
- case SQL_C_SSHORT :
- return [[NSNumber allocWithZone:zone] initWithShort:*(short *)value];
- case SQL_C_USHORT :
- return [[NSNumber allocWithZone:zone] initWithUnsignedShort:*(unsigned short *)value];
- case SQL_C_LONG :
- case SQL_C_SLONG :
- return [[NSNumber allocWithZone:zone] initWithLong:*(long *)value];
- case SQL_C_ULONG :
- return [[NSNumber allocWithZone:zone] initWithLong:*(unsigned long *)value];
- case SQL_C_FLOAT :
- return [[NSNumber allocWithZone:zone] initWithFloat:*(float *)value];
- case SQL_C_DOUBLE :
- return [[NSNumber allocWithZone:zone] initWithDouble:*(double *)value];
- case SQL_C_CHAR :
- return [[NSDecimalNumber allocWithZone:zone] initWithString: [NSString stringWithCString:value length:length]];
- }
- // Should never get here
- return [EONull null];
- }
-
- - (void)takeInputValue:(id)value
- {
- // Check for nulls
- if(value == [EONull null]) {
- _returnedLength = SQL_NULL_DATA;
- return;
- }
-
- // Check against _cType to determine how to build the number.
- switch (_cType) {
- case SQL_C_SHORT :
- case SQL_C_SSHORT :
- *(short *)_value = [value shortValue];
- _returnedLength = sizeof(short);
- break;
- case SQL_C_USHORT :
- *(unsigned short *)_value = [value unsignedShortValue];
- _returnedLength = sizeof(unsigned short);
- break;
- case SQL_C_LONG :
- case SQL_C_SLONG :
- *(long *)_value = [value longValue];
- _returnedLength = sizeof(long);
- break;
- case SQL_C_ULONG :
- *(unsigned long *)_value = [value unsignedLongValue];
- _returnedLength = sizeof(unsigned long);
- break;
- case SQL_C_FLOAT :
- *(float *)_value = [value floatValue];
- _returnedLength = sizeof(float);
- break;
- case SQL_C_DOUBLE :
- *(double *)_value = [value doubleValue];
- _returnedLength = sizeof(double);
- break;
- case SQL_C_CHAR : {
- const char *cString =[[value stringValue] cString];
- memcpy(_value, cString, strlen(cString) + 1);
- _returnedLength = SQL_NTS;
- break;
- }
- }
- }
-
- @end
-
- @implementation ODBCDateColumn
-
- - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
- {
- [super initWithAttribute:attribute channel:channel];
- _valueLength = sizeof (TIMESTAMP_STRUCT);
- _cType = SQL_C_TIMESTAMP;
- return self;
- }
-
- - (id)buildValueFromSQLValue:(const void *)value length:(unsigned)length zone:(NSZone *)zone
- {
- // Check if we got a value
- if (length == SQL_NULL_DATA) return [EONull null];
-
- return [_attribute newDateForYear:((TIMESTAMP_STRUCT*)value)->year
- month:((TIMESTAMP_STRUCT*)value)->month
- day:((TIMESTAMP_STRUCT*)value)->day
- hour:((TIMESTAMP_STRUCT*)value)->hour
- minute:((TIMESTAMP_STRUCT*)value)->minute
- second:((TIMESTAMP_STRUCT*)value)->second
- millisecond:0 /*((TIMESTAMP_STRUCT*)value)->fraction*/
- timezone:nil
- zone:zone];
- }
-
- - (void)takeInputValue:(id)value
- {
- TIMESTAMP_STRUCT *date;
-
- // Check for nulls
- if(value == [EONull null]) {
- _returnedLength = SQL_NULL_DATA;
- return;
- }
-
- date = (void *)_value;
-
- date->year = [value yearOfCommonEra];
- date->month = [value monthOfYear];
- date->day = [value dayOfMonth];
- date->hour = [value hourOfDay];
- date->minute = [value minuteOfHour];
- date->second = [value secondOfMinute];
- date->fraction = 0 /*[value milliseconds]*/;
-
- _returnedLength = sizeof(TIMESTAMP_STRUCT);
- }
-
- @end
-
- @implementation ODBCByteColumn
-
- - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
- {
- [super initWithAttribute:attribute channel:channel];
-
- if(_adaptorValueType == EOAdaptorCharactersType) {
- _cType = SQL_C_CHAR;
- _valueLength += 1; // make some space for \0.
- } else {
- _cType = SQL_C_BINARY;
- }
-
- return self;
- }
-
- - (id)buildValueFromSQLValue:(const void *)value length:(unsigned)length zone:(NSZone *)zone
- {
- // Check for NULL first by looking at the number of bytes returned
- if (length == SQL_NULL_DATA) return [EONull null];
-
- // create a value of type NSString if the adaptorValueType is a string
- if (_adaptorValueType == EOAdaptorCharactersType) {
- // remove trailing spaces
- while(length && (((char *)value)[length-1] == ' ')) length -= 1;
- return [_attribute newValueForBytes:value length:length encoding:_encoding];
- } else {
- return [_attribute newValueForBytes:value length:length];
- }
- }
-
- - (void)takeInputValue:(id)value
- {
- // Check for nulls
- if(value == [EONull null]) {
- _returnedLength = SQL_NULL_DATA;
- return;
- }
-
- // Check against _cType to determine how to build the number.
- switch (_cType) {
- case SQL_C_CHAR : {
- const char *cString = [value cString];
- memcpy(_value, cString, strlen(cString) + 1);
- _returnedLength = SQL_NTS;
- break;
- }
-
- case SQL_C_BINARY: {
- memcpy(_value, [value bytes], [value length]);
- _returnedLength = [value length];
- break;
- }
- }
- }
-
- @end
-
- @implementation ODBCLongByteColumn
-
- - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
- {
- [super initWithAttribute:attribute channel:channel];
-
- // Make the column length the largest multiple of a page
- _valueLength = ODBC_MAX_RETLEN;
-
- return self;
- }
-
- - (BOOL)couldBind
- {
- return NO;
- }
-
- - (void)takeInputValue:(id)value
- {
- // Check for nulls
- if(value == [EONull null]) {
- _returnedLength = SQL_NULL_DATA;
- return;
- }
-
- // Check against _cType to determine how to build the value.
- switch (_cType) {
- case SQL_C_CHAR : {
- const char *cString = [value cString];
- if(_valueLength < (strlen(cString) + 1)) {
- [self freeValue];
- _valueLength = strlen(cString) + 1;
- [self allocateValue];
- }
-
- memcpy(_value, cString, strlen(cString) + 1);
- _returnedLength = SQL_NTS;
- break;
- }
-
- case SQL_C_BINARY: {
- if(_valueLength < [value length]) {
- [self freeValue];
- _valueLength = [value length];
- [self allocateValue];
- }
- memcpy(_value, [value bytes], [value length]);
- _returnedLength = [value length];
- break;
- }
- }
- }
-
- @end
-