You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1644 lines
45 KiB

/* glpsql.c */
/***********************************************************************
* This code is part of GLPK (GNU Linear Programming Kit).
*
* Author: Heinrich Schuchardt <xypron.glpk@gmx.de>.
*
* Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
* 2009, 2010, 2011, 2013 Andrew Makhorin, Department for Applied
* Informatics, Moscow Aviation Institute, Moscow, Russia. All rights
* reserved. E-mail: <mao@gnu.org>.
*
* GLPK is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GLPK 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 GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPK. If not, see <http://www.gnu.org/licenses/>.
***********************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "glpmpl.h"
#include "glpsql.h"
#ifdef ODBC_DLNAME
#define HAVE_ODBC
#define libodbc ODBC_DLNAME
#define h_odbc (get_env_ptr()->h_odbc)
#endif
#ifdef MYSQL_DLNAME
#define HAVE_MYSQL
#define libmysql MYSQL_DLNAME
#define h_mysql (get_env_ptr()->h_mysql)
#endif
static void *db_iodbc_open_int(TABDCA *dca, int mode, const char
**sqllines);
static void *db_mysql_open_int(TABDCA *dca, int mode, const char
**sqllines);
/**********************************************************************/
#if defined(HAVE_ODBC) || defined(HAVE_MYSQL)
#define SQL_FIELD_MAX 100
/* maximal field count */
#define SQL_FDLEN_MAX 255
/* maximal field length */
/***********************************************************************
* NAME
*
* args_concat - concatenate arguments
*
* SYNOPSIS
*
* static char **args_concat(TABDCA *dca);
*
* DESCRIPTION
*
* The arguments passed in dca are SQL statements. A SQL statement may
* be split over multiple arguments. The last argument of a SQL
* statement will be terminated with a semilocon. Each SQL statement is
* merged into a single zero terminated string. Boundaries between
* arguments are replaced by space.
*
* RETURNS
*
* Buffer with SQL statements */
static char **args_concat(TABDCA *dca)
{
const char *arg;
int i;
int j;
int j0;
int j1;
int len;
int lentot;
int narg;
int nline = 0;
void *ret;
char **sqllines = NULL;
narg = mpl_tab_num_args(dca);
/* The SQL statements start with argument 3. */
if (narg < 3)
return NULL;
/* Count the SQL statements */
for (j = 3; j <= narg; j++)
{
arg = mpl_tab_get_arg(dca, j);
len = strlen(arg);
if (arg[len-1] == ';' || j == narg)
nline ++;
}
/* Allocate string buffer. */
sqllines = (char **) xmalloc((nline+1) * sizeof(char **));
/* Join arguments */
sqllines[0] = NULL;
j0 = 3;
i = 0;
lentot = 0;
for (j = 3; j <= narg; j++)
{
arg = mpl_tab_get_arg(dca, j);
len = strlen(arg);
/* add length of part */
lentot += len;
/* add length of space separating parts or 0x00 at end of SQL
statement */
lentot++;
if (arg[len-1] == ';' || j == narg)
{ /* Join arguments for a single SQL statement */
sqllines[i] = xmalloc(lentot);
sqllines[i+1] = NULL;
sqllines[i][0] = 0x00;
for (j1 = j0; j1 <= j; j1++)
{ if(j1>j0)
strcat(sqllines[i], " ");
strcat(sqllines[i], mpl_tab_get_arg(dca, j1));
}
len = strlen(sqllines[i]);
if (sqllines[i][len-1] == ';')
sqllines[i][len-1] = 0x00;
j0 = j+1;
i++;
lentot = 0;
}
}
return sqllines;
}
/***********************************************************************
* NAME
*
* free_buffer - free multiline string buffer
*
* SYNOPSIS
*
* static void free_buffer(char **buf);
*
* DESCRIPTION
*
* buf is a list of strings terminated by NULL.
* The memory for the strings and for the list is released. */
static void free_buffer(char **buf)
{ int i;
for(i = 0; buf[i] != NULL; i++)
xfree(buf[i]);
xfree(buf);
}
static int db_escaped_string_length(const char* from)
/* length of escaped string */
{
int count;
const char *pointer;
for (pointer = from, count = 0; *pointer != (char) '\0'; pointer++,
count++)
{
switch (*pointer)
{
case '\'':
count++;
break;
}
}
return count;
}
static int db_escape_string (char *to, const char *from)
/* escape string*/
{
const char *source = from;
char *target = to;
unsigned int remaining;
remaining = strlen(from);
if (to == NULL)
to = (char *) (from + remaining);
while (remaining > 0)
{
switch (*source)
{
case '\'':
*target = '\'';
target++;
*target = '\'';
break;
default:
*target = *source;
}
source++;
target++;
remaining--;
}
/* Write the terminating NUL character. */
*target = '\0';
return target - to;
}
static char *db_generate_select_stmt(TABDCA *dca)
/* generate select statement */
{
char *arg;
char const *field;
char *query;
int j;
int narg;
int nf;
int total;
total = 50;
nf = mpl_tab_num_flds(dca);
narg = mpl_tab_num_args(dca);
for (j=1; j <= nf && j <= SQL_FIELD_MAX; j++)
{
field = mpl_tab_get_name(dca, j);
total += strlen(field);
total += 2;
}
arg = (char *) mpl_tab_get_arg(dca, narg);
total += strlen(arg);
query = xmalloc( total * sizeof(char));
strcpy (query, "SELECT ");
for (j=1; j <= nf && j <= SQL_FIELD_MAX; j++)
{
field = mpl_tab_get_name(dca, j);
strcat(query, field);
if ( j < nf )
strcat(query, ", ");
}
strcat(query, " FROM ");
strcat(query, arg);
return query;
}
static char *db_generate_insert_stmt(TABDCA *dca)
/* generate insert statement */
{
char *arg;
char const *field;
char *query;
int j;
int narg;
int nf;
int total;
total = 50;
nf = mpl_tab_num_flds(dca);
narg = mpl_tab_num_args(dca);
for (j=1; j <= nf && j <= SQL_FIELD_MAX; j++)
{
field = mpl_tab_get_name(dca, j);
total += strlen(field);
total += 5;
}
arg = (char *) mpl_tab_get_arg(dca, narg);
total += strlen(arg);
query = xmalloc( (total+1) * sizeof(char));
strcpy (query, "INSERT INTO ");
strcat(query, arg);
strcat(query, " ( ");
for (j=1; j <= nf && j <= SQL_FIELD_MAX; j++)
{
field = mpl_tab_get_name(dca, j);
strcat(query, field);
if ( j < nf )
strcat(query, ", ");
}
strcat(query, " ) VALUES ( ");
for (j=1; j <= nf && j <= SQL_FIELD_MAX; j++)
{
strcat(query, "?");
if ( j < nf )
strcat(query, ", ");
}
strcat(query, " )");
return query;
}
#endif
/**********************************************************************/
#ifndef HAVE_ODBC
void *db_iodbc_open(TABDCA *dca, int mode)
{ xassert(dca == dca);
xassert(mode == mode);
xprintf("iODBC table driver not supported\n");
return NULL;
}
int db_iodbc_read(TABDCA *dca, void *link)
{ xassert(dca != dca);
xassert(link != link);
return 0;
}
int db_iodbc_write(TABDCA *dca, void *link)
{ xassert(dca != dca);
xassert(link != link);
return 0;
}
int db_iodbc_close(TABDCA *dca, void *link)
{ xassert(dca != dca);
xassert(link != link);
return 0;
}
#else
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__WOE__)
#include <windows.h>
#endif
#include <sql.h>
#include <sqlext.h>
struct db_odbc
{
int mode; /*'R' = Read, 'W' = Write*/
SQLHDBC hdbc; /*connection handle*/
SQLHENV henv; /*environment handle*/
SQLHSTMT hstmt; /*statement handle*/
SQLSMALLINT nresultcols; /* columns in result*/
SQLULEN collen[SQL_FIELD_MAX+1];
SQLLEN outlen[SQL_FIELD_MAX+1];
SQLSMALLINT coltype[SQL_FIELD_MAX+1];
SQLCHAR data[SQL_FIELD_MAX+1][SQL_FDLEN_MAX+1];
#if 1 /* 12/I-2014 */
SQLDOUBLE datanum[SQL_FIELD_MAX+1];
#endif
SQLCHAR colname[SQL_FIELD_MAX+1][SQL_FDLEN_MAX+1];
int isnumeric[SQL_FIELD_MAX+1];
int nf;
/* number of fields in the csv file */
int ref[1+SQL_FIELD_MAX];
/* ref[k] = k', if k-th field of the csv file corresponds to
k'-th field in the table statement; if ref[k] = 0, k-th field
of the csv file is ignored */
SQLCHAR *query;
/* query generated by db_iodbc_open */
};
SQLRETURN SQL_API dl_SQLAllocHandle (
SQLSMALLINT HandleType,
SQLHANDLE InputHandle,
SQLHANDLE *OutputHandle)
{
typedef SQLRETURN SQL_API ep_SQLAllocHandle(
SQLSMALLINT HandleType,
SQLHANDLE InputHandle,
SQLHANDLE *OutputHandle);
ep_SQLAllocHandle *fn;
fn = (ep_SQLAllocHandle *) xdlsym(h_odbc, "SQLAllocHandle");
xassert(fn != NULL);
return (*fn)(HandleType, InputHandle, OutputHandle);
}
SQLRETURN SQL_API dl_SQLBindCol (
SQLHSTMT StatementHandle,
SQLUSMALLINT ColumnNumber,
SQLSMALLINT TargetType,
SQLPOINTER TargetValue,
SQLLEN BufferLength,
SQLLEN *StrLen_or_Ind)
{
typedef SQLRETURN SQL_API ep_SQLBindCol(
SQLHSTMT StatementHandle,
SQLUSMALLINT ColumnNumber,
SQLSMALLINT TargetType,
SQLPOINTER TargetValue,
SQLLEN BufferLength,
SQLLEN *StrLen_or_Ind);
ep_SQLBindCol *fn;
fn = (ep_SQLBindCol *) xdlsym(h_odbc, "SQLBindCol");
xassert(fn != NULL);
return (*fn)(StatementHandle, ColumnNumber, TargetType,
TargetValue, BufferLength, StrLen_or_Ind);
}
SQLRETURN SQL_API dl_SQLCloseCursor (
SQLHSTMT StatementHandle)
{
typedef SQLRETURN SQL_API ep_SQLCloseCursor (
SQLHSTMT StatementHandle);
ep_SQLCloseCursor *fn;
fn = (ep_SQLCloseCursor *) xdlsym(h_odbc, "SQLCloseCursor");
xassert(fn != NULL);
return (*fn)(StatementHandle);
}
SQLRETURN SQL_API dl_SQLDisconnect (
SQLHDBC ConnectionHandle)
{
typedef SQLRETURN SQL_API ep_SQLDisconnect(
SQLHDBC ConnectionHandle);
ep_SQLDisconnect *fn;
fn = (ep_SQLDisconnect *) xdlsym(h_odbc, "SQLDisconnect");
xassert(fn != NULL);
return (*fn)(ConnectionHandle);
}
SQLRETURN SQL_API dl_SQLDriverConnect (
SQLHDBC hdbc,
SQLHWND hwnd,
SQLCHAR *szConnStrIn,
SQLSMALLINT cbConnStrIn,
SQLCHAR *szConnStrOut,
SQLSMALLINT cbConnStrOutMax,
SQLSMALLINT *pcbConnStrOut,
SQLUSMALLINT fDriverCompletion)
{
typedef SQLRETURN SQL_API ep_SQLDriverConnect(
SQLHDBC hdbc,
SQLHWND hwnd,
SQLCHAR * szConnStrIn,
SQLSMALLINT cbConnStrIn,
SQLCHAR * szConnStrOut,
SQLSMALLINT cbConnStrOutMax,
SQLSMALLINT * pcbConnStrOut,
SQLUSMALLINT fDriverCompletion);
ep_SQLDriverConnect *fn;
fn = (ep_SQLDriverConnect *) xdlsym(h_odbc, "SQLDriverConnect");
xassert(fn != NULL);
return (*fn)(hdbc, hwnd, szConnStrIn, cbConnStrIn, szConnStrOut,
cbConnStrOutMax, pcbConnStrOut, fDriverCompletion);
}
SQLRETURN SQL_API dl_SQLEndTran (
SQLSMALLINT HandleType,
SQLHANDLE Handle,
SQLSMALLINT CompletionType)
{
typedef SQLRETURN SQL_API ep_SQLEndTran (
SQLSMALLINT HandleType,
SQLHANDLE Handle,
SQLSMALLINT CompletionType);
ep_SQLEndTran *fn;
fn = (ep_SQLEndTran *) xdlsym(h_odbc, "SQLEndTran");
xassert(fn != NULL);
return (*fn)(HandleType, Handle, CompletionType);
}
SQLRETURN SQL_API dl_SQLExecDirect (
SQLHSTMT StatementHandle,
SQLCHAR * StatementText,
SQLINTEGER TextLength)
{
typedef SQLRETURN SQL_API ep_SQLExecDirect (
SQLHSTMT StatementHandle,
SQLCHAR * StatementText,
SQLINTEGER TextLength);
ep_SQLExecDirect *fn;
fn = (ep_SQLExecDirect *) xdlsym(h_odbc, "SQLExecDirect");
xassert(fn != NULL);
return (*fn)(StatementHandle, StatementText, TextLength);
}
SQLRETURN SQL_API dl_SQLFetch (
SQLHSTMT StatementHandle)
{
typedef SQLRETURN SQL_API ep_SQLFetch (
SQLHSTMT StatementHandle);
ep_SQLFetch *fn;
fn = (ep_SQLFetch*) xdlsym(h_odbc, "SQLFetch");
xassert(fn != NULL);
return (*fn)(StatementHandle);
}
SQLRETURN SQL_API dl_SQLFreeHandle (
SQLSMALLINT HandleType,
SQLHANDLE Handle)
{
typedef SQLRETURN SQL_API ep_SQLFreeHandle (
SQLSMALLINT HandleType,
SQLHANDLE Handle);
ep_SQLFreeHandle *fn;
fn = (ep_SQLFreeHandle *) xdlsym(h_odbc, "SQLFreeHandle");
xassert(fn != NULL);
return (*fn)(HandleType, Handle);
}
SQLRETURN SQL_API dl_SQLDescribeCol (
SQLHSTMT StatementHandle,
SQLUSMALLINT ColumnNumber,
SQLCHAR * ColumnName,
SQLSMALLINT BufferLength,
SQLSMALLINT * NameLength,
SQLSMALLINT * DataType,
SQLULEN * ColumnSize,
SQLSMALLINT * DecimalDigits,
SQLSMALLINT * Nullable)
{
typedef SQLRETURN SQL_API ep_SQLDescribeCol (
SQLHSTMT StatementHandle,
SQLUSMALLINT ColumnNumber,
SQLCHAR *ColumnName,
SQLSMALLINT BufferLength,
SQLSMALLINT *NameLength,
SQLSMALLINT *DataType,
SQLULEN *ColumnSize,
SQLSMALLINT *DecimalDigits,
SQLSMALLINT *Nullable);
ep_SQLDescribeCol *fn;
fn = (ep_SQLDescribeCol *) xdlsym(h_odbc, "SQLDescribeCol");
xassert(fn != NULL);
return (*fn)(StatementHandle, ColumnNumber, ColumnName,
BufferLength, NameLength,
DataType, ColumnSize, DecimalDigits, Nullable);
}
SQLRETURN SQL_API dl_SQLGetDiagRec (
SQLSMALLINT HandleType,
SQLHANDLE Handle,
SQLSMALLINT RecNumber,
SQLCHAR *Sqlstate,
SQLINTEGER *NativeError,
SQLCHAR *MessageText,
SQLSMALLINT BufferLength,
SQLSMALLINT *TextLength)
{
typedef SQLRETURN SQL_API ep_SQLGetDiagRec (
SQLSMALLINT HandleType,
SQLHANDLE Handle,
SQLSMALLINT RecNumber,
SQLCHAR *Sqlstate,
SQLINTEGER *NativeError,
SQLCHAR *MessageText,
SQLSMALLINT BufferLength,
SQLSMALLINT *TextLength);
ep_SQLGetDiagRec *fn;
fn = (ep_SQLGetDiagRec *) xdlsym(h_odbc, "SQLGetDiagRec");
xassert(fn != NULL);
return (*fn)(HandleType, Handle, RecNumber, Sqlstate,
NativeError, MessageText, BufferLength, TextLength);
}
SQLRETURN SQL_API dl_SQLGetInfo (
SQLHDBC ConnectionHandle,
SQLUSMALLINT InfoType,
SQLPOINTER InfoValue,
SQLSMALLINT BufferLength,
SQLSMALLINT *StringLength)
{
typedef SQLRETURN SQL_API ep_SQLGetInfo (
SQLHDBC ConnectionHandle,
SQLUSMALLINT InfoType,
SQLPOINTER InfoValue,
SQLSMALLINT BufferLength,
SQLSMALLINT *StringLength);
ep_SQLGetInfo *fn;
fn = (ep_SQLGetInfo *) xdlsym(h_odbc, "SQLGetInfo");
xassert(fn != NULL);
return (*fn)(ConnectionHandle, InfoType, InfoValue, BufferLength,
StringLength);
}
SQLRETURN SQL_API dl_SQLNumResultCols (
SQLHSTMT StatementHandle,
SQLSMALLINT *ColumnCount)
{
typedef SQLRETURN SQL_API ep_SQLNumResultCols (
SQLHSTMT StatementHandle,
SQLSMALLINT *ColumnCount);
ep_SQLNumResultCols *fn;
fn = (ep_SQLNumResultCols *) xdlsym(h_odbc, "SQLNumResultCols");
xassert(fn != NULL);
return (*fn)(StatementHandle, ColumnCount);
}
SQLRETURN SQL_API dl_SQLSetConnectAttr (
SQLHDBC ConnectionHandle,
SQLINTEGER Attribute,
SQLPOINTER Value,
SQLINTEGER StringLength)
{
typedef SQLRETURN SQL_API ep_SQLSetConnectAttr (
SQLHDBC ConnectionHandle,
SQLINTEGER Attribute,
SQLPOINTER Value,
SQLINTEGER StringLength);
ep_SQLSetConnectAttr *fn;
fn = (ep_SQLSetConnectAttr *) xdlsym(h_odbc, "SQLSetConnectAttr");
xassert(fn != NULL);
return (*fn)(ConnectionHandle, Attribute, Value, StringLength);
}
SQLRETURN SQL_API dl_SQLSetEnvAttr (
SQLHENV EnvironmentHandle,
SQLINTEGER Attribute,
SQLPOINTER Value,
SQLINTEGER StringLength)
{
typedef SQLRETURN SQL_API ep_SQLSetEnvAttr (
SQLHENV EnvironmentHandle,
SQLINTEGER Attribute,
SQLPOINTER Value,
SQLINTEGER StringLength);
ep_SQLSetEnvAttr *fn;
fn = (ep_SQLSetEnvAttr *) xdlsym(h_odbc, "SQLSetEnvAttr");
xassert(fn != NULL);
return (*fn)(EnvironmentHandle, Attribute, Value, StringLength);
}
static void extract_error(
char *fn,
SQLHANDLE handle,
SQLSMALLINT type);
static int is_numeric(
SQLSMALLINT coltype);
/***********************************************************************
* NAME
*
* db_iodbc_open - open connection to ODBC data base
*
* SYNOPSIS
*
* #include "glpsql.h"
* void *db_iodbc_open(TABDCA *dca, int mode);
*
* DESCRIPTION
*
* The routine db_iodbc_open opens a connection to an ODBC data base.
* It then executes the sql statements passed.
*
* In the case of table read the SELECT statement is executed.
*
* In the case of table write the INSERT statement is prepared.
* RETURNS
*
* The routine returns a pointer to data storage area created. */
void *db_iodbc_open(TABDCA *dca, int mode)
{ void *ret;
char **sqllines;
sqllines = args_concat(dca);
if (sqllines == NULL)
{ xprintf("Missing arguments in table statement.\n"
"Please, supply table driver, dsn, and query.\n");
return NULL;
}
ret = db_iodbc_open_int(dca, mode, (const char **) sqllines);
free_buffer(sqllines);
return ret;
}
static void *db_iodbc_open_int(TABDCA *dca, int mode, const char
**sqllines)
{
struct db_odbc *sql;
SQLRETURN ret;
SQLCHAR FAR *dsn;
SQLCHAR info[256];
SQLSMALLINT colnamelen;
SQLSMALLINT nullable;
SQLSMALLINT scale;
const char *arg;
int narg;
int i, j;
int total;
if (libodbc == NULL)
{
xprintf("No loader for shared ODBC library available\n");
return NULL;
}
if (h_odbc == NULL)
{
h_odbc = xdlopen(libodbc);
if (h_odbc == NULL)
{ xprintf("unable to open library %s\n", libodbc);
xprintf("%s\n", get_err_msg());
return NULL;
}
}
sql = (struct db_odbc *) xmalloc(sizeof(struct db_odbc));
if (sql == NULL)
return NULL;
sql->mode = mode;
sql->hdbc = NULL;
sql->henv = NULL;
sql->hstmt = NULL;
sql->query = NULL;
narg = mpl_tab_num_args(dca);
dsn = (SQLCHAR FAR *) mpl_tab_get_arg(dca, 2);
/* allocate an environment handle */
ret = dl_SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,
&(sql->henv));
/* set attribute to enable application to run as ODBC 3.0
application */
ret = dl_SQLSetEnvAttr(sql->henv, SQL_ATTR_ODBC_VERSION,
(void *) SQL_OV_ODBC3, 0);
/* allocate a connection handle */
ret = dl_SQLAllocHandle(SQL_HANDLE_DBC, sql->henv, &(sql->hdbc));
/* connect */
ret = dl_SQLDriverConnect(sql->hdbc, NULL, dsn, SQL_NTS, NULL, 0,
NULL, SQL_DRIVER_COMPLETE);
if (SQL_SUCCEEDED(ret))
{ /* output information about data base connection */
xprintf("Connected to ");
dl_SQLGetInfo(sql->hdbc, SQL_DBMS_NAME, (SQLPOINTER)info,
sizeof(info), NULL);
xprintf("%s ", info);
dl_SQLGetInfo(sql->hdbc, SQL_DBMS_VER, (SQLPOINTER)info,
sizeof(info), NULL);
xprintf("%s - ", info);
dl_SQLGetInfo(sql->hdbc, SQL_DATABASE_NAME, (SQLPOINTER)info,
sizeof(info), NULL);
xprintf("%s\n", info);
}
else
{ /* describe error */
xprintf("Failed to connect\n");
extract_error("SQLDriverConnect", sql->hdbc, SQL_HANDLE_DBC);
dl_SQLFreeHandle(SQL_HANDLE_DBC, sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_ENV, sql->henv);
xfree(sql);
return NULL;
}
/* set AUTOCOMMIT on*/
ret = dl_SQLSetConnectAttr(sql->hdbc, SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER)SQL_AUTOCOMMIT_ON, 0);
/* allocate a statement handle */
ret = dl_SQLAllocHandle(SQL_HANDLE_STMT, sql->hdbc, &(sql->hstmt));
/* initialization queries */
for(j = 0; sqllines[j+1] != NULL; j++)
{
sql->query = (SQLCHAR *) sqllines[j];
xprintf("%s\n", sql->query);
ret = dl_SQLExecDirect(sql->hstmt, sql->query, SQL_NTS);
switch (ret)
{
case SQL_SUCCESS:
case SQL_SUCCESS_WITH_INFO:
case SQL_NO_DATA_FOUND:
break;
default:
xprintf("db_iodbc_open: Query\n\"%s\"\nfailed.\n",
sql->query);
extract_error("SQLExecDirect", sql->hstmt, SQL_HANDLE_STMT);
dl_SQLFreeHandle(SQL_HANDLE_STMT, sql->hstmt);
dl_SQLDisconnect(sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_DBC, sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_ENV, sql->henv);
xfree(sql);
return NULL;
}
/* commit statement */
dl_SQLEndTran(SQL_HANDLE_ENV, sql->henv, SQL_COMMIT);
}
if ( sql->mode == 'R' )
{ sql->nf = mpl_tab_num_flds(dca);
for(j = 0; sqllines[j] != NULL; j++)
arg = sqllines[j];
total = strlen(arg);
if (total > 7 && 0 == strncmp(arg, "SELECT ", 7))
{
total = strlen(arg);
sql->query = xmalloc( (total+1) * sizeof(char));
strcpy (sql->query, arg);
}
else
{
sql->query = db_generate_select_stmt(dca);
}
xprintf("%s\n", sql->query);
if (dl_SQLExecDirect(sql->hstmt, sql->query, SQL_NTS) !=
SQL_SUCCESS)
{
xprintf("db_iodbc_open: Query\n\"%s\"\nfailed.\n", sql->query);
extract_error("SQLExecDirect", sql->hstmt, SQL_HANDLE_STMT);
dl_SQLFreeHandle(SQL_HANDLE_STMT, sql->hstmt);
dl_SQLDisconnect(sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_DBC, sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_ENV, sql->henv);
xfree(sql->query);
xfree(sql);
return NULL;
}
xfree(sql->query);
/* determine number of result columns */
ret = dl_SQLNumResultCols(sql->hstmt, &sql->nresultcols);
total = sql->nresultcols;
if (total > SQL_FIELD_MAX)
{ xprintf("db_iodbc_open: Too many fields (> %d) in query.\n"
"\"%s\"\n", SQL_FIELD_MAX, sql->query);
dl_SQLFreeHandle(SQL_HANDLE_STMT, sql->hstmt);
dl_SQLDisconnect(sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_DBC, sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_ENV, sql->henv);
xfree(sql->query);
return NULL;
}
for (i = 1; i <= total; i++)
{ /* return a set of attributes for a column */
ret = dl_SQLDescribeCol(sql->hstmt, (SQLSMALLINT) i,
sql->colname[i], SQL_FDLEN_MAX,
&colnamelen, &(sql->coltype[i]), &(sql->collen[i]), &scale,
&nullable);
sql->isnumeric[i] = is_numeric(sql->coltype[i]);
/* bind columns to program vars, converting all types to CHAR*/
if (sql->isnumeric[i])
#if 0 /* 12/I-2014 */
{ dl_SQLBindCol(sql->hstmt, i, SQL_DOUBLE, sql->data[i],
#else
{ dl_SQLBindCol(sql->hstmt, i, SQL_DOUBLE, &sql->datanum[i],
#endif
SQL_FDLEN_MAX, &(sql->outlen[i]));
} else
{ dl_SQLBindCol(sql->hstmt, i, SQL_CHAR, sql->data[i],
SQL_FDLEN_MAX, &(sql->outlen[i]));
}
for (j = sql->nf; j >= 1; j--)
{ if (strcmp(mpl_tab_get_name(dca, j), sql->colname[i]) == 0)
break;
}
sql->ref[i] = j;
}
}
else if ( sql->mode == 'W' )
{ for(j = 0; sqllines[j] != NULL; j++)
arg = sqllines[j];
if ( NULL != strchr(arg, '?') )
{
total = strlen(arg);
sql->query = xmalloc( (total+1) * sizeof(char));
strcpy (sql->query, arg);
}
else
{
sql->query = db_generate_insert_stmt(dca);
}
xprintf("%s\n", sql->query);
}
return sql;
}
int db_iodbc_read(TABDCA *dca, void *link)
{
struct db_odbc *sql;
SQLRETURN ret;
char buf[SQL_FDLEN_MAX+1];
int i;
int len;
double num;
sql = (struct db_odbc *) link;
xassert(sql != NULL);
xassert(sql->mode == 'R');
ret=dl_SQLFetch(sql->hstmt);
if (ret== SQL_ERROR)
return -1;
if (ret== SQL_NO_DATA_FOUND)
return -1; /*EOF*/
for (i=1; i <= sql->nresultcols; i++)
{
if (sql->ref[i] > 0)
{
len = sql->outlen[i];
if (len != SQL_NULL_DATA)
{
if (sql->isnumeric[i])
{ mpl_tab_set_num(dca, sql->ref[i],
#if 0 /* 12/I-2014 */
*((const double *) sql->data[i]));
#else
(const double) sql->datanum[i]);
#endif
}
else
{ if (len > SQL_FDLEN_MAX)
len = SQL_FDLEN_MAX;
else if (len < 0)
len = 0;
strncpy(buf, (const char *) sql->data[i], len);
buf[len] = 0x00;
mpl_tab_set_str(dca, sql->ref[i], strtrim(buf));
}
}
}
}
return 0;
}
int db_iodbc_write(TABDCA *dca, void *link)
{
struct db_odbc *sql;
char *part;
char *query;
char *template;
char num[50];
int k;
int len;
int nf;
sql = (struct db_odbc *) link;
xassert(sql != NULL);
xassert(sql->mode == 'W');
len = strlen(sql->query);
template = (char *) xmalloc( (len + 1) * sizeof(char) );
strcpy(template, sql->query);
nf = mpl_tab_num_flds(dca);
for (k = 1; k <= nf; k++)
{ switch (mpl_tab_get_type(dca, k))
{ case 'N':
len += 20;
break;
case 'S':
len += db_escaped_string_length(mpl_tab_get_str(dca, k));
len += 2;
break;
default:
xassert(dca != dca);
}
}
query = xmalloc( (len + 1 ) * sizeof(char) );
query[0] = 0x00;
for (k = 1, part = strtok (template, "?"); (part != NULL);
part = strtok (NULL, "?"), k++)
{
if (k > nf) break;
strcat( query, part );
switch (mpl_tab_get_type(dca, k))
{ case 'N':
#if 0 /* 02/XI-2010 by xypron */
sprintf(num, "%-18g",mpl_tab_get_num(dca, k));
#else
sprintf(num, "%.*g", DBL_DIG, mpl_tab_get_num(dca, k));
#endif
strcat( query, num );
break;
case 'S':
strcat( query, "'");
db_escape_string( query + strlen(query),
mpl_tab_get_str(dca, k) );
strcat( query, "'");
break;
default:
xassert(dca != dca);
}
}
if (part != NULL)
strcat(query, part);
if (dl_SQLExecDirect(sql->hstmt, (SQLCHAR *) query, SQL_NTS)
!= SQL_SUCCESS)
{
xprintf("db_iodbc_write: Query\n\"%s\"\nfailed.\n", query);
extract_error("SQLExecDirect", sql->hdbc, SQL_HANDLE_DBC);
xfree(query);
xfree(template);
return 1;
}
xfree(query);
xfree(template);
return 0;
}
int db_iodbc_close(TABDCA *dca, void *link)
{
struct db_odbc *sql;
sql = (struct db_odbc *) link;
xassert(sql != NULL);
/* Commit */
if ( sql->mode == 'W' )
dl_SQLEndTran(SQL_HANDLE_ENV, sql->henv, SQL_COMMIT);
if ( sql->mode == 'R' )
dl_SQLCloseCursor(sql->hstmt);
dl_SQLFreeHandle(SQL_HANDLE_STMT, sql->hstmt);
dl_SQLDisconnect(sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_DBC, sql->hdbc);
dl_SQLFreeHandle(SQL_HANDLE_ENV, sql->henv);
if ( sql->mode == 'W' )
xfree(sql->query);
xfree(sql);
dca->link = NULL;
return 0;
}
static void extract_error(
char *fn,
SQLHANDLE handle,
SQLSMALLINT type)
{
SQLINTEGER i = 0;
SQLINTEGER native;
SQLCHAR state[ 7 ];
SQLCHAR text[256];
SQLSMALLINT len;
SQLRETURN ret;
xprintf("\nThe driver reported the following diagnostics whilst "
"running %s\n", fn);
do
{
ret = dl_SQLGetDiagRec(type, handle, ++i, state, &native, text,
sizeof(text), &len );
if (SQL_SUCCEEDED(ret))
xprintf("%s:%ld:%ld:%s\n", state, i, native, text);
}
while( ret == SQL_SUCCESS );
}
static int is_numeric(SQLSMALLINT coltype)
{
int ret = 0;
switch (coltype)
{
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_SMALLINT:
case SQL_INTEGER:
case SQL_REAL:
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_TINYINT:
case SQL_BIGINT:
ret = 1;
break;
}
return ret;
}
#endif
/**********************************************************************/
#ifndef HAVE_MYSQL
void *db_mysql_open(TABDCA *dca, int mode)
{ xassert(dca == dca);
xassert(mode == mode);
xprintf("MySQL table driver not supported\n");
return NULL;
}
int db_mysql_read(TABDCA *dca, void *link)
{ xassert(dca != dca);
xassert(link != link);
return 0;
}
int db_mysql_write(TABDCA *dca, void *link)
{ xassert(dca != dca);
xassert(link != link);
return 0;
}
int db_mysql_close(TABDCA *dca, void *link)
{ xassert(dca != dca);
xassert(link != link);
return 0;
}
#else
#if defined(__CYGWIN__) || defined(__MINGW32__) || defined(__WOE__)
#include <windows.h>
#endif
#ifdef __CYGWIN__
#define byte_defined 1
#endif
#if 0 /* 12/II-2014; to fix namespace bug */
#include <my_global.h>
#include <my_sys.h>
#endif
#include <mysql.h>
struct db_mysql
{
int mode; /*'R' = Read, 'W' = Write*/
MYSQL *con; /*connection*/
MYSQL_RES *res; /*result*/
int nf;
/* number of fields in the csv file */
int ref[1+SQL_FIELD_MAX];
/* ref[k] = k', if k-th field of the csv file corresponds to
k'-th field in the table statement; if ref[k] = 0, k-th field
of the csv file is ignored */
char *query;
/* query generated by db_mysql_open */
};
void STDCALL dl_mysql_close(MYSQL *sock)
{
typedef void STDCALL ep_mysql_close(MYSQL *sock);
ep_mysql_close *fn;
fn = (ep_mysql_close *) xdlsym(h_mysql, "mysql_close");
xassert(fn != NULL);
return (*fn)(sock);
}
const char * STDCALL dl_mysql_error(MYSQL *mysql)
{
typedef const char * STDCALL ep_mysql_error(MYSQL *mysql);
ep_mysql_error *fn;
fn = (ep_mysql_error *) xdlsym(h_mysql, "mysql_error");
xassert(fn != NULL);
return (*fn)(mysql);
}
MYSQL_FIELD * STDCALL dl_mysql_fetch_fields(MYSQL_RES *res)
{
typedef MYSQL_FIELD * STDCALL
ep_mysql_fetch_fields(MYSQL_RES *res);
ep_mysql_fetch_fields *fn;
fn = (ep_mysql_fetch_fields *) xdlsym(h_mysql, "mysql_fetch_fields");
xassert(fn != NULL);
return (*fn)(res);
}
unsigned long * STDCALL dl_mysql_fetch_lengths(MYSQL_RES *result)
{
typedef unsigned long * STDCALL
ep_mysql_fetch_lengths(MYSQL_RES *result);
ep_mysql_fetch_lengths *fn;
fn = (ep_mysql_fetch_lengths *) xdlsym(h_mysql,
"mysql_fetch_lengths");
xassert(fn != NULL);
return (*fn)(result);
}
MYSQL_ROW STDCALL dl_mysql_fetch_row(MYSQL_RES *result)
{
typedef MYSQL_ROW STDCALL ep_mysql_fetch_row(MYSQL_RES *result);
ep_mysql_fetch_row *fn;
fn = (ep_mysql_fetch_row *) xdlsym(h_mysql, "mysql_fetch_row");
xassert(fn != NULL);
return (*fn)(result);
}
unsigned int STDCALL dl_mysql_field_count(MYSQL *mysql)
{
typedef unsigned int STDCALL ep_mysql_field_count(MYSQL *mysql);
ep_mysql_field_count *fn;
fn = (ep_mysql_field_count *) xdlsym(h_mysql, "mysql_field_count");
xassert(fn != NULL);
return (*fn)(mysql);
}
MYSQL * STDCALL dl_mysql_init(MYSQL *mysql)
{
typedef MYSQL * STDCALL ep_mysql_init(MYSQL *mysql);
ep_mysql_init *fn;
fn = (ep_mysql_init *) xdlsym(h_mysql, "mysql_init");
xassert(fn != NULL);
return (*fn)(mysql);
}
unsigned int STDCALL dl_mysql_num_fields(MYSQL_RES *res)
{
typedef unsigned int STDCALL ep_mysql_num_fields(MYSQL_RES *res);
ep_mysql_num_fields *fn;
fn = (ep_mysql_num_fields *) xdlsym(h_mysql, "mysql_num_fields");
xassert(fn != NULL);
return (*fn)(res);
}
int STDCALL dl_mysql_query(MYSQL *mysql, const char *q)
{
typedef int STDCALL ep_mysql_query(MYSQL *mysql, const char *q);
ep_mysql_query *fn;
fn = (ep_mysql_query *) xdlsym(h_mysql, "mysql_query");
xassert(fn != NULL);
return (*fn)(mysql, q);
}
MYSQL * STDCALL dl_mysql_real_connect(MYSQL *mysql, const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket,
unsigned long clientflag)
{
typedef MYSQL * STDCALL ep_mysql_real_connect(MYSQL *mysql,
const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket,
unsigned long clientflag);
ep_mysql_real_connect *fn;
fn = (ep_mysql_real_connect *) xdlsym(h_mysql,
"mysql_real_connect");
xassert(fn != NULL);
return (*fn)(mysql, host, user, passwd, db, port, unix_socket,
clientflag);
}
MYSQL_RES * STDCALL dl_mysql_use_result(MYSQL *mysql)
{
typedef MYSQL_RES * STDCALL ep_mysql_use_result(MYSQL *mysql);
ep_mysql_use_result *fn;
fn = (ep_mysql_use_result *) xdlsym(h_mysql, "mysql_use_result");
xassert(fn != NULL);
return (*fn)(mysql);
}
/***********************************************************************
* NAME
*
* db_mysql_open - open connection to ODBC data base
*
* SYNOPSIS
*
* #include "glpsql.h"
* void *db_mysql_open(TABDCA *dca, int mode);
*
* DESCRIPTION
*
* The routine db_mysql_open opens a connection to a MySQL data base.
* It then executes the sql statements passed.
*
* In the case of table read the SELECT statement is executed.
*
* In the case of table write the INSERT statement is prepared.
* RETURNS
*
* The routine returns a pointer to data storage area created. */
void *db_mysql_open(TABDCA *dca, int mode)
{ void *ret;
char **sqllines;
sqllines = args_concat(dca);
if (sqllines == NULL)
{ xprintf("Missing arguments in table statement.\n"
"Please, supply table driver, dsn, and query.\n");
return NULL;
}
ret = db_mysql_open_int(dca, mode, (const char **) sqllines);
free_buffer(sqllines);
return ret;
}
static void *db_mysql_open_int(TABDCA *dca, int mode, const char
**sqllines)
{
struct db_mysql *sql = NULL;
char *arg = NULL;
const char *field;
MYSQL_FIELD *fields;
char *keyword;
char *value;
char *query;
char *dsn;
/* "Server=[server_name];Database=[database_name];UID=[username];*/
/* PWD=[password];Port=[port]"*/
char *server = NULL; /* Server */
char *user = NULL; /* UID */
char *password = NULL; /* PWD */
char *database = NULL; /* Database */
unsigned int port = 0; /* Port */
int narg;
int i, j, total;
if (libmysql == NULL)
{
xprintf("No loader for shared MySQL library available\n");
return NULL;
}
if (h_mysql == NULL)
{
h_mysql = xdlopen(libmysql);
if (h_mysql == NULL)
{ xprintf("unable to open library %s\n", libmysql);
xprintf("%s\n", get_err_msg());
return NULL;
}
}
sql = (struct db_mysql *) xmalloc(sizeof(struct db_mysql));
if (sql == NULL)
return NULL;
sql->mode = mode;
sql->res = NULL;
sql->query = NULL;
sql->nf = mpl_tab_num_flds(dca);
narg = mpl_tab_num_args(dca);
if (narg < 3 )
xprintf("MySQL driver: string list too short \n");
/* get connection string*/
dsn = (char *) mpl_tab_get_arg(dca, 2);
/* copy connection string*/
i = strlen(dsn);
i++;
arg = xmalloc(i * sizeof(char));
strcpy(arg, dsn);
/*tokenize connection string*/
for (i = 1, keyword = strtok (arg, "="); (keyword != NULL);
keyword = strtok (NULL, "="), i++)
{
value = strtok (NULL, ";");
if (value==NULL)
{
xprintf("db_mysql_open: Missing value for keyword %s\n",
keyword);
xfree(arg);
xfree(sql);
return NULL;
}
if (0 == strcmp(keyword, "Server"))
server = value;
else if (0 == strcmp(keyword, "Database"))
database = value;
else if (0 == strcmp(keyword, "UID"))
user = value;
else if (0 == strcmp(keyword, "PWD"))
password = value;
else if (0 == strcmp(keyword, "Port"))
port = (unsigned int) atol(value);
}
/* Connect to database */
sql->con = dl_mysql_init(NULL);
if (!dl_mysql_real_connect(sql->con, server, user, password, database,
port, NULL, 0))
{
xprintf("db_mysql_open: Connect failed\n");
xprintf("%s\n", dl_mysql_error(sql->con));
xfree(arg);
xfree(sql);
return NULL;
}
xfree(arg);
for(j = 0; sqllines[j+1] != NULL; j++)
{ query = (char *) sqllines[j];
xprintf("%s\n", query);
if (dl_mysql_query(sql->con, query))
{
xprintf("db_mysql_open: Query\n\"%s\"\nfailed.\n", query);
xprintf("%s\n",dl_mysql_error(sql->con));
dl_mysql_close(sql->con);
xfree(sql);
return NULL;
}
}
if ( sql->mode == 'R' )
{ sql->nf = mpl_tab_num_flds(dca);
for(j = 0; sqllines[j] != NULL; j++)
arg = (char *) sqllines[j];
total = strlen(arg);
if (total > 7 && 0 == strncmp(arg, "SELECT ", 7))
{
total = strlen(arg);
query = xmalloc( (total+1) * sizeof(char));
strcpy (query, arg);
}
else
{
query = db_generate_select_stmt(dca);
}
xprintf("%s\n", query);
if (dl_mysql_query(sql->con, query))
{
xprintf("db_mysql_open: Query\n\"%s\"\nfailed.\n", query);
xprintf("%s\n",dl_mysql_error(sql->con));
dl_mysql_close(sql->con);
xfree(query);
xfree(sql);
return NULL;
}
xfree(query);
sql->res = dl_mysql_use_result(sql->con);
if (sql->res)
{
/* create references between query results and table fields*/
total = dl_mysql_num_fields(sql->res);
if (total > SQL_FIELD_MAX)
{ xprintf("db_mysql_open: Too many fields (> %d) in query.\n"
"\"%s\"\n", SQL_FIELD_MAX, query);
xprintf("%s\n",dl_mysql_error(sql->con));
dl_mysql_close(sql->con);
xfree(query);
xfree(sql);
return NULL;
}
fields = dl_mysql_fetch_fields(sql->res);
for (i = 1; i <= total; i++)
{
for (j = sql->nf; j >= 1; j--)
{
if (strcmp(mpl_tab_get_name(dca, j), fields[i-1].name)
== 0)
break;
}
sql->ref[i] = j;
}
}
else
{
if(dl_mysql_field_count(sql->con) == 0)
{
xprintf("db_mysql_open: Query was not a SELECT\n\"%s\"\n",
query);
xprintf("%s\n",dl_mysql_error(sql->con));
xfree(query);
xfree(sql);
return NULL;
}
else
{
xprintf("db_mysql_open: Query\n\"%s\"\nfailed.\n", query);
xprintf("%s\n",dl_mysql_error(sql->con));
xfree(query);
xfree(sql);
return NULL;
}
}
}
else if ( sql->mode == 'W' )
{ for(j = 0; sqllines[j] != NULL; j++)
arg = (char *) sqllines[j];
if ( NULL != strchr(arg, '?') )
{
total = strlen(arg);
query = xmalloc( (total+1) * sizeof(char));
strcpy (query, arg);
}
else
query = db_generate_insert_stmt(dca);
sql->query = query;
xprintf("%s\n", query);
}
return sql;
}
int db_mysql_read(TABDCA *dca, void *link)
{ struct db_mysql *sql;
char buf[255+1];
char **row;
unsigned long *lengths;
MYSQL_FIELD *fields;
double num;
int len;
unsigned long num_fields;
int i;
sql = (struct db_mysql *) link;
xassert(sql != NULL);
xassert(sql->mode == 'R');
if (NULL == sql->res)
{
xprintf("db_mysql_read: no result set available");
return 1;
}
if (NULL==(row = (char **)dl_mysql_fetch_row(sql->res))) {
return -1; /*EOF*/
}
lengths = dl_mysql_fetch_lengths(sql->res);
fields = dl_mysql_fetch_fields(sql->res);
num_fields = dl_mysql_num_fields(sql->res);
for (i=1; i <= num_fields; i++)
{
if (row[i-1] != NULL)
{ len = (size_t) lengths[i-1];
if (len > 255)
len = 255;
strncpy(buf, (const char *) row[i-1], len);
buf[len] = 0x00;
if (0 != (fields[i-1].flags & NUM_FLAG))
{ strspx(buf); /* remove spaces*/
if (str2num(buf, &num) != 0)
{ xprintf("'%s' cannot be converted to a number.\n", buf);
return 1;
}
if (sql->ref[i] > 0)
mpl_tab_set_num(dca, sql->ref[i], num);
}
else
{ if (sql->ref[i] > 0)
mpl_tab_set_str(dca, sql->ref[i], strtrim(buf));
}
}
}
return 0;
}
int db_mysql_write(TABDCA *dca, void *link)
{
struct db_mysql *sql;
char *part;
char *query;
char *template;
char num[50];
int k;
int len;
int nf;
sql = (struct db_mysql *) link;
xassert(sql != NULL);
xassert(sql->mode == 'W');
len = strlen(sql->query);
template = (char *) xmalloc( (len + 1) * sizeof(char) );
strcpy(template, sql->query);
nf = mpl_tab_num_flds(dca);
for (k = 1; k <= nf; k++)
{ switch (mpl_tab_get_type(dca, k))
{ case 'N':
len += 20;
break;
case 'S':
len += db_escaped_string_length(mpl_tab_get_str(dca, k));
len += 2;
break;
default:
xassert(dca != dca);
}
}
query = xmalloc( (len + 1 ) * sizeof(char) );
query[0] = 0x00;
for (k = 1, part = strtok (template, "?"); (part != NULL);
part = strtok (NULL, "?"), k++)
{
if (k > nf) break;
strcat( query, part );
switch (mpl_tab_get_type(dca, k))
{ case 'N':
#if 0 /* 02/XI-2010 by xypron */
sprintf(num, "%-18g",mpl_tab_get_num(dca, k));
#else
sprintf(num, "%.*g", DBL_DIG, mpl_tab_get_num(dca, k));
#endif
strcat( query, num );
break;
case 'S':
strcat( query, "'");
db_escape_string( query + strlen(query),
mpl_tab_get_str(dca, k) );
strcat( query, "'");
break;
default:
xassert(dca != dca);
}
}
if (part != NULL)
strcat(query, part);
if (dl_mysql_query(sql->con, query))
{
xprintf("db_mysql_write: Query\n\"%s\"\nfailed.\n", query);
xprintf("%s\n",dl_mysql_error(sql->con));
xfree(query);
xfree(template);
return 1;
}
xfree(query);
xfree(template);
return 0;
}
int db_mysql_close(TABDCA *dca, void *link)
{
struct db_mysql *sql;
sql = (struct db_mysql *) link;
xassert(sql != NULL);
dl_mysql_close(sql->con);
if ( sql->mode == 'W' )
xfree(sql->query);
xfree(sql);
dca->link = NULL;
return 0;
}
#endif
/* eof */