home *** CD-ROM | disk | FTP | other *** search
- /*
- ODBCAdaptor.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"
-
- //
- // The unique ODBCEnvironment variable. Shared by
- // all the instances of ODBCAdaptor
- //
- static HENV odbcEnvironment = NULL;
-
- @implementation ODBCAdaptor
-
- + (void)initialize
- {
- //
- // Initialize once for all the odbcEnvironment variable
- //
- if (!odbcEnvironment && SQLAllocEnv(&odbcEnvironment) != SQL_SUCCESS)
- [NSException raise:EOGeneralAdaptorException format:@"Unable to allocate ODBC environment"];
- }
-
- - initWithName:(NSString *)name
- {
- //
- // This method does nothing. I kept it here
- // only as a reminder: this is the designated
- // initializer
- //
- [super initWithName:name];
- return self;
- }
-
- - (EOAdaptorContext *)createAdaptorContext
- {
- return [[[ODBCContext allocWithZone:[self zone]] initWithAdaptor:self] autorelease];
- }
-
- - (Class)defaultExpressionClass
- {
- return [ODBCSQLExpression class];
- }
-
- - (BOOL)isValidQualifierType:(NSString *)typeName model:(EOModel *)model
- {
- NSDictionary *dict = [ODBCAdaptor typeInfoForModel:model];
- NSDictionary *typeInfo;
-
- typeInfo = [dict objectForKey:typeName];
-
- if(!typeInfo) {
- return NO;
- }
-
- return [[typeInfo objectForKey:@"isSearchable"] isEqualToString:@"YES"];
- }
-
- - (void)assertConnectionDictionaryIsValid
- {
- // If there is already an open channel, there is no need to
- // retest the connection
- //
- if(![self hasOpenChannels]) {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- ODBCContext *context = [self createAdaptorContext];
- ODBCChannel *channel = [context createAdaptorChannel];
-
- [channel openChannel];
- [channel closeChannel]; // Just try to connect to the database
-
- [pool release];
- // The channel is autoreleased, so everything should go away
- // with the pool
- }
- }
-
- - (NSString *)odbcConnectionString
- {
- NSString *userName, *password, *dataSource, *connectionString;
-
- //
- // If there is a connection string in the dictionary, just use it.
- //
- if ((connectionString = [_connectionDictionary objectForKey:connectionStringKey]) != nil) {
- return connectionString;
- }
-
- //
- // Get connection information
- //
- dataSource = [_connectionDictionary objectForKey:dataSourceKey];
- userName = [_connectionDictionary objectForKey:userNameKey];
- password = [_connectionDictionary objectForKey:passwordKey];
-
- // construct connection string
- return [NSString stringWithFormat:@"DSN=%@;UID=%@;PWD=%@", dataSource ? dataSource: @"", userName ? userName : @"", password ? password : @""];
- }
-
- - (void *)odbcEnvironment { return odbcEnvironment; }
-
- @end
-
- @implementation ODBCAdaptor (ODBCTypeMapping)
-
- + (NSArray *)externalTypesWithModel:(EOModel *)model
- {
- //
- // Just extract the information from the model file
- //
- return [[ODBCAdaptor typeInfoForModel:model] allKeys];
- }
-
-
- //
- // The following table describe, for every ODBC type, the preferred
- // corresponding Foundation class.
- //
- typedef struct {
- int odbcType;
- NSString *odbcTypeString;
- NSString *internalType;
- NSString *valueType; // For NSNumber, mostly
- } typeStruct;
-
- static typeStruct defaultCorrespondingTypes[] = {
- { SQL_VARCHAR, @"VARCHAR", @"NSString", nil },
- { SQL_CHAR, @"CHAR", @"NSString", nil },
- { SQL_LONGVARCHAR, @"LONG VARCHAR", @"NSString", nil },
- { SQL_DECIMAL, @"DECIMAL", @"NSDecimalNumber", nil },
- { SQL_NUMERIC, @"NUMERIC", @"NSDecimalNumber", nil },
- { SQL_BIGINT, @"BIGINT", @"NSNumber", @"l" }, // long L
- { SQL_SMALLINT, @"SMALLINT", @"NSNumber", @"s" }, // short S
- { SQL_INTEGER, @"INTEGER", @"NSNumber", @"i" }, // integer I
- { SQL_REAL, @"REAL", @"NSNumber", @"f" }, // float
- { SQL_FLOAT, @"FLOAT", @"NSNumber", @"d" }, // double
- { SQL_DOUBLE, @"DOUBLE", @"NSNumber", @"d" }, // double
- { SQL_BIT, @"BIT", @"NSNumber", @"c" }, // char
- { SQL_TINYINT, @"TINYINT", @"NSNumber", @"c" }, // char C
- { SQL_VARBINARY, @"VARBINARY", @"NSData", nil },
- { SQL_BINARY, @"BINARY", @"NSData", nil },
- { SQL_LONGVARBINARY, @"LONG VARBINARY", @"NSData", nil },
- { SQL_TIMESTAMP, @"TIMESTAMP", @"NSCalendarDate", nil },
- { SQL_DATE, @"DATE", @"NSCalendarDate", nil },
- { SQL_TIME, @"TIME", @"NSCalendarDate", nil },
- { SQL_TYPE_NULL, nil, nil}
- };
-
- //
- // Possible optimisation here ! Use two map tables to hash the name to SQL
- // and vice versa.
- //
- + (NSString *)stringRepresentationForOdbcType:(int)type
- {
- unsigned i = 0;
- while(defaultCorrespondingTypes[i].odbcType != SQL_TYPE_NULL) {
- if(defaultCorrespondingTypes[i].odbcType == type) {
- return defaultCorrespondingTypes[i].odbcTypeString;
- }
- i += 1;
- }
-
- return nil;
- }
-
- + (int)odbcTypeForStringRepresentation:(NSString *)type
- {
- unsigned i = 0;
-
- while(defaultCorrespondingTypes[i].odbcType != SQL_TYPE_NULL) {
- if([defaultCorrespondingTypes[i].odbcTypeString isEqualToString:type])
- return defaultCorrespondingTypes[i].odbcType;
- i += 1;
- }
-
- return SQL_TYPE_NULL;
- }
-
- + (NSString *)odbcTypeForExternalType:(NSString *)extType model:(EOModel *)model
-
- {
- NSDictionary *dict = [ODBCAdaptor typeInfoForModel:model];
- NSDictionary *typeInfo;
-
- //
- // Extract the relevant typeInfo from the model
- //
- typeInfo = [dict objectForKey:extType];
- if(!typeInfo) {
- return nil;
- }
-
- //
- // The first item of the list is the prefered one
- //
- return [[typeInfo objectForKey:@"defaultODBCType"] objectAtIndex:0];
- }
-
- + (NSString *)internalTypeForExternalType:(NSString *)extType model:(EOModel *)model
- {
- NSString *odbcType;
- unsigned int i = 0;
-
- odbcType = [self odbcTypeForExternalType:extType model:model];
- //
- // Search for it in my table.
- //
- while(defaultCorrespondingTypes[i].internalType) {
- if([defaultCorrespondingTypes[i].odbcTypeString isEqualToString:odbcType]) {
- return defaultCorrespondingTypes[i].internalType;
- }
- }
-
- return nil;
- }
-
- + (NSString *)externalTypeForOdbcType:(int)type model:(EOModel *)model;
- {
- NSString *odbcType = nil;
- unsigned i = 0, c;
- NSDictionary *typeInfo;
- NSArray *allKeys;
- NSString *best = nil;
- unsigned position = UINT_MAX;
-
- //
- // First, get the string representation for the odbc type
- //
- odbcType = [self stringRepresentationForOdbcType:type];
- if(!odbcType) return nil;
-
- //
- // Get the infoType dictionary from the model
- //
- typeInfo = [ODBCAdaptor typeInfoForModel:model];
- if(!typeInfo) return nil;;
-
- //
- // Look everywhere, and return the best one. Or nil if not found
- //
- allKeys = [typeInfo allKeys];
- for(i = 0, c = [allKeys count]; i < c; i++) {
- NSString *key = [allKeys objectAtIndex:i];
- NSDictionary *currentTypeInfo = [typeInfo objectForKey:key];
- NSArray *defaultODBCType = [currentTypeInfo objectForKey:@"defaultODBCType"];
- unsigned j,k;
-
- k = [defaultODBCType count];
- if(k > position) k = position + 1;
- for(j = 0; j < k ; j++) {
- NSString *type = [defaultODBCType objectAtIndex:j];
- if([type isEqualToString:odbcType]) {
- best = key;
- position = j;
- break;
- }
- }
- // if(position == 0) break;
- }
- return best;
- }
-
- + (void)assignExternalInfoForAttribute:(EOAttribute *)attribute
- {
- int sqlTypeArray[20];
- unsigned i = 0;
- NSString *externalType = nil;
- EOModel *model = [[attribute parent] model];
-
- //
- // Set the column name from the attribute name
- //
- [super assignExternalInfoForAttribute:attribute];
-
- //
- // Set the ODBC types we want to use prioritized.
- //
- switch ([attribute adaptorValueType]) {
- case EOAdaptorNumberType:
- if([attribute scale] != 0) {
- sqlTypeArray[i++] = SQL_DECIMAL;
- sqlTypeArray[i++] = SQL_NUMERIC;
- sqlTypeArray[i++] = SQL_REAL;
- sqlTypeArray[i++] = SQL_FLOAT;
- sqlTypeArray[i++] = SQL_DOUBLE;
- } else {
- // sqlTypeArray[i++] = SQL_BIT; // Useless
- // sqlTypeArray[i++] = SQL_TINYINT;
- // sqlTypeArray[i++] = SQL_SMALLINT;
- sqlTypeArray[i++] = SQL_INTEGER;
- sqlTypeArray[i++] = SQL_BIGINT;
- sqlTypeArray[i++] = SQL_DECIMAL;
- sqlTypeArray[i++] = SQL_NUMERIC;
- sqlTypeArray[i++] = SQL_REAL;
- sqlTypeArray[i++] = SQL_FLOAT;
- sqlTypeArray[i++] = SQL_DOUBLE;
- }
- break;
-
- case EOAdaptorDateType:
- sqlTypeArray[i++] = SQL_TIMESTAMP;
- // If there is no TIMESTAMP stuff, store it in a string
- break;
-
- case EOAdaptorBytesType:
- if([attribute width]) {
- sqlTypeArray[i++] = SQL_BINARY;
- sqlTypeArray[i++] = SQL_VARBINARY;
- sqlTypeArray[i++] = SQL_LONGVARBINARY;
- } else {
- sqlTypeArray[i++] = SQL_LONGVARBINARY;
- sqlTypeArray[i++] = SQL_VARBINARY;
- sqlTypeArray[i++] = SQL_BINARY;
- }
- break;
-
- case EOAdaptorCharactersType:
- if([attribute width]) {
- sqlTypeArray[i++] = SQL_CHAR;
- sqlTypeArray[i++] = SQL_VARCHAR;
- sqlTypeArray[i++] = SQL_LONGVARCHAR;
- } else {
- sqlTypeArray[i++] = SQL_LONGVARCHAR;
- }
-
- break;
- }
- // By default store it in a string. Since a driver must provide at least
- // CHAR and VARCHAR, we are going to find at least one matching type.
- sqlTypeArray[i++] = SQL_VARCHAR;
- sqlTypeArray[i++] = SQL_CHAR;
- sqlTypeArray[i++] = SQL_LONGVARCHAR;
-
- sqlTypeArray[i++] = SQL_TYPE_NULL;
-
- i = 0;
- while(sqlTypeArray[i] != SQL_TYPE_NULL) {
- NSDictionary *currentTypeInfo = nil;
-
- // Search an external type matching the ODBC type
- externalType = [self externalTypeForOdbcType:sqlTypeArray[i] model:model];
-
- currentTypeInfo = [[ODBCAdaptor typeInfoForModel:model] objectForKey:externalType];
-
- if(externalType) {
- // check precision and width and scales...
- // Reject it if it does not fit
- unsigned attPrecision;
- int attScale;
- NSString *typePrecision; // Precision or width
- NSString *typeMaxScale;
- NSString *typeMinScale;
- BOOL found = NO;
-
- typePrecision = [currentTypeInfo objectForKey:@"precision"];
- typeMaxScale = [currentTypeInfo objectForKey:@"maxScale"];
- typeMinScale = [currentTypeInfo objectForKey:@"minScale"];
-
- // If precision is undefined, just accept the type as-is
- if(!typePrecision) {
- found = YES; // Unlikely to happens
- } else {
- switch ([attribute adaptorValueType]) {
- case EOAdaptorNumberType:
- attPrecision = [attribute precision];
- if(attPrecision > [typePrecision intValue]) {
- break; // Go to the next one
- }
- if(!typeMinScale || !typeMaxScale) {
- found = YES;
- break;
- }
- attScale = [attribute scale];
- if(attScale <= [typeMaxScale intValue] &&
- attScale >= [typeMinScale intValue]) {
- found = YES;
- }
- break;
-
- case EOAdaptorCharactersType:
- case EOAdaptorBytesType:
- attPrecision = [attribute width];
- if(attPrecision <= [typePrecision intValue]) {
- found = YES;
- }
- break;
-
- case EOAdaptorDateType:
- found = YES;
- break;
- }
-
- }
-
- if(found) break;
- }
- i++;
- }
-
- [attribute setExternalType:externalType];
- }
-
- + (void)assignExternalInfoForEntireModel:(EOModel *)model
- {
- NS_DURING
- [model setConnectionDictionary:[ODBCAdaptor resetOdbcInfoWithConnectionDictionary:[model connectionDictionary]]];
- [super assignExternalInfoForEntireModel:model];
- NS_HANDLER
- NSLog(@"Unable to convert the model. If you retry it could succeed...\n%@", localException);
- NS_ENDHANDLER
- }
-
- @end
-
- @implementation ODBCAdaptor(_ODBCPrivate)
-
- // In this method there is one thing to do:
- // Check for the unsigned/signed crap.
-
- - (EOAttribute *)attributeWithName:(NSString *)name columnName:(NSString *)columnName externalType:(NSString *)externalType odbcType:(int)odbcType length:(int)length precision:(int)precision scale:(int)scale nullable:(BOOL)nullable
- {
- unsigned i = 0;
- EOAttribute *attribute = [[[EOAttribute alloc] init] autorelease];
- [attribute setName:name];
- [attribute setColumnName:columnName];
-
- [attribute setExternalType:externalType];
-
- while(defaultCorrespondingTypes[i].odbcType != odbcType) {
- i += 1;
- }
-
- [attribute setValueClassName:defaultCorrespondingTypes[i].internalType];
- [attribute setValueType:defaultCorrespondingTypes[i].valueType];
-
- switch([attribute adaptorValueType]) {
- case EOAdaptorNumberType:
- [attribute setPrecision:precision];
- [attribute setScale:scale];
- [attribute setWidth:0];
- break;
- case EOAdaptorDateType:
- [attribute setWidth:0];
- break;
- case EOAdaptorCharactersType:
- case EOAdaptorBytesType:
- [attribute setWidth:length];
- break;
- }
-
- [attribute setAllowsNull:nullable];
- return attribute;
- }
-
- @end
-
- @implementation ODBCAdaptor(ODBCInfoAccessors)
-
- + (NSDictionary *)getOdbcInfoWithConnectionDictionary:(NSDictionary *)connectionDictionary
- {
- NSAutoreleasePool *pool = [NSAutoreleasePool new];
- ODBCAdaptor *adaptor = [[[self alloc] initWithName:@"ODBCAdaptor"] autorelease];
- ODBCContext *context;
- ODBCChannel *channel;
- NSMutableDictionary *result;
-
- [adaptor setConnectionDictionary:connectionDictionary];
- context = (ODBCContext *)[adaptor createAdaptorContext];
- channel = (ODBCChannel *)[context createAdaptorChannel] ;
-
- result = [[connectionDictionary mutableCopy] autorelease];
-
- NS_DURING
- [context odbcConnect];
- [result setObject:[context odbcDriverInfo] forKey:driverInfoKey];
- [result setObject:[channel odbcTypeInfo] forKey:typeInfoKey];
- [context odbcDisconnect];
- NS_HANDLER
- // Setup two empty dictionaries in the result
- if(![result objectForKey:driverInfoKey])
- [result setObject:[NSDictionary dictionary] forKey:driverInfoKey];
-
- if(![result objectForKey:typeInfoKey])
- [result setObject:[NSDictionary dictionary] forKey:typeInfoKey];
-
- NS_ENDHANDLER
-
- //
- // Necessary because dealloc on the context need to access
- // the driver info dictionary.
- // We cannot use setConnectionDictionnary here, because it's raising
- // if a connection is open...
- //
- [adaptor->_connectionDictionary autorelease];
- adaptor->_connectionDictionary = [result retain];
-
- [result retain];
-
- [pool release]; // the adaptor/context/channel should all go away here.
-
- return [result autorelease];
- }
-
- + (NSDictionary *)resetOdbcInfoWithConnectionDictionary:(NSDictionary *)connectionDictionary
- {
- if([connectionDictionary objectForKey:driverInfoKey] || [connectionDictionary objectForKey:typeInfoKey]) {
- NSMutableDictionary *newDict = [[connectionDictionary mutableCopy] autorelease];
-
- [newDict removeObjectForKey:driverInfoKey];
- [newDict removeObjectForKey:typeInfoKey];
-
- return newDict;
- } else {
- return connectionDictionary;
- }
- }
-
- + (NSDictionary *)typeInfoForModel:(EOModel *)model
- {
- NSDictionary *typeInfo;
-
- typeInfo = [[model connectionDictionary] objectForKey:typeInfoKey];
-
- if(!typeInfo) {
- [model setConnectionDictionary:[ODBCAdaptor getOdbcInfoWithConnectionDictionary:[model connectionDictionary]]];
- typeInfo = [[model connectionDictionary] objectForKey:typeInfoKey];
- }
- return typeInfo;
- }
-
- + (NSDictionary *)driverInfoForModel:(EOModel *)model
- {
- NSDictionary *driverInfo;
-
- driverInfo = [[model connectionDictionary] objectForKey:driverInfoKey];
-
- if(!driverInfo) {
- [model setConnectionDictionary:[ODBCAdaptor getOdbcInfoWithConnectionDictionary:[model connectionDictionary]]];
- driverInfo = [[model connectionDictionary] objectForKey:driverInfoKey];
- }
- return driverInfo;
- }
-
- - (NSDictionary *)typeInfo
- {
- NSDictionary *typeInfo = [_connectionDictionary objectForKey:typeInfoKey];
-
- if(!typeInfo) {
- // I cannot use the accessor -setConnectionDictionary: because
- // there is a raise if a channel is open...
- //
- _connectionDictionary = [[ODBCAdaptor getOdbcInfoWithConnectionDictionary:[_connectionDictionary autorelease]] retain];
- typeInfo = [_connectionDictionary objectForKey:typeInfoKey];
- }
- return typeInfo;
- }
-
- - (NSDictionary *)driverInfo
- {
- NSDictionary *driverInfo = [_connectionDictionary objectForKey:driverInfoKey];
-
- if(!driverInfo) {
- // I cannot use the accessor -setConnectionDictionary: because
- // there is a raise if a channel is open...
- //
- _connectionDictionary = [[ODBCAdaptor getOdbcInfoWithConnectionDictionary:[_connectionDictionary autorelease]] retain];
- driverInfo = [_connectionDictionary objectForKey:driverInfoKey];
- }
- return driverInfo;
- }
-
- @end
-