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

  1. /*
  2.    ODBCContext.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. @implementation ODBCContext(_ODBCErrorNotification)
  22.  
  23. - (void)odbcErrorWithChannel:(ODBCChannel *)channel string:(NSString *)location raise:(BOOL)flag
  24. {
  25.     HENV henv   = [(ODBCAdaptor *)_adaptor odbcEnvironment];
  26.     HDBC hdbc   = _odbcDatabaseConnection;
  27.     HSTMT hstmt = (channel ? [channel odbcStatement]: SQL_NULL_HSTMT);
  28.     UCHAR sqlState[SQL_SQLSTATE_SIZE+1];
  29.     SDWORD nativeError;    
  30.     UCHAR errorMsg[SQL_MAX_MESSAGE_LENGTH + 1];
  31.     SWORD errorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
  32.     SWORD msgLength;
  33.     RETCODE retCode;
  34.     NSMutableString *returnString = [NSMutableString string];
  35.  
  36.     [returnString appendString:location];
  37.     
  38.     if(!henv) henv = SQL_NULL_HENV;
  39.     if(!hdbc) hdbc = SQL_NULL_HDBC;
  40.  
  41.     while(1) { // Break or return will exit the loop
  42.         retCode = SQLError(henv, hdbc, hstmt, sqlState, &nativeError, errorMsg, errorMsgMax, &msgLength);
  43.  
  44.         if(retCode == SQL_NO_DATA_FOUND) {
  45.             // No more errors.
  46.             break;
  47.         } else if (retCode == SQL_ERROR) {
  48.             [returnString appendString:@" - Unable to access the ODBC error description"];
  49.             break;
  50.         } else if (retCode == SQL_INVALID_HANDLE) {
  51.             [returnString appendString:@" - General ODBC adaptor Failure"];
  52.             break;
  53.         } else if( (retCode == SQL_SUCCESS) || (retCode == SQL_SUCCESS_WITH_INFO) ) {
  54.             [returnString appendFormat:@"\n%s-%d: %s", sqlState, nativeError, errorMsg];
  55.         }
  56.     }
  57.  
  58.     if(flag) {
  59.         // We raise here
  60.         [NSException raise:EOGeneralAdaptorException format:@"%@", returnString];
  61.     }
  62.  
  63.     if(_debug) {
  64.         NSLog(@"ODBC INFO: %@", returnString);
  65.     }
  66. }
  67.  
  68. @end
  69.  
  70. @implementation ODBCContext
  71.  
  72. - initWithAdaptor:(EOAdaptor *)anAdaptor
  73. {
  74.     [super initWithAdaptor:anAdaptor];
  75.     _odbcDatabaseConnection = NULL;
  76.     _openChannelCount = 0;
  77.     _isConnected = NO;
  78.     _fetchesInProgress = 0;
  79.     return self;
  80. }
  81.  
  82. - (BOOL)_driverCrashOnFreeConnect
  83. {
  84.     NSDictionary *driverInfo = [(ODBCAdaptor *)_adaptor driverInfo];
  85.     NSString *driverName = [driverInfo objectForKey:@"DRIVER_NAME"];
  86.     BOOL isBogus = NO;
  87.  
  88.     if([driverName isEqual:@"odbcjt32.dll"]) {
  89.         // I did not check all the versions, but the version
  90.         // 3.40.2829 of this dll is not working.
  91.         // In doubt, let say that they are all bogus.
  92.         //
  93.         isBogus = YES;
  94.     }
  95.     
  96.     if(_debug && isBogus) NSLog(@"Warning: Leaking the database connection due to a bug in the ODBC driver.");
  97.  
  98.     return isBogus;
  99. }
  100.  
  101. - (void)dealloc
  102. {
  103.     RETCODE result;
  104.  
  105.     if(_isConnected) [self odbcDisconnect];
  106.  
  107.     //
  108.     // The Microsoft Jet engine is crashing on SQLFreeConnect().
  109.     // This engine is used by the following ODBC drivers: Access, FoxPro, dBase, text, Excel and Paradox...
  110.     // If we are using one of these drivers, we just leak the connection.
  111.     //
  112.     if([self _driverCrashOnFreeConnect]) {
  113.         result = SQL_SUCCESS;
  114.     } else {
  115.         result = SQLFreeConnect(_odbcDatabaseConnection);
  116.     }
  117.     
  118.     _odbcDatabaseConnection = NULL;
  119.  
  120.     if ( result != SQL_SUCCESS ) {
  121.         [self odbcErrorWithChannel:nil string:@"SQLFreeConnect in -[ODBCContext odbcDisconnect]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  122.     }
  123.  
  124.     [super dealloc];
  125. }
  126.  
  127. - (void)odbcConnect
  128. {
  129.     RETCODE result;
  130.     HENV odbcEnvironmentHandle = [(ODBCAdaptor *)_adaptor odbcEnvironment];
  131.     UCHAR *connectionString;
  132.     UCHAR connectionStringOut[1024];
  133.     SWORD connectionStringOutLength;
  134.  
  135.     //
  136.     // If already connected then return
  137.     //
  138.     if (_isConnected) return;
  139.  
  140.     if (_delegateRespondsTo.shouldConnect) {
  141.         if (![_delegate adaptorContextShouldConnect:self]) {
  142.             return;
  143.         }
  144.     }
  145.     
  146.     //
  147.     // Do a simple check on the connection dictionnary
  148.     //
  149.     if (![_adaptor connectionDictionary])
  150.         [NSException raise:EOGeneralAdaptorException format:@"Unable to connect to server"];
  151.  
  152.     //
  153.     // Allocate the connnection if it's the first connect call
  154.     //
  155.     if(!_odbcDatabaseConnection) {
  156.         result = SQLAllocConnect (odbcEnvironmentHandle, &_odbcDatabaseConnection);
  157.  
  158.         if ( result != SQL_SUCCESS ) {
  159.             [self odbcErrorWithChannel:nil string:@"SQLAllocConnect in -[ODBCContext odbcConnect]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  160.         }
  161.     }
  162.  
  163.     //
  164.     // Connect to the driver. Note: SQLDriverConnect is not supported by all the drivers...
  165.     //
  166.     connectionString = (UCHAR *)[[(ODBCAdaptor *)_adaptor odbcConnectionString] cString];
  167.  
  168.     if(_debug) NSLog(@"connect with: %s", connectionString);
  169.     
  170.     result = SQLDriverConnect (_odbcDatabaseConnection, NULL, connectionString, strlen(connectionString), connectionStringOut, sizeof(connectionStringOut), &connectionStringOutLength, SQL_DRIVER_NOPROMPT);
  171.  
  172.     if ( result != SQL_SUCCESS ) {
  173.             [self odbcErrorWithChannel:nil string:@"SQLDriverConnect in -[ODBCContext odbcConnect]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  174.     }
  175.     _isConnected = YES;
  176.  
  177. }
  178.  
  179. - (void)odbcDisconnect
  180. {
  181.     RETCODE result;
  182.  
  183.     //
  184.     // If already disconected then return
  185.     //
  186.     if (!_isConnected) return;
  187.  
  188.     //
  189.     // Check the current transaction count
  190.     //
  191.     if (_transactionNestingLevel){
  192.         [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: attempt to disconnect while a transaction was in progress", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
  193.     }
  194.  
  195.     //
  196.     // Disconnect from the datasource and free the handle.
  197.     // NOTE: Some drivers don't support SQLDisconnect...
  198.     //
  199.     if(_debug) NSLog(@"disconnect");
  200.  
  201.  
  202.     result = SQLDisconnect(_odbcDatabaseConnection);
  203.  
  204.     if ( result != SQL_SUCCESS ) {
  205.         [self odbcErrorWithChannel:nil string:@"SQLDisconnect in -[ODBCContext odbcDisconnect]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  206.     }
  207.     _isConnected = NO;
  208.     
  209. }
  210.  
  211. - (void)beginTransaction
  212. {
  213.     //
  214.     // If not connected then raise exception
  215.     //
  216.     if (!_isConnected) {
  217.         [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: attempted to begin a transaction without connecting", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
  218.     }
  219.  
  220.     //
  221.     // If within a transaction then raise exception
  222.     //
  223.     if (_transactionNestingLevel) {
  224.         [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: attempted to begin a transaction within a transaction", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
  225.     }
  226.  
  227.     //
  228.     // Notify the delegate with should
  229.     //
  230.     if (_delegateRespondsTo.shouldBegin) {
  231.         if(![_delegate adaptorContextShouldBegin:self])
  232.             return;
  233.     }
  234.  
  235.     //
  236.     // This is a little bit of a lie, since it does nothing.  In ODBC, the
  237.     // transaction begins at login time, or after a commit or rollback.
  238.     //
  239.     [self transactionDidBegin];
  240.  
  241.     //
  242.     // Notify the delegate with did
  243.     //
  244.     if (_delegateRespondsTo.didBegin)
  245.         [_delegate adaptorContextDidBegin:self];
  246. }
  247.  
  248. - (void)commitTransaction
  249. {
  250.     RETCODE result;
  251.  
  252.     //
  253.     // If not within a traction then raise execption
  254.     //
  255.     if (!_transactionNestingLevel) {
  256.         [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: attempted to commit a transaction without beginning one", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
  257.     }
  258.  
  259.     //
  260.     // If already fetching then raise exception
  261.     //
  262.     if (_fetchesInProgress) {
  263.         [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: attempted to commit a transaction while a fetch was in progress", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
  264.     }
  265.  
  266.     //
  267.     // Notify the delegate
  268.     //
  269.     if (_delegateRespondsTo.shouldCommit) {
  270.         if(![_delegate adaptorContextShouldCommit:self])
  271.             return;
  272.     }
  273.  
  274.     //
  275.     // Commit the transaction
  276.     //
  277.     result = SQLTransact(SQL_NULL_HENV, _odbcDatabaseConnection, SQL_COMMIT);
  278.  
  279.     if ( result != SQL_SUCCESS ) {
  280.         [self odbcErrorWithChannel:nil string:@"SQLTransact in -[ODBCContext commitTransaction]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  281.     }
  282.  
  283.     [self transactionDidCommit];
  284.  
  285.     //
  286.     // Notify the delegate
  287.     //
  288.     if (_delegateRespondsTo.didCommit)
  289.         [_delegate adaptorContextDidCommit:self];
  290. }
  291.  
  292. - (void)rollbackTransaction
  293. {
  294.     RETCODE result;
  295.  
  296.     //
  297.     // If not within transaction then raise exception
  298.     //
  299.     if (!_transactionNestingLevel) {
  300.         [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: attempted to rollback a transaction without beginning one", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
  301.     }
  302.  
  303.     //
  304.     // If already fetching the raise exception
  305.     //
  306.     if (_fetchesInProgress) {
  307.         [NSException raise:NSInternalInconsistencyException format:@"%@ -- %@ 0x%x: attempted to rollback a transaction while a fetch was in progress", NSStringFromSelector(_cmd), NSStringFromClass([self class]), self];
  308.     }
  309.  
  310.     //
  311.     // Notify the delegate
  312.     //
  313.     if (_delegateRespondsTo.shouldRollback) {
  314.         if(![_delegate adaptorContextShouldRollback:self])
  315.             return;
  316.     }
  317.  
  318.     result = SQLTransact (SQL_NULL_HENV, _odbcDatabaseConnection, SQL_ROLLBACK);
  319.  
  320.     if ( result != SQL_SUCCESS ) {
  321.         [self odbcErrorWithChannel:nil string:@"SQLTransact in -[ODBCContext rollbackTransaction]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  322.     }
  323.  
  324.     [self transactionDidRollback];
  325.  
  326.     //
  327.     // Notify the delegate
  328.     //
  329.     if (_delegateRespondsTo.didRollback)
  330.         [_delegate adaptorContextDidRollback:self];
  331. }
  332.  
  333. - (BOOL)canNestTransactions
  334. {
  335.     return NO;
  336. }
  337.  
  338. - (unsigned)transactionNestingLevel
  339. {
  340.     return _transactionNestingLevel;
  341. }
  342.  
  343. - (EOAdaptorChannel *)createAdaptorChannel
  344. {
  345.     return [[[ODBCChannel allocWithZone:[self zone]] initWithAdaptorContext:self] autorelease];
  346. }
  347.  
  348. - (void *)odbcDatabaseConnection
  349. {
  350.     return _odbcDatabaseConnection;
  351. }
  352.  
  353. - (void)setOdbcDatabaseConnection:(void *)odbcDatabaseConnection
  354. {
  355.     _odbcDatabaseConnection = odbcDatabaseConnection;
  356.     _isConnected = YES;
  357. }
  358.  
  359. - (void)channelWillOpen
  360. {
  361.     if(!_openChannelCount) {
  362.         [self odbcConnect];
  363.     }
  364.     _openChannelCount += 1;
  365. }
  366.  
  367. - (void)channelDidClose
  368. {
  369.     if(_openChannelCount) _openChannelCount -= 1;
  370.     if(!_openChannelCount) {
  371.         [self odbcDisconnect];
  372.     }
  373. }
  374.  
  375. - (void)channelWillBeginFetching
  376. {
  377.     _fetchesInProgress++;
  378. }
  379.  
  380. - (void)channelDidEndFetching
  381. {
  382.     _fetchesInProgress--;
  383.  
  384. }
  385.  
  386. - (NSString *)_getStringInfoForKey:(unsigned int)key
  387. {
  388.     RETCODE result;
  389.     char charValue[1024];
  390.     short lengthReturned;
  391.  
  392.     result = SQLGetInfo(_odbcDatabaseConnection, key , charValue, sizeof(charValue), &lengthReturned);
  393.  
  394.     if ( result != SQL_SUCCESS ) {
  395.         [(ODBCContext *)self odbcErrorWithChannel:nil string:@"SQLGetInfo in -[ODBCContext odbcGetInfo]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  396.     }
  397.  
  398.     return [NSString stringWithCString:charValue];
  399. }
  400.  
  401. - (unsigned short)_getShortInfoForKey:(unsigned int)key
  402. {
  403.     RETCODE result;
  404.     unsigned short shortValue;
  405.  
  406.     result = SQLGetInfo(_odbcDatabaseConnection, key , &shortValue, sizeof(shortValue), NULL);
  407.  
  408.     if ( result != SQL_SUCCESS ) {
  409.         [(ODBCContext *)self odbcErrorWithChannel:nil string:@"SQLGetInfo in -[ODBCContext odbcGetInfo]" raise: (result != SQL_SUCCESS_WITH_INFO)];
  410.     }
  411.     return shortValue;
  412. }
  413.  
  414. - (NSDictionary *)odbcDriverInfo
  415. {
  416.     NSMutableDictionary *driverInfo = [NSMutableDictionary new];
  417.     BOOL automaticConnection = NO;
  418.  
  419.     if (_debug) NSLog (@" *** [%@ odbcDriverInfo]\n", self);
  420.  
  421.     if(!_isConnected) {
  422.         automaticConnection = YES;
  423.         [self odbcConnect];
  424.     }
  425.  
  426.     NS_DURING
  427.         // Database name and version
  428.         [driverInfo setObject:[self _getStringInfoForKey:SQL_DBMS_NAME] forKey:@"DBMS_NAME"];
  429.     [driverInfo setObject:[self _getStringInfoForKey:SQL_DBMS_VER] forKey:@"DBMS_VER"];
  430.  
  431.     // ODBC Driver name and version
  432.     [driverInfo setObject:[self _getStringInfoForKey:SQL_DRIVER_NAME] forKey:@"DRIVER_NAME"];
  433.     [driverInfo setObject:[self _getStringInfoForKey:SQL_DRIVER_VER] forKey:@"DRIVER_VER"];
  434.  
  435.     // Version of ODBC supported by the driver
  436.     [driverInfo setObject:[self _getStringInfoForKey:SQL_DRIVER_ODBC_VER] forKey:@"DRIVER_ODBC_VER"];
  437.  
  438.     // API Conformance level
  439.     switch([self _getShortInfoForKey:SQL_ODBC_API_CONFORMANCE]) {
  440.         case SQL_OAC_NONE:
  441.             [driverInfo setObject:@"NONE" forKey:@"ODBC_API_CONFORMANCE"];
  442.             break;
  443.  
  444.         case SQL_OAC_LEVEL1:
  445.             [driverInfo setObject:@"LEVEL1" forKey:@"ODBC_API_CONFORMANCE"];
  446.             break;
  447.  
  448.         case SQL_OAC_LEVEL2:
  449.             [driverInfo setObject:@"LEVEL2" forKey:@"ODBC_API_CONFORMANCE"];
  450.             break;
  451.     }
  452.  
  453.     // SQL Conformance level
  454.     switch([self _getShortInfoForKey:SQL_ODBC_SQL_CONFORMANCE]) {
  455.         case SQL_OAC_NONE:
  456.             [driverInfo setObject:@"MINIMUM" forKey:@"ODBC_SQL_CONFORMANCE"];
  457.             break;
  458.  
  459.         case SQL_OAC_LEVEL1:
  460.             [driverInfo setObject:@"CORE" forKey:@"ODBC_SQL_CONFORMANCE"];
  461.             break;
  462.  
  463.         case SQL_OAC_LEVEL2:
  464.             [driverInfo setObject:@"EXTENDED" forKey:@"ODBC_SQL_CONFORMANCE"];
  465.             break;
  466.     }
  467.             
  468.     
  469.     // Support for NOT NULL in CREATE TABLE statement.
  470.     if([self _getShortInfoForKey:SQL_NON_NULLABLE_COLUMNS] == SQL_NNC_NON_NULL) {
  471.         [driverInfo setObject:@"Y" forKey:@"NON_NULLABLE_COLUMNS"];
  472.     }
  473.  
  474.     NS_HANDLER
  475.         if(automaticConnection) {
  476.             [self odbcDisconnect];
  477.         }
  478.         [localException raise];
  479.     NS_ENDHANDLER
  480.     
  481.     if(automaticConnection) {
  482.         [self odbcDisconnect];
  483.     }
  484.  
  485.     return driverInfo;
  486. }
  487.  
  488.  
  489. @end
  490.