2005/5/9

     
 

ODBCConnection.cpp

artefaktur
// -*- mode:C++; tab-width:2; c-basic-offset:2; indent-tabs-mode:nil -*- 
//
// Copyright (C) 2000-2005 by Roger Rene Kommer / artefaktur, Kassel, Germany.
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public License (LGPL).
// 
// 
// 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 
// License ACDK-FreeLicense document enclosed in the distribution
// for more for more details.
// This file is part of the Artefaktur Component Development Kit:
//                         ACDK
// 
// Please refer to
// - http://www.acdk.de
// - http://www.artefaktur.com
// - http://acdk.sourceforge.net
// for more information.
// 
// $Header: /cvsroot/acdk/acdk/acdk_sql_odbc/src/acdk/sql/odbc/ODBCConnection.cpp,v 1.6 2005/03/08 18:55:37 kommer Exp $
#include "ODBCConnection.h"
#include "ODBCDriver.h"
#include "ODBCResultSetMetaData.h"
#include "ODBCPreparedStatement.h"
#include "ODBCCallableStatement.h"

#include <acdk/util/Properties.h>
#include <acdk/lang/Integer.h>
#include <acdk/lang/Boolean.h>
#include <acdk/lang/NumberFormatException.h>
#include <acdk/lang/System.h>
#include <acdk/util/logging/Log.h>

namespace acdk {
namespace sql {
namespace odbc {

ODBCConnection::ODBCConnection(INP(RODBCDriver) driver) 
: _driver(driver)
, _dbch(Nil)
, _prop(Nil)
, _url(Nil)
, _loginTimeout(0)
, _connectionTimeout(0)
, _queryTimeout(0)
, _opened(false)
, _readOnly(false)
, _autoCommit(true)
, _asyncCalls(false)
, _traceCalls(false)
, _maxRows(-1)
, _maxLength(-1)
, _scanEscapes(true)
, _scrollableCursors(false) 
{ 
}

ODBCConnection::~ODBCConnection() 
{ 
  ACDK_NLOG("acdk.sql.odbc", Debug, "ODBCConnection::~ODBCConnection");
  close(); 
}

RDatabaseMetaData 
ODBCConnection::getMetaData()
{
  return new ODBCDatabaseMetaData(this); 
}

// same problem as with ODBCDriver: the interface Connection doesn't have a method to open a connection,
// but the open of a DB may fail and we shouldn't throw an exception within the constructor. the alternative 
// is an init-function, which has to be called immediately after the creation of the new object
 
RODBCConnection
ODBCConnection::init(INP(RString) url, INP(acdk::util::RProperties) prop)
{
  RString dsn, uid, pwd;
  int stop, equl, strt = 10;

  // a jdbc-url for sqlodbc looks like: "jdbc:odbc:datasource", where datasource may contain additional host/port-
  // info or attributes: [//host[:port]/]databasename[/attrib[/...]]
  // currently I've no idea, how to connect to an outside odbc-driver, which hasn't an alias on current host, the
  // attribs will be checked for user=... and password=..., which will be deleted but used internally, any other
  // slash will be turned into a ';' and then given to the underlaying odbc-drivermanager.
  // Note: we don't use net::parseURL(), because we have to these additional tasks on the url. 
  
  if (_dbch != Nil)
    return this;
  if (_driver->acceptsURL(url) == false)
    return Nil;
  _dbch = new ODBCHandle(SQL_HANDLE_DBC);
    
  _dbch->init(_driver->_getODBCHandle());

  
  if (url->length() == strt) {
    // empty datasource, use ODBC default source
    dsn = new String("DEFAULT");
  } else {
    if (url->startsWith("//", strt) == true)
      strt += 2;
    if ((stop = url->indexOf('/', strt)) == -1)
      dsn = new String(url->substring(strt));
    else {
      RStringBuffer sb = new StringBuffer;
      sb->append(url->substring(strt, stop));
      do {
        strt = stop + 1;
        if (url->length() <= strt) {
          break;
        }
        if ((stop = url->indexOf('/', strt)) == -1)
          stop = url->length();
        equl = url->indexOf('=', strt);
        if ((equl >= 0) && (equl < stop)) {
          RString key = url->substring(strt, equl);
          if (key->equalsIgnoreCase("user") == true) {
            uid = new String(url->substring(equl + 1, stop));
          } else if (key->equalsIgnoreCase("password") == true) {
            pwd = new String(url->substring(equl + 1, stop));
          } else {
            sb->append(';');
            sb->append(url->substring(strt, stop));
          }
        } else {
          sb->append(';');
          sb->append(url->substring(strt, stop));
        }
      } while (stop < url->length());
      dsn = new String(sb);
    }
  }
  if (uid == Nil)
    uid = new String();
  if (pwd == Nil)
    pwd = new String();
#if defined(ACDK_ODBC_DEBUG)
  ::acdk::lang::System::err->println(RString("INFO: parsed url \"") + url + "\" into DSN=\"" + dsn + "\", UID=\"" + uid + "\" PWD=\"" + pwd + "\".");
#endif
/FONT>
#if !defined(ACDK_MINI)
  RString ndsn = dsn->convertToNative();
  RString nuid = uid->convertToNative();
  RString npwd = pwd->convertToNative();
#else
/FONT>
  RString ndsn = dsn;
  RString nuid = uid;
  RString npwd = pwd;
#endif
/FONT>
  callSQL6(_dbch, SQLConnect, (ODBC_NATIVE_CHAR*)ndsn->native_c_str(), ndsn->length(), 
                              (ODBC_NATIVE_CHAR*)nuid->native_c_str(), nuid->length(), 
                              (ODBC_NATIVE_CHAR*)npwd->native_c_str(), npwd->length());
  _url = url;
  _prop = prop;

  /* maybe these will fail */
  _parseProperties(prop);
  //_setSQLFlags(); // this WILL fail (at least on mysql)
  return this;
}

#define _parseOneProp(__name, __var, __method, __exception, __defval) \
  key = new String(__name); \
  res = prop->getProperty(key); \
  if (res != Nil) { \
    try { \
      __var = __method(res); \
    } catch (__exception e) { \
      /* the caller might not catch ???Exception, but SQLException. */ \
      RString _errmsg_ = RString("invalid Property-value for \"") \
       + __name + "\""; \
      THROW1(SQLException, _errmsg_); \
    } \
  } else { \
    __var = __defval; \
  }

#define _unsupportedProp(__name) \
  key = new String(__name); \
  res = prop->getProperty(key); \
  if (res != Nil) { \
    RString _errmsg_ = RString("unsupported Property-value \"") \
     + __name + "\" for ODBC-version " + ::acdk::lang::Integer::toString(ODBCVER, 16); \
    THROW1(SQLException, _errmsg_); \
  }

