Revision: 1.4, Wed Jul 23 18:52:38 2003 UTC (23 months, 1 week ago) by elizthom
Branch: MAIN
CVS Tags: nsodbc_v2, HEAD
Changes since 1.3: +2 -1 lines
make compatible with aolserver 4.0+
/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/nsodbc/nsodbc.c,v 1.4 2003/07/23 18:52:38 elizthom Exp $, compiled: " __DATE__ " " __TIME__;

#include "ns.h"
#include "nsdb.h"

#ifndef REDBRICK
#include <sql.h>
#include <sqlext.h>
#else
#include <rbsql.h>
#include <rbsqlext.h>
#endif

#define RC_OK(rc) (!((rc)>>1))
#define MAX_ERROR_MSG 500
#define MAX_IDENTIFIER 256

static char    *ODBCName(void);
static int      ODBCServerInit(char *server, char *hModule, char *driver);
static int      ODBCOpenDb(Ns_DbHandle *handle);
static int      ODBCCloseDb(Ns_DbHandle *handle); 
static int      ODBCGetRow(Ns_DbHandle *handle, Ns_Set *row);
static int      ODBCCancel(Ns_DbHandle *handle);
static int      ODBCExec(Ns_DbHandle *handle, char *sql); 
static Ns_Set  *ODBCBindRow(Ns_DbHandle *handle); 
static int	ODBCFreeStmt(Ns_DbHandle *handle);
static void     ODBCLog(RETCODE rc, Ns_DbHandle *handle);
static char    *odbcName = "ODBC";
static HENV     odbcenv; 
static Tcl_CmdProc ODBCCmd; 

static Ns_DbProc odbcProcs[] = {
    {DbFn_Name, ODBCName},
    {DbFn_ServerInit, ODBCServerInit},
    {DbFn_OpenDb, ODBCOpenDb},
    {DbFn_CloseDb, ODBCCloseDb},
    {DbFn_GetRow, ODBCGetRow}, 
    {DbFn_Flush, ODBCCancel},
    {DbFn_Cancel, ODBCCancel},
    {DbFn_Exec, ODBCExec}, 
    {DbFn_BindRow, ODBCBindRow}, 
    {0, NULL}
};


/*
 *----------------------------------------------------------------------
 *
 * Ns_DbDriverInit -
 *
 *	ODBC module load routine.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	Initializes ODBC.
 *
 *----------------------------------------------------------------------
 */

NS_EXPORT int   Ns_ModuleVersion = 1;

