home *** CD-ROM | disk | FTP | other *** search
/ OpenStep (Enterprise) / OpenStepENTCD.toast / OEDEV / EODEV.Z / ODBCColumn.m < prev    next >
Encoding:
Text File  |  1996-09-10  |  17.0 KB  |  563 lines

  1. /*
  2.    ODBCColumn.m
  3.  
  4.    Copyright (c) 1996 NeXT Software, Inc.  All rights reserved.
  5.  
  6.    IMPORTANT:  This NeXT software is supplied to you in consideration of your agreement
  7.    to the terms of the NeXT Software License Agreement stated in the file ODBC_LICENSE.rtf
  8.    provided with the software.  Your use of the software is governed by such terms.
  9.    Do not use, install, or make copies of the software if you do not agree to such terms.
  10.  
  11.    THIS SOFTWARE IS FURNISHED ON AN "AS-IS" BASIS. NeXT MAKES NO WARRANTIES OF ANY KIND,
  12.    EITHER EXPRESS OR IMPLIED, AS TO ANY MATTER WHATSOEVER, INCLUDING WITHOUT LIMITATION
  13.    THE CONDITION, MERCHANTABILITY, OR FITNESS FOR ANY PARTICULAR PURPOSE OF THIS SOFTWARE.
  14.    NeXT DOES NOT ASSUME ANY LIABILITY REGARDING USE OF, OR ANY DEFECT IN, THIS SOFTWARE.
  15.    IN NO EVENT SHALL NeXT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
  16.    DAMAGES, EVEN IF IT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  17. */
  18.  
  19. #import "ODBCPrivate.h"
  20.  
  21. #define ODBC_MAX_RETLEN  NSRoundDownToMultipleOfPageSize(65535)
  22.  
  23. @implementation ODBCColumn
  24.  
  25. static ODBCColumn *uniquePlaceHolderColumn = nil;
  26.  
  27. + allocWithZone:(NSZone *)zone
  28. {
  29.     if ([self class] != [ODBCColumn class]) {
  30.         return [super allocWithZone:zone];
  31.     }
  32.  
  33.     if (!uniquePlaceHolderColumn) {
  34.         uniquePlaceHolderColumn = [[super allocWithZone:zone] init];
  35.     }
  36.     
  37.     return uniquePlaceHolderColumn;
  38. }
  39.  
  40. + (Class)columnClassForAttribute:(EOAttribute *)attribute
  41. {
  42.     switch ([attribute adaptorValueType]) {
  43.  
  44.         case EOAdaptorNumberType:
  45.             return [ODBCNumberColumn class];
  46.  
  47.         case EOAdaptorCharactersType:
  48.         case EOAdaptorBytesType:
  49.  
  50.             if(![attribute width])
  51.                 return [ODBCLongByteColumn class];
  52.  
  53.             if([attribute width] > ODBC_MAX_RETLEN)
  54.                 return [ODBCLongByteColumn class];
  55.  
  56.             return [ODBCByteColumn class];
  57.  
  58.  
  59.         case EOAdaptorDateType:
  60.             return [ODBCDateColumn class];
  61.     }
  62.     return [ODBCByteColumn class];
  63. }
  64.  
  65. - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
  66. {
  67.     if (self == uniquePlaceHolderColumn) {
  68.         Class realColumnClass = [ODBCColumn columnClassForAttribute:attribute];
  69.  
  70.         return [[realColumnClass alloc] initWithAttribute:attribute channel:channel];
  71.     }
  72.  
  73.     //
  74.     // Overide in subclass
  75.     //
  76.     _cType = SQL_C_CHAR;
  77.     _valueLength = [attribute width];
  78.     _returnedLength = SQL_NO_TOTAL;
  79.  
  80.     //
  81.     // Used only if we are NOT binding
  82.     //
  83.     _column = 0;
  84.     _statement = 0;
  85.  
  86.     //
  87.     // Buffer to hold the data
  88.     //
  89.     _value = NULL;
  90.  
  91.     //
  92.     // Cached info
  93.     //
  94.     _attribute = [attribute retain];
  95.     _adaptorValueType = [attribute adaptorValueType];
  96.     _encoding = [[[channel adaptorContext] adaptor] databaseEncoding];
  97.     _channel = channel;
  98.     return self;
  99. }
  100.  
  101. - (void)dealloc
  102. {
  103.     [_attribute release];
  104.     [self freeValue];
  105.     [super dealloc];
  106. }
  107.  
  108. - (void)allocateValue
  109. {
  110.     if(_value) {
  111.         [NSException raise: EOGeneralAdaptorException format: @"-[ODBCColumn allocateValue]: memory already allocated"];
  112.     }
  113.     
  114.     _value = NSZoneMalloc ([self zone], _valueLength + 1);
  115.  
  116.     if(!_value) {
  117.         [NSException raise: EOGeneralAdaptorException format: @"-[ODBCColumn allocateValue]: Unable to allocate memory"];
  118.     }
  119. }
  120.  
  121. - (void)freeValue
  122. {
  123.     if(!_value) {
  124.         [NSException raise: EOGeneralAdaptorException format: @"-[ODBCColumn freeValue]: Nothing to free !"];
  125.     }
  126.  
  127.     NSZoneFree ([self zone], _value);
  128.     _value = NULL;
  129. }
  130.  
  131. - (BOOL)couldBind
  132. {
  133.     return YES;
  134. }
  135.  
  136. - (void)connectToColumn:(unsigned)index ofStatement:(void *)statement useBinding:(BOOL)useBinding;
  137. {
  138.     if(useBinding && [self couldBind]) {
  139.         [self bindToColumn:index ofStatement:statement];
  140.     } else {
  141.         // Allocate memory to bind and keep track of column
  142.         if(!_value) [self allocateValue];
  143.         _column = index;
  144.         _statement = statement;
  145.     }
  146. }
  147.  
  148.  
  149. - (void)bindToColumn:(unsigned)column ofStatement:(void *)statement
  150. {
  151.     RETCODE result;
  152.  
  153.     // Allocate memory to bind
  154.     if(!_value) [self allocateValue];
  155.  
  156.     // bind memory to specified column
  157.     result = SQLBindCol (statement, column, _cType, _value, _valueLength, &_returnedLength);
  158.  
  159.     if(result != SQL_SUCCESS) {
  160.         if(result == SQL_SUCCESS_WITH_INFO) {
  161.             [self freeValue];
  162.         }
  163.         [(ODBCContext *)[_channel adaptorContext] odbcErrorWithChannel:_channel string:@"SQLBindCol in -[ODBCColumn bindToColumn:ofStatement:]" raise:(result != SQL_SUCCESS_WITH_INFO)];
  164.     }
  165. }
  166.  
  167. - (id)buildValueFromSQLValue:(const void *)value length:(unsigned)length zone:(NSZone *)zone
  168. {
  169.     [NSException raise:EOGeneralAdaptorException format:@"-[ODBCColumn buildValueFromSQLValue:length:zone:] should be implemented by the subclass"];
  170.     return nil;
  171. }
  172.  
  173. - (id)fetchWithZone:(NSZone *)zone
  174. {
  175.     if(!_column) {
  176.         // The column is bound to _value.
  177.         return [self buildValueFromSQLValue:_value length:_returnedLength zone:zone];
  178.     } else {
  179.         RETCODE result;
  180.         NSMutableData *data;
  181.         id value;
  182.  
  183.         // Try to get first chunk of data
  184.         _returnedLength = SQL_NO_TOTAL;
  185.         result = SQLGetData(_statement, _column, _cType, _value, _valueLength, &_returnedLength);
  186.         if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO) {
  187.             [(ODBCContext *)[_channel adaptorContext] odbcErrorWithChannel:_channel string:@"SQLGetData in -[ODBCLongByteColumn fetchWithZone:]" raise:YES];
  188.         }
  189.  
  190.         // We need to make sure here, if we get SQL_SUCCESS_WITH_INFO,
  191.         // that one of the reasons is REALLY 01004 (Data Truncated)
  192.  
  193.         
  194.         if(_returnedLength == SQL_NULL_DATA) return [EONull null];
  195.  
  196.         // Check if we got the entire contents of the field on the first try
  197.         if (result == SQL_SUCCESS) { // Test is bogus...
  198.             return [self buildValueFromSQLValue:_value length:_returnedLength zone:zone];
  199.         }
  200.  
  201.         // Allocate and initialize data with bytes
  202.         data = [[NSMutableData alloc] initWithBytes:_value length:_returnedLength];
  203.  
  204.         // Keep on getting the rest of the data and appending it
  205.         do {
  206.             _returnedLength = SQL_NO_TOTAL;
  207.             result = SQLGetData (_statement, _column, _cType, _value, _valueLength, &_returnedLength);
  208.             if(result != SQL_SUCCESS && result != SQL_SUCCESS_WITH_INFO && result != SQL_NO_DATA_FOUND) {
  209.                 [data release];
  210.                 [(ODBCContext *)[_channel adaptorContext] odbcErrorWithChannel:_channel string:@"SQLGetData in -[ODBCLongByteColumn fetchWithZone:]" raise:YES];
  211.             }
  212.             [data appendBytes:_value length:_returnedLength];
  213.         } while( result != SQL_SUCCESS && result != SQL_NO_DATA_FOUND);
  214.  
  215.         value = [self buildValueFromSQLValue:[data bytes] length:[data length] zone:zone];
  216.         [data release];
  217.         return value;
  218.     }
  219. }
  220.  
  221.  
  222. - (void)bindAttribute:(EOAttribute *)attribute forInputColumn:(unsigned)column ofStatement:(void *)statement
  223. {
  224.     RETCODE result;
  225.     int odbcType = [attribute odbcType];
  226.     int precision = 0;
  227.     
  228.     switch(_adaptorValueType) {
  229.         case EOAdaptorNumberType:
  230.             precision = [attribute precision];
  231.             break;
  232.         case EOAdaptorCharactersType:
  233.         case EOAdaptorBytesType:
  234.             precision = [attribute width];
  235.             break;
  236.         case EOAdaptorDateType:
  237.             // I have no clue !
  238.             precision = 23; // whatever !
  239.     }
  240.  
  241.     // Allocate memory to bind
  242.     if(!_value) [self allocateValue];
  243.  
  244.     // bind memory to specified column
  245.     result = SQLBindParameter(statement, column, SQL_PARAM_INPUT, _cType, odbcType , precision, [attribute scale], _value, _valueLength, &_returnedLength);
  246.  
  247.     if(result != SQL_SUCCESS) {
  248.         if(result == SQL_SUCCESS_WITH_INFO) {
  249.             [self freeValue];
  250.         }
  251.         [(ODBCContext *)[_channel adaptorContext] odbcErrorWithChannel:_channel string:@"SQLBindCol in -[ODBCColumn bindToColumn:ofStatement:]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  252.     }
  253. }
  254.  
  255.  
  256. - (void)takeInputValue:(id)value
  257. {
  258.     [NSException raise:EOGeneralAdaptorException format:@"takeInputValue: should be implemented by the subclass"];
  259. }
  260.  
  261.  
  262. @end
  263.  
  264. @implementation ODBCNumberColumn
  265.  
  266. - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
  267. {
  268.     [super initWithAttribute:attribute channel:channel];
  269.  
  270.     // Initialize value type and length
  271.     if ([[attribute valueClassName] isEqualToString:@"NSDecimalNumber"]) {
  272.         // we read decimal number as strings
  273.         _cType = SQL_C_CHAR;
  274.         _valueLength = (sizeof(char) * [attribute precision]) + 10; // sign, exponent, this kind of stuff...
  275.     } else {
  276.         switch ((unichar) [[attribute valueType] characterAtIndex:0]) {
  277.           case 'c':
  278.           case 's':
  279.               //_cType = SQL_C_SSHORT;
  280.               _cType = SQL_C_SHORT;
  281.               //_odbcType = SQL_SMALLINT;
  282.               _valueLength = sizeof (short);
  283.               break;
  284.           case 'i':
  285.           case 'l':
  286.               //_cType = SQL_C_SLONG;
  287.               _cType = SQL_C_LONG;
  288.               //_odbcType = SQL_BIGINT;
  289.              _valueLength = sizeof (long);
  290.               break;
  291.           case 'C':
  292.           case 'S':
  293.               //_cType = SQL_C_USHORT;
  294.               _cType = SQL_C_SHORT;
  295.               //_odbcType = SQL_SMALLINT;
  296.               _valueLength = sizeof (unsigned short);
  297.               break;
  298.           case 'I':
  299.           case 'L':
  300.               //_cType = SQL_C_ULONG;
  301.               _cType = SQL_C_LONG;
  302.               //_odbcType = SQL_BIGINT;
  303.               _valueLength = sizeof (unsigned long);
  304.               break;
  305.           case 'f':
  306.               _cType = SQL_C_FLOAT;
  307.               //_odbcType = SQL_FLOAT;
  308.               _valueLength = sizeof (float);
  309.               break;
  310.           case 'd':
  311.               _cType = SQL_C_DOUBLE;
  312.               //_odbcType = SQL_DOUBLE;
  313.               _valueLength = sizeof (double);
  314.               break;
  315.           default:
  316.               [NSException raise:NSInternalInconsistencyException
  317.                         format:@"*** -[%@ %s] unknown number type: \"%@\"",
  318.                          NSStringFromClass([self class]), sel_getName(_cmd),
  319.                         [attribute valueType]];
  320.         }
  321.     }
  322.     return self;
  323. }
  324.  
  325. - (id)buildValueFromSQLValue:(const void *)value length:(unsigned)length zone:(NSZone *)zone
  326. {
  327.     // Check for nulls
  328.     if (length == SQL_NULL_DATA) return [EONull null];
  329.  
  330.     // Check against _cType to determine how to construct the number.
  331.     switch (_cType) {
  332.     case SQL_C_SHORT :
  333.     case SQL_C_SSHORT :
  334.         return [[NSNumber allocWithZone:zone] initWithShort:*(short *)value];
  335.     case SQL_C_USHORT :
  336.         return [[NSNumber allocWithZone:zone] initWithUnsignedShort:*(unsigned short *)value];
  337.     case SQL_C_LONG :
  338.     case SQL_C_SLONG :
  339.         return [[NSNumber allocWithZone:zone] initWithLong:*(long *)value];
  340.     case SQL_C_ULONG :
  341.         return [[NSNumber allocWithZone:zone] initWithLong:*(unsigned long *)value];
  342.     case SQL_C_FLOAT :
  343.         return [[NSNumber allocWithZone:zone] initWithFloat:*(float *)value];
  344.     case SQL_C_DOUBLE :
  345.         return [[NSNumber allocWithZone:zone] initWithDouble:*(double *)value];
  346.     case SQL_C_CHAR :
  347.         return [[NSDecimalNumber allocWithZone:zone] initWithString: [NSString stringWithCString:value length:length]];
  348.     }
  349.     // Should never get here
  350.     return [EONull null];
  351. }
  352.  
  353. - (void)takeInputValue:(id)value
  354. {
  355.     // Check for nulls
  356.     if(value == [EONull null]) {
  357.         _returnedLength = SQL_NULL_DATA;
  358.         return;
  359.     }
  360.  
  361.     // Check against _cType to determine how to build the number.
  362.     switch (_cType) {
  363.     case SQL_C_SHORT :
  364.     case SQL_C_SSHORT :
  365.         *(short *)_value = [value shortValue];
  366.         _returnedLength = sizeof(short);
  367.         break;
  368.     case SQL_C_USHORT :
  369.         *(unsigned short *)_value = [value unsignedShortValue];
  370.         _returnedLength = sizeof(unsigned short);
  371.         break;
  372.     case SQL_C_LONG :
  373.     case SQL_C_SLONG :
  374.         *(long *)_value = [value longValue];
  375.         _returnedLength = sizeof(long);
  376.         break;
  377.     case SQL_C_ULONG :
  378.         *(unsigned long *)_value = [value unsignedLongValue];
  379.         _returnedLength = sizeof(unsigned long);
  380.         break;
  381.     case SQL_C_FLOAT :
  382.         *(float *)_value = [value floatValue];
  383.         _returnedLength = sizeof(float);
  384.         break;
  385.     case SQL_C_DOUBLE :
  386.         *(double *)_value = [value doubleValue];
  387.         _returnedLength = sizeof(double);
  388.         break;
  389.     case SQL_C_CHAR : {
  390.         const char *cString =[[value stringValue] cString];
  391.         memcpy(_value, cString, strlen(cString) + 1);
  392.         _returnedLength = SQL_NTS;
  393.         break;
  394.         }
  395.     }
  396. }
  397.  
  398. @end
  399.  
  400. @implementation ODBCDateColumn
  401.  
  402. - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
  403. {
  404.     [super initWithAttribute:attribute channel:channel];
  405.     _valueLength = sizeof (TIMESTAMP_STRUCT);
  406.     _cType = SQL_C_TIMESTAMP;
  407.     return self;
  408. }
  409.  
  410. - (id)buildValueFromSQLValue:(const void *)value length:(unsigned)length zone:(NSZone *)zone
  411. {
  412.     // Check if we got a value
  413.     if (length == SQL_NULL_DATA) return [EONull null];
  414.  
  415.     return [_attribute newDateForYear:((TIMESTAMP_STRUCT*)value)->year
  416.                                 month:((TIMESTAMP_STRUCT*)value)->month
  417.                                   day:((TIMESTAMP_STRUCT*)value)->day
  418.                                  hour:((TIMESTAMP_STRUCT*)value)->hour
  419.                                minute:((TIMESTAMP_STRUCT*)value)->minute
  420.                                second:((TIMESTAMP_STRUCT*)value)->second
  421.                           millisecond:0 /*((TIMESTAMP_STRUCT*)value)->fraction*/
  422.                              timezone:nil
  423.                                  zone:zone];
  424. }
  425.  
  426. - (void)takeInputValue:(id)value
  427. {
  428.     TIMESTAMP_STRUCT *date;
  429.     
  430.     // Check for nulls
  431.     if(value == [EONull null]) {
  432.         _returnedLength = SQL_NULL_DATA;
  433.         return;
  434.     }
  435.  
  436.     date = (void *)_value;
  437.  
  438.     date->year = [value yearOfCommonEra];
  439.     date->month = [value monthOfYear];
  440.     date->day = [value dayOfMonth];
  441.     date->hour = [value hourOfDay];
  442.     date->minute = [value minuteOfHour];
  443.     date->second = [value secondOfMinute];
  444.     date->fraction = 0 /*[value milliseconds]*/;
  445.  
  446.     _returnedLength = sizeof(TIMESTAMP_STRUCT);
  447. }
  448.  
  449. @end
  450.  
  451. @implementation ODBCByteColumn
  452.  
  453. - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
  454. {
  455.     [super initWithAttribute:attribute channel:channel];
  456.     
  457.     if(_adaptorValueType == EOAdaptorCharactersType) {
  458.         _cType = SQL_C_CHAR;
  459.         _valueLength += 1; // make some space for \0.
  460.     } else {
  461.         _cType = SQL_C_BINARY;
  462.     }
  463.  
  464.     return self;
  465. }
  466.  
  467. - (id)buildValueFromSQLValue:(const void *)value length:(unsigned)length zone:(NSZone *)zone
  468. {
  469.     // Check for NULL first by looking at the number of bytes returned
  470.     if (length == SQL_NULL_DATA) return [EONull null];
  471.  
  472.     // create a value of type NSString if the adaptorValueType is a string
  473.     if (_adaptorValueType == EOAdaptorCharactersType) {
  474.         // remove trailing spaces
  475.         while(length && (((char *)value)[length-1] == ' ')) length -= 1;
  476.         return [_attribute newValueForBytes:value length:length encoding:_encoding];
  477.     } else {
  478.         return [_attribute newValueForBytes:value length:length];
  479.     }
  480. }
  481.  
  482. - (void)takeInputValue:(id)value
  483. {
  484.     // Check for nulls
  485.     if(value == [EONull null]) {
  486.         _returnedLength = SQL_NULL_DATA;
  487.         return;
  488.     }
  489.  
  490.     // Check against _cType to determine how to build the number.
  491.     switch (_cType) {
  492.         case SQL_C_CHAR : {
  493.             const char *cString = [value cString];
  494.             memcpy(_value, cString, strlen(cString) + 1);
  495.             _returnedLength = SQL_NTS;
  496.             break;
  497.         }
  498.  
  499.         case SQL_C_BINARY: {
  500.             memcpy(_value, [value bytes], [value length]);
  501.             _returnedLength = [value length];
  502.             break;
  503.         }
  504.     }
  505. }
  506.  
  507. @end
  508.  
  509. @implementation ODBCLongByteColumn
  510.  
  511. - initWithAttribute:(EOAttribute *)attribute channel:(ODBCChannel *)channel
  512. {
  513.     [super initWithAttribute:attribute channel:channel];
  514.  
  515.     // Make the column length the largest multiple of a page
  516.     _valueLength = ODBC_MAX_RETLEN;
  517.  
  518.     return self;
  519. }
  520.  
  521. - (BOOL)couldBind
  522. {
  523.     return NO;
  524. }
  525.  
  526. - (void)takeInputValue:(id)value
  527. {
  528.     // Check for nulls
  529.     if(value == [EONull null]) {
  530.         _returnedLength = SQL_NULL_DATA;
  531.         return;
  532.     }
  533.  
  534.     // Check against _cType to determine how to build the value.
  535.     switch (_cType) {
  536.         case SQL_C_CHAR : {
  537.             const char *cString = [value cString];
  538.             if(_valueLength < (strlen(cString) + 1)) {
  539.                 [self freeValue];
  540.                 _valueLength = strlen(cString) + 1;
  541.                 [self allocateValue];
  542.             }
  543.             
  544.             memcpy(_value, cString, strlen(cString) + 1);
  545.             _returnedLength = SQL_NTS;
  546.             break;
  547.         }
  548.  
  549.         case SQL_C_BINARY: {
  550.             if(_valueLength < [value length]) {
  551.                 [self freeValue];
  552.                 _valueLength = [value length];
  553.                 [self allocateValue];
  554.             }
  555.             memcpy(_value, [value bytes], [value length]);
  556.             _returnedLength = [value length];
  557.             break;
  558.         }
  559.     }
  560. }
  561.  
  562. @end
  563.