  // currently statement-relevant key/values are: (statement-interface doesn't have own properties, here we set default values)
  //
  // SQL_ATTR_CONCURRENCY: SQLUINTEGER: SQL_CONCUR_READ_ONLY (default), SQL_CONCUR_LOCK, SQL_CONCUR_ROWVER, SQL_CONCUR_VALUES 
  // SQL_ATTR_CURSOR_SCROLLABLE: SQLUINTEGER: SQL_NONSCROLLABLE (default), SQL_SCROLLABLE 
  // SQL_ATTR_CURSOR_SENSITIVITY: SQLUINTEGER: SQL_UNSPECIFIED (default), SQL_INSENSITIVE, SQL_SENSITIVE
  // SQL_ATTR_CURSOR_TYPE: SQLUINTEGER: SQL_CURSOR_FORWARD_ONLY (default), SQL_CURSOR_STATIC, SQL_CURSOR_KEYSET_DRIVEN, SQL_CURSOR_DYNAMIC
  // SQL_ATTR_MAX_LENGTH: SQLUINTEGER: 0 (default)
  // SQL_ATTR_MAX_ROWS: SQLUINTEGER: 0 (default)
  // SQL_ATTR_NOSCAN: SQLUINTEGER: SQL_NOSCAN_OFF (default), SQL_NOSCAN_ON
  // SQL_ATTR_QUERY_TIMEOUT: SQLUINTEGER: 0 (default)
void
ODBCConnection::_parseProperties(INP(acdk::util::RProperties) prop)
{
  RString key, res;
  
  if (prop == Nil)
    return;

/* choose your part: macro-calls or many code... */

  _parseOneProp("LoginTimeout", _loginTimeout, ::acdk::lang::Integer::decode, NumberFormatException, 0);
#if ODBCVER >= 0x0300
  _parseOneProp("ConnectionTimeout", _connectionTimeout, ::acdk::lang::Integer::decode, NumberFormatException, 0);
#else
/FONT>
  _unsupportedProp("ConnectionTimeout");
#endif
/FONT>
  _parseOneProp("ReadOnly", _readOnly, ::acdk::lang::Boolean::getBoolean, Exception, false);
  _parseOneProp("AutoCommit", _autoCommit, ::acdk::lang::Boolean::getBoolean, Exception, true);
#if ODBCVER >= 0x0300
  _parseOneProp("AsyncCalls", _asyncCalls, ::acdk::lang::Boolean::getBoolean, Exception, false);
#else
/FONT>
  _unsupportedProp("AsyncCalls");
#endif
/FONT>
  _parseOneProp("TraceCalls", _traceCalls, ::acdk::lang::Boolean::getBoolean, Exception, false);

  _parseOneProp("QueryTimeout", _queryTimeout, ::acdk::lang::Integer::decode, NumberFormatException, 0);
  _parseOneProp("MaxLength", _maxLength, ::acdk::lang::Integer::decode, NumberFormatException, 0);
  _parseOneProp("MaxRows", _maxRows, ::acdk::lang::Integer::decode, NumberFormatException, 0);
  _parseOneProp("ScanEscapes", _scanEscapes, ::acdk::lang::Boolean::getBoolean, Exception, true);
  _parseOneProp("ScrollableCursors", _scrollableCursors, ::acdk::lang::Boolean::getBoolean, Exception, false);

}

void
ODBCConnection::_setSQLFlags()
{
  if (_dbch == Nil)
    return;
    
  // currently connection-relevant key/values are:
  //
  //  SQL_ATTR_ACCESS_MODE:        SQLUINTEGER: SQL_MODE_READ_ONLY, SQL_MODE_READ_WRITE (default)
  //  SQL_ATTR_ASYNC_ENABLE:       SQLUINTEGER: SQL_ASYNC_ENABLE_OFF (default), SQL_ASYNC_ENABLE_ON
  //  SQL_ATTR_AUTOCOMMIT:         SQLUINTEGER: SQL_AUTOCOMMIT_OFF, SQL_AUTOCOMMIT_ON (default)
  //  SQL_ATTR_CONNECTION_TIMEOUT: SQLUINTEGER: 0 (default) 
  //  SQL_ATTR_LOGIN_TIMEOUT:      SQLUINTEGER: 0 (default)
  //  SQL_ATTR_TRACE:              SQLUINTEGER: SQL_OPT_TRACE_OFF (default), SQL_OPT_TRACE_ON
  // maybe we shouldn't set those values, which are default, or we might get unneeded errors
  _dbch->_setSQLFlag(SQL_ATTR_ACCESS_MODE, (_readOnly == true)? SQL_MODE_READ_ONLY : SQL_MODE_READ_WRITE);
#if ODBCVER >= 0x0300
  _dbch->_setSQLFlag(SQL_ATTR_ASYNC_ENABLE, (_asyncCalls == false)? SQL_ASYNC_ENABLE_OFF : SQL_ASYNC_ENABLE_ON);
#endif // ODBCVER
  _dbch->_setSQLFlag(SQL_ATTR_AUTOCOMMIT, (_autoCommit == false)? SQL_AUTOCOMMIT_OFF : SQL_AUTOCOMMIT_ON);
#if ODBCVER >= 0x0300
  _dbch->_setSQLFlag(SQL_ATTR_CONNECTION_TIMEOUT, _connectionTimeout);
#endif // ODBCVER
  _dbch->_setSQLFlag(SQL_ATTR_LOGIN_TIMEOUT, _loginTimeout);
  _dbch->_setSQLFlag(SQL_ATTR_TRACE, (_traceCalls == false)? SQL_OPT_TRACE_OFF : SQL_OPT_TRACE_ON);
}

void
ODBCConnection::_setSQLFlag(SQLUINTEGER key, SQLUINTEGER val)
{
  if (_dbch == Nil)
    return; // maybe we should throw an exception.
  _dbch->_setSQLFlag(key, val);
}

void 
ODBCConnection::commit() 
{ 
  if (_autoCommit == true) 
    return; 
  THROW0(UnsupportedOperationException); 
  _dbch->_chkSQLrcode(SQLEndTran(_dbch->_getSQLHType(), _dbch->_getSQLHandle(), SQL_COMMIT), "SQLEndTran", __FILE__, __LINE__);
  //callSQL1(_dbch, SQLEndTran, SQL_COMMIT);
}

RStatement
ODBCConnection::createStatement()
{
  RODBCStatement stmt = new ODBCStatement(this);
  
  stmt->init(_prop);

#if 0
  /* maybe we shouldn't set those values, which are default, or we might get unneeded errors */
  stmt->setEscapeProcessing(_scanEscapes);
#if ODBCVER >= 0x0300
  stmt->setScrollableCursor(_scrollableCursors);
#endif // ODBCVER
  stmt->setQueryTimeout(_queryTimeout);
  stmt->setMaxFieldSize(_maxLength);
  stmt->setMaxRows(_maxRows);
#endif // 0
  return &stmt;
}

RPreparedStatement 
ODBCConnection::prepareStatement(INP(RString) sql)
{
  RODBCPreparedStatement stmt =  new ODBCPreparedStatement(this, sql);
  stmt->init(_prop);
#if 0

#if ODBCVER >= 0x0300
  stmt->setScrollableCursor(_scrollableCursors);
#endif // ODBCVER
  stmt->setQueryTimeout(_queryTimeout);
  stmt->setMaxFieldSize(_maxLength);
  stmt->setMaxRows(_maxRows);
#endif  
  return &stmt;
}

RCallableStatement 
ODBCConnection::prepareCall(INP(RString) sql)
{
  RODBCCallableStatement stmt = new ODBCCallableStatement(this, sql);
  stmt->init(_prop);
  return &stmt;
}

} // odbc
} // sql
} // acdk