NS_EXPORT int
Ns_DbDriverInit(char *driver, char *configPath)
{
    RETCODE         rc;

    rc = SQLAllocEnv(&odbcenv);
    if (rc != SQL_SUCCESS) {
	Ns_Log(Error, "%s: failed to allocate odbc", driver);
        return NS_ERROR;
    }
    if (Ns_DbRegisterDriver(driver, odbcProcs) != NS_OK) {
        Ns_Log(Error, "%s: failed to register driver", driver);
        return NS_ERROR;
    }
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCServerInit -
 *
 *	Initialize the server when nsdb is ready.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	Adds ns_odbc command.
 *
 *----------------------------------------------------------------------
 */

static int
AddCmd(Tcl_Interp *interp, void *ignored)
{
    Tcl_CreateCommand(interp, "ns_odbc", ODBCCmd, NULL, NULL);
    return NS_OK;
}

static int
ODBCServerInit(char *server, char *hModule, char *driver)
{
    return Ns_TclInitInterps(server, AddCmd, NULL);
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCName -
 *
 *	Return the ODBC driver name.
 *
 * Results:
 *	Pointer to static string name.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char    *
ODBCName(void)
{
    return odbcName;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCOpenDb -
 *
 *	Open an ODBC datasource.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	Performs both SQLAllocConnect and SQLConnect.
 *
 *----------------------------------------------------------------------
 */

static int
ODBCOpenDb(Ns_DbHandle *handle)
{
    HDBC            hdbc;
    RETCODE         rc;
    int             userlen, passwordlen;

    handle->connected = NS_FALSE;
    handle->connection = NULL;
    handle->statement = NULL;
    rc = SQLAllocConnect(odbcenv, &hdbc);
    ODBCLog(rc, handle);
    if (!RC_OK(rc)) {
        return NS_ERROR;
    }
    if (handle->user != NULL) {
        userlen = strlen(handle->user);
    } else {
        userlen = 0;
    }
    if (handle->password != NULL) {
        passwordlen = strlen(handle->password);
    } else {
        passwordlen = 0;
    }
    Ns_Log(Notice, "%s[%s]: attemping to open '%s'",
	   handle->driver, handle->poolname, handle->datasource);
    rc = SQLConnect(hdbc, handle->datasource,
		    (SWORD) strlen(handle->datasource), handle->user,
		    (SWORD) userlen, handle->password, (SWORD) passwordlen);
    ODBCLog(rc, handle);
    if (!RC_OK(rc)) {
        return NS_ERROR;
    }
    handle->connection = (void *) hdbc;
    handle->connected = NS_TRUE;
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCCloseDb -
 *
 *	Close an ODBC datasource.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	Performs both SQLDisconnect and SQLFreeConnect.
 *
 *----------------------------------------------------------------------
 */

static int
ODBCCloseDb(Ns_DbHandle *handle)
{
    RETCODE         rc;

    rc = SQLDisconnect((HDBC) handle->connection);
    if (!RC_OK(rc)) {
        return NS_ERROR;
    }
    rc = SQLFreeConnect((HDBC) handle->connection);
    if (!RC_OK(rc)) {
        return NS_ERROR;
    }
    handle->connection = NULL;
    handle->connected = NS_FALSE;
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBExec -
 *
 *	Send an SQL statement.
 *
 * Results:
 *	NS_DML, NS_ROWS, or NS_ERROR.
 *
 * Side effects:
 *	Database may be modified or rows may be waiting.
 *
 *----------------------------------------------------------------------
 */

static int
ODBCExec(Ns_DbHandle *handle, char *sql)
{
    HSTMT           hstmt;
    RETCODE         rc;
    int             status;
    short	    numcols;

    /*
     * Allocate a new statement.
     */

    rc = SQLAllocStmt((HDBC) handle->connection, &hstmt);
    ODBCLog(rc, handle);
    if (!RC_OK(rc)) {
        return NS_ERROR;
    }

    /*
     * Send the SQL and determine if rows are available.
     */

    handle->statement = hstmt;
    rc = SQLExecDirect(hstmt, sql, SQL_NTS);
    ODBCLog(rc, handle);
    if (RC_OK(rc)) {
        rc = SQLNumResultCols(hstmt, &numcols);
	ODBCLog(rc, handle);
	if (RC_OK(rc)) {
	    if (numcols != 0) {
		handle->fetchingRows = 1;
		status = NS_ROWS;
	    } else {
	        status = NS_DML;
	    }
	}
    }

    /* 
     * Free the statement unless rows are waiting.
     */

    if (!RC_OK(rc)) {
	status = NS_ERROR;
    }
    if (status != NS_ROWS && ODBCFreeStmt(handle) != NS_OK) {
	status = NS_ERROR;
    }
    return status;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCBindRow -
 *
 *	Bind row after an ODBCExec which returned NS_ROWS.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	Given Ns_Set is modified with column names.
 *
 *----------------------------------------------------------------------
 */

static Ns_Set  *
ODBCBindRow(Ns_DbHandle *handle)
{
    HSTMT           hstmt;
    RETCODE         rc;
    Ns_Set         *row;
    short           i, numcols;
    char            colname[100];
    short           cbcolname;
    SWORD           sqltype, ibscale, nullable;
    UDWORD          cbcoldef;

    if (!handle->fetchingRows) {
        Ns_Log(Error, "%s[%s]: no waiting rows",
	       handle->driver, handle->poolname);
        return NULL;
    }
    row = handle->row;
    hstmt = (HSTMT) handle->statement;
    rc = SQLNumResultCols(hstmt, &numcols);
    for (i = 1; RC_OK(rc) && i <= numcols; i++) {
        rc = SQLDescribeCol(hstmt, i, colname, sizeof(colname),
            &cbcolname, &sqltype, &cbcoldef, &ibscale, &nullable);
        ODBCLog(rc, handle);
	if (RC_OK(rc)) {
	    Ns_SetPut(row, colname, NULL);
	}
    }
    if (!RC_OK(rc)) {
	ODBCFreeStmt(handle);
	row = NULL;
    }
    return row;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCGetRow -
 *
 *	Fetch the next row.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	Given Ns_Set is modified with new values.
 *
 *----------------------------------------------------------------------
 */

static int
ODBCGetRow(Ns_DbHandle *handle, Ns_Set *row)
{
    RETCODE         rc;
    short           i;
    HSTMT           hstmt;
    short           numcols;

    if (!handle->fetchingRows) {
        Ns_Log(Error, "%s[%s]: no waiting rows",
	       handle->driver, handle->poolname);
        return NS_ERROR;
    }
    hstmt = (HSTMT) handle->statement;
    rc = SQLNumResultCols(hstmt, &numcols);
    ODBCLog(rc, handle);
    if (!RC_OK(rc)) {
	goto error;
    }
    if (numcols != Ns_SetSize(row)) {
	Ns_Log(Error, "%s[%s]: mismatched number of rows",
	       handle->driver, handle->poolname);
	goto error;
    }
    rc = SQLFetch(hstmt);
    ODBCLog(rc, handle);
    if (rc == SQL_NO_DATA_FOUND) {
	ODBCFreeStmt(handle);
	return NS_END_DATA;
    }
    for (i = 1; RC_OK(rc) && i <= numcols; i++) {
	char            datum[8192];
	SDWORD          cbvalue;

	rc = SQLGetData(hstmt, i, SQL_C_CHAR, datum, sizeof(datum), &cbvalue);
	ODBCLog(rc, handle);
	if (RC_OK(rc)) {
	    Ns_SetPutValue(row, i - 1, cbvalue == SQL_NULL_DATA ? "" : datum);
	}
    }
    if (!RC_OK(rc)) {
error:
	ODBCFreeStmt(handle);
	return NS_ERROR;
    }
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCCancel -
 *
 *	Cancel the active select.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	Rows are flushed.
 *
 *----------------------------------------------------------------------
 */

static int
ODBCCancel(Ns_DbHandle *handle)
{
    RETCODE         rc;
    int		    status;

    status = NS_OK;
    if (handle->fetchingRows) {
        rc = SQLCancel((HSTMT) handle->statement);
        ODBCLog(rc, handle);
	status = ODBCFreeStmt(handle);
    }
    return status;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCCmd -
 *
 *	Process the ns_odbc command.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
ODBCCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv)
{
    Ns_DbHandle    *handle;
    RETCODE         rc;
    char            buf[MAX_IDENTIFIER];
    SWORD FAR       cbInfoValue;

    if (argc != 3) {
        Tcl_AppendResult(interp, "wrong # args: should be \"",
            argv[0], " cmd handle\"", NULL);
        return TCL_ERROR;
    }
    if (Ns_TclDbGetHandle(interp, argv[2], &handle) != TCL_OK) {
        return TCL_ERROR;
    }

    /*
     * Make sure this is an open ODBC handle.
     */

    if (Ns_DbDriverName(handle) != odbcName) {
        Tcl_AppendResult(interp, "handle \"", argv[1], "\" is not of type \"",
            odbcName, "\"", NULL);
        return TCL_ERROR;
    }

    if (handle->connection == NULL) {
        Tcl_AppendResult(interp, "handle \"", argv[1], "\" not connected", NULL);
        return TCL_ERROR;
    }

    if (STREQ(argv[1], "dbmsname")) {
        rc = SQLGetInfo((HDBC) handle->connection, SQL_DBMS_NAME, buf, sizeof(buf), &cbInfoValue);
        ODBCLog(rc, handle);
        if (!RC_OK(rc)) {
            Tcl_SetResult(interp, "could not determine dbmsname", TCL_STATIC);
            return TCL_ERROR;
        }
    } else if (STREQ(argv[1], "dbmsver")) {
        rc = SQLGetInfo((HDBC) handle->connection, SQL_DBMS_VER, buf, sizeof(buf), &cbInfoValue);
        ODBCLog(rc, handle);
        if (!RC_OK(rc)) {
            Tcl_SetResult(interp, "could not determine dbmsver", TCL_STATIC);
            return TCL_ERROR;
        }
    } else {
        Tcl_AppendResult(interp, "unknown command \"", argv[1],
            "\": should be dbmsname or dbmsver.", NULL);
        return TCL_ERROR;
    }

    Tcl_SetResult(interp, buf, TCL_VOLATILE);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * ODBCLog -
 *
 *	Log a possible error.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May add log entry.
 *
 *----------------------------------------------------------------------
 */

static void
ODBCLog(RETCODE rc, Ns_DbHandle *handle)
{
    HDBC hdbc;
    HSTMT hstmt;
    Ns_LogSeverity  severity;
    char            szSQLSTATE[6];
    SDWORD          nErr;
    char            msg[MAX_ERROR_MSG + 1];
    SWORD            cbmsg;

    if (rc == SQL_SUCCESS_WITH_INFO) {
        severity = Warning;
    } else if (rc == SQL_ERROR) {
        severity = Error;
    } else {
	return;
    }
    hdbc = (HDBC) handle->connection;
    hstmt = (HSTMT) handle->statement;
    while (SQLError(odbcenv, hdbc, hstmt, szSQLSTATE, &nErr, msg,
            sizeof(msg), &cbmsg) == SQL_SUCCESS) {
        Ns_Log(severity, "%s[%s]: odbc message: "
	       "SQLSTATE = %s, Native err = %ld, msg = '%s'",
	       handle->driver, handle->poolname, szSQLSTATE, nErr, msg);
        strcpy(handle->cExceptionCode, szSQLSTATE);
        Ns_DStringFree(&(handle->dsExceptionMsg));
        Ns_DStringAppend(&(handle->dsExceptionMsg), msg);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ODBCFreeStmt -
 *
 *	Free the handle's current statement.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
ODBCFreeStmt(Ns_DbHandle *handle)
{
    RETCODE rc;

    rc = SQLFreeStmt((HSTMT) handle->statement, SQL_DROP);
    handle->statement = NULL;
    handle->fetchingRows = 0;
    if (!RC_OK(rc)) {
	return NS_ERROR;
    }
    return NS_OK;
}

Back to SourceForge.net

Powered by ViewCVS 1.0-dev