/*
* gksql_firebird.cxx
*
* Firebird/Interbase driver module for GnuGk
*
* Copyright (c) 2006, Michal Zygmuntowicz
*
* This work is published under the GNU Public License (GPL)
* see file COPYING for details.
* We also explicitely grant the right to link this code
* with the OpenH323 library.
*
* $Log: gksql_firebird.cxx,v $
* Revision 1.2 2006/06/08 07:38:42 willamowius
* compile fixes for gcc 3.3.x
*
* Revision 1.1 2006/06/02 09:21:34 zvision
* Firebird SQL driver
*
*/
#if HAS_FIREBIRD
#if defined(_WIN32) && (_MSC_VER <= 1200)
#pragma warning(disable:4786) // warning about too long debug symbol off
#pragma warning(disable:4284)
#endif
#include <ptlib.h>
#include <cmath>
#include <ibase.h>
#include "gksql.h"
namespace {
PString XSQLVARToPString(XSQLVAR *sqlvar)
{
if (sqlvar->sqltype & 1)
if (*(sqlvar->sqlind) == -1)
return PString();
switch (sqlvar->sqltype & ~1L) {
case SQL_TEXT:
return PString(sqlvar->sqldata, sqlvar->sqllen);
case SQL_SHORT:
return sqlvar->sqlscale < 0
? PString(PString::Decimal, *(int*)(sqlvar->sqldata) * pow(10.0, sqlvar->sqlscale), abs(sqlvar->sqlscale))
: PString(*(short*)(sqlvar->sqldata));
case SQL_LONG:
return sqlvar->sqlscale < 0
? PString(PString::Decimal, *(long*)(sqlvar->sqldata) * pow(10.0, sqlvar->sqlscale), abs(sqlvar->sqlscale))
: PString(*(long*)(sqlvar->sqldata));
case SQL_DOUBLE:
return sqlvar->sqlscale < 0
? PString(PString::Decimal, *(double*)(sqlvar->sqldata) * pow(10.0, sqlvar->sqlscale), abs(sqlvar->sqlscale))
: PString(PString::Printf, "%f", *(double*)(sqlvar->sqldata));
case SQL_INT64:
return sqlvar->sqlscale < 0
? PString(PString::Decimal, *(ISC_INT64*)(sqlvar->sqldata) * pow(10.0, sqlvar->sqlscale), abs(sqlvar->sqlscale))
: PString(*(ISC_INT64*)(sqlvar->sqldata));
case SQL_FLOAT:
return PString(PString::Printf, "%f", (double)*(float*)(sqlvar->sqldata));
case SQL_VARYING:
return PString(sqlvar->sqldata + 2, *(short*)(sqlvar->sqldata));
default:
return PString();
}
}
} // end of namespace
/** Class that encapsulates SQL query result for Firebird backend.
It does not provide any multithread safety, so should be accessed
from a single thread at time.
*/
class GkIBSQLResult : public GkSQLResult
{
public:
/// Build the result from SELECT type query
GkIBSQLResult(
/// transaction handle
isc_tr_handle tr,
/// statement handle
isc_stmt_handle stmt,
/// SELECT type query result
XSQLDA* selectResult
);
/// Build the empty result and store query execution error information
GkIBSQLResult(
/// Firebird specific error code
unsigned int errorCode,
/// PostgreSQL specific error message text
const char* errorMsg,
/// transaction handle
isc_tr_handle tr = NULL,
/// statement handle
isc_stmt_handle stmt = NULL,
/// SELECT type query result
XSQLDA* selectResult = NULL
);
virtual ~GkIBSQLResult();
/** @return
Backend specific error message, if the query failed.
*/
virtual PString GetErrorMessage();
/** @return
Backend specific error code, if the query failed.
*/
virtual long GetErrorCode();
/** @return
True if rows can be fetched in random access order, false if
rows have to be fethed sequentially and can be retrieved only once.
*/
virtual bool HasRandomAccess();
/** Fetch a single row from the result set. After each row is fetched,
cursor position is moved to a next row.
@return
True if the row has been fetched, false if no more rows are available.
*/
virtual bool FetchRow(
/// array to be filled with string representations of the row fields
PStringArray& result
);
virtual bool FetchRow(
/// array to be filled with string representations of the row fields
ResultRow& result
);
/** @return
True if the column at the index #fieldOffset# is NULL in the row
fetched most recently.
*/
virtual bool IsNullField(
/// index of the column to check
long fieldOffset
);
/** Fetch a single row from the result set. This function requires
that the backend supports random row access.
@return
True if the row has been fetched, false if a row at the given offset
does not exists or SQL backend does not support random row access.
*/
virtual bool FetchRow(
/// array to be filled with string representations of the row fields
PStringArray& result,
/// index (0 based) of the row to fetch
long rowOffset
);
virtual bool FetchRow(
/// array to be filled with string representations of the row fields
ResultRow& result,
/// index (0 based) of the row to fetch
long rowOffset
);
/** @return
True if the column at the index #fieldOffset# is NULL in the row
at the specified index.
*/
virtual bool IsNullField(
/// index of the column to check
long fieldOffset,
/// index (0 based) of the row to check
long rowOffset
);
private:
GkIBSQLResult();
GkIBSQLResult(const GkIBSQLResult&);
GkIBSQLResult& operator=(const GkIBSQLResult&);
protected:
/// query result for SELECT type queries
XSQLDA* m_sqlResult;
/// transaction handle
isc_tr_handle m_tr;
/// statement handle
isc_stmt_handle m_stmt;
/// the most recent row returned by fetch operation
int m_sqlRow;
/// Firebird specific error code (if the query failed)
unsigned int m_errorCode;
/// Firebird specific error message text (if the query failed)
PString m_errorMessage;
};
/// Firebird/Interbase backend connection implementation.
class GkIBSQLConnection : public GkSQLConnection
{
public:
/// Build a new Firebird connection object
GkIBSQLConnection(
/// name to use in the log
const char* name = "Firebird"
);
virtual ~GkIBSQLConnection();
protected:
class IBSQLConnWrapper : public GkSQLConnection::SQLConnWrapper
{
public:
IBSQLConnWrapper(
/// unique identifier for this connection
int id,
/// host:port this connection is made to
const PString& host,
/// Firebird connection object
isc_db_handle conn
) : SQLConnWrapper(id, host), m_conn(conn) {}
virtual ~IBSQLConnWrapper();
private:
IBSQLConnWrapper();
IBSQLConnWrapper(const IBSQLConnWrapper&);
IBSQLConnWrapper& operator=(const IBSQLConnWrapper&);
public:
isc_db_handle m_conn;
};
/** Create a new SQL connection using parameters stored in this object.
When the connection is to be closed, the object is simply deleted
using delete operator.
@return
NULL if database connection could not be established
or an object of IBSQLConnWrapper class.
*/
virtual SQLConnPtr CreateNewConnection(
/// unique identifier for this connection
int id
);
/** Execute the query using specified SQL connection.
@return
Query execution result.
*/
virtual GkSQLResult* ExecuteQuery(
/// SQL connection to use for query execution
SQLConnPtr conn,
/// query string
const char* queryStr,
/// maximum time (ms) for the query execution, -1 means infinite
long timeout = -1
);
/** Escape any special characters in the string, so it can be used in a SQL query.
@return
Escaped string.
*/
virtual PString EscapeString(
/// SQL connection to get escaping parameters from
SQLConnPtr conn,
/// string to be escaped
const char* str
);
private:
GkIBSQLConnection(const GkIBSQLConnection&);
GkIBSQLConnection& operator=(const GkIBSQLConnection&);
};
GkIBSQLResult::GkIBSQLResult(
/// transaction handle
isc_tr_handle tr,
/// statement handle
isc_stmt_handle stmt,
/// SELECT type query result
XSQLDA* selectResult
)
: GkSQLResult(false), m_sqlResult(selectResult), m_tr(tr), m_stmt(stmt), m_sqlRow(-1),
m_errorCode(0)
{
if (m_sqlResult != NULL) {
m_numRows = 100000;
m_numFields = m_sqlResult->sqld;
for (int i = 0; i < m_sqlResult->sqld; ++i) {
m_sqlResult->sqlvar[i].sqlind = new short;
if ((m_sqlResult->sqlvar[i].sqltype & ~1L) == SQL_VARYING)
m_sqlResult->sqlvar[i].sqldata = new char[m_sqlResult->sqlvar[i].sqllen + 2];
else
m_sqlResult->sqlvar[i].sqldata = new char[m_sqlResult->sqlvar[i].sqllen];
}
} else
m_queryError = true;
m_selectType = (m_numFields != 0);
}
GkIBSQLResult::GkIBSQLResult(
/// Firebird specific error code
unsigned int errorCode,
/// Firebird specific error message text
const char* errorMsg,
/// transaction handle
isc_tr_handle tr,
/// statement handle
isc_stmt_handle stmt,
/// SELECT type query result
XSQLDA* selectResult
)
: GkSQLResult(true), m_sqlResult(selectResult), m_tr(tr), m_stmt(stmt), m_sqlRow(-1),
m_errorCode(errorCode), m_errorMessage(errorMsg)
{
}
GkIBSQLResult::~GkIBSQLResult()
{
ISC_STATUS status[20];
if (m_stmt != NULL)
isc_dsql_free_statement(status, &m_stmt, DSQL_drop);
if (m_sqlResult != NULL) {
for (int i = 0; i < m_sqlResult->sqld; ++i) {
if (m_sqlResult->sqlvar[i].sqldata != NULL)
delete [] m_sqlResult->sqlvar[i].sqldata;
if (m_sqlResult->sqlvar[i].sqlind != NULL)
delete m_sqlResult->sqlvar[i].sqlind;
}
delete [] reinterpret_cast<char*>(m_sqlResult);
}
if (m_tr != NULL)
if (m_queryError)
isc_rollback_transaction(status, &m_tr);
else
isc_commit_transaction(status, &m_tr);
}
bool GkIBSQLResult::HasRandomAccess()
{
return false;
}
PString GkIBSQLResult::GetErrorMessage()
{
return m_errorMessage;
}
long GkIBSQLResult::GetErrorCode()
{
return m_errorCode;
}
bool GkIBSQLResult::FetchRow(
/// array to be filled with string representations of the row fields
PStringArray& result
)
{
if (m_sqlResult == NULL || m_numRows <= 0)
return false;
if (m_sqlRow < 0)
m_sqlRow = 0;
if (m_sqlRow >= m_numRows)
return false;
ISC_STATUS retval;
ISC_STATUS status[20];
retval = isc_dsql_fetch(status, &m_stmt, 1, m_sqlResult);
if (status[0] == 1 && status[1] != 0) {
m_numRows = m_sqlRow;
if (retval != 100) {
long errcode = isc_sqlcode(status);
char errormsg[512];
if (errcode == -999) {
errcode = status[1];
long *pvector = status;
errormsg[isc_interprete(errormsg, &pvector)] = 0;
} else {
strcpy(errormsg, "SQL:");
isc_sql_interprete(static_cast<short>(errcode), errormsg + 4, 512 - 4);
}
PTRACE(2, "Firebird\tFailed to fetch query row (" << errcode
<< "): " << errormsg
);
}
return false;
}
result.SetSize(m_sqlResult->sqld);
for (PINDEX i = 0; i < m_sqlResult->sqld; ++i)
result[i] = XSQLVARToPString(&(m_sqlResult->sqlvar[i]));
m_sqlRow++;
return true;
}
bool GkIBSQLResult::FetchRow(
/// array to be filled with string representations of the row fields
ResultRow& result
)
{
if (m_sqlResult == NULL || m_numRows <= 0)
return false;
if (m_sqlRow < 0)
m_sqlRow = 0;
if (m_sqlRow >= m_numRows)
return false;
ISC_STATUS retval;
ISC_STATUS status[20];
retval = isc_dsql_fetch(status, &m_stmt, 1, m_sqlResult);
if (status[0] == 1 && status[1] != 0) {
m_numRows = m_sqlRow;
if (retval != 100) {
long errcode = isc_sqlcode(status);
char errormsg[512];
if (errcode == -999) {
errcode = status[1];
long *pvector = status;
errormsg[isc_interprete(errormsg, &pvector)] = 0;
} else {
strcpy(errormsg, "SQL:");
isc_sql_interprete(static_cast<short>(errcode), errormsg + 4, 512 - 4);
}
PTRACE(2, "Firebird\tFailed to fetch query row (" << errcode
<< "): " << errormsg
);
}
return false;
}
result.resize(m_numFields);
for (PINDEX i = 0; i < m_numFields; i++) {
result[i].first = XSQLVARToPString(&(m_sqlResult->sqlvar[i]));
result[i].second = PString(m_sqlResult->sqlvar[i].aliasname, m_sqlResult->sqlvar[i].aliasname_length);
}
m_sqlRow++;
return true;
}
bool GkIBSQLResult::IsNullField(
/// index of the column to check
long fieldOffset
)
{
return m_sqlResult == NULL || m_sqlRow < 0 || m_sqlRow >= m_numRows
|| fieldOffset < 0 || fieldOffset >= m_numFields
|| ((m_sqlResult->sqlvar[fieldOffset].sqltype & 1) && *(m_sqlResult->sqlvar[fieldOffset].sqlind) == -1);
}
bool GkIBSQLResult::FetchRow(
/// array to be filled with string representations of the row fields
PStringArray& result,
/// index (0 based) of the row to fetch
long rowOffset
)
{
return false;
}
bool GkIBSQLResult::FetchRow(
/// array to be filled with string representations of the row fields
ResultRow& result,
/// index (0 based) of the row to fetch
long rowOffset
)
{
return false;
}
bool GkIBSQLResult::IsNullField(
/// index of the column to check
long fieldOffset,
/// index (0 based) of the row to check
long rowOffset
)
{
return false;
}
GkIBSQLConnection::GkIBSQLConnection(
/// name to use in the log
const char* name
) : GkSQLConnection(name)
{
}
GkIBSQLConnection::~GkIBSQLConnection()
{
}
GkIBSQLConnection::IBSQLConnWrapper::~IBSQLConnWrapper()
{
ISC_STATUS status[20];
isc_detach_database(status, &m_conn);
}
GkSQLConnection::SQLConnPtr GkIBSQLConnection::CreateNewConnection(
/// unique identifier for this connection
int id
)
{
unsigned dpb_offset = 0;
std::vector<char> dpb(1);
dpb[dpb_offset++] = isc_dpb_version1;
if (!m_username) {
dpb.resize(dpb.size() + 2 + m_username.GetLength());
dpb[dpb_offset++] = isc_dpb_user_name;
dpb[dpb_offset++] = m_username.GetLength();
memcpy(&(dpb[dpb_offset]), (const char*)m_username, m_username.GetLength());
dpb_offset += m_username.GetLength();
}
if (!m_password) {
dpb.resize(dpb.size() + 2 + m_password.GetLength());
dpb[dpb_offset++] = isc_dpb_password;
dpb[dpb_offset++] = m_password.GetLength();
memcpy(&(dpb[dpb_offset]), (const char*)m_password, m_password.GetLength());
dpb_offset += m_password.GetLength();
}
ISC_STATUS status[20];
isc_db_handle conn = NULL;
std::string dbname(m_database);
if (!m_host) {
dbname.insert(0, ":");
dbname.insert(0, m_host);
}
isc_attach_database(status, 0, const_cast<char*>(dbname.c_str()), &conn, dpb_offset, &(dpb[0]));
if (status[0] == 1 && status[1] != 0) {
long *pvector = status;
char errormsg[512];
errormsg[isc_interprete(errormsg, &pvector)] = 0;
PTRACE(2, GetName() << "\tFirebird connection to " << m_username << '@' << dbname
<< " failed (isc_attach_database failed): " << errormsg
);
return NULL;
}
PTRACE(5, GetName() << "\tFirebird connection to " << m_username << '@' << dbname
<< " established successfully"
);
return new IBSQLConnWrapper(id, dbname, conn);
}
GkSQLResult* GkIBSQLConnection::ExecuteQuery(
/// SQL connection to use for query execution
GkSQLConnection::SQLConnPtr con,
/// query string
const char* queryStr,
/// maximum time (ms) for the query execution, -1 means infinite
long /*timeout*/
)
{
isc_db_handle conn = ((IBSQLConnWrapper*)con)->m_conn;
char errormsg[512];
ISC_STATUS status[20];
isc_tr_handle tr = NULL;
isc_stmt_handle stmt = NULL;
isc_start_transaction(status, &tr, 1, &conn, 0, NULL);
if (status[0] == 1 && status[1] != 0) {
long *pvector = status;
char errormsg[512];
errormsg[isc_interprete(errormsg, &pvector)] = 0;
return new GkIBSQLResult(status[1], errormsg);
}
isc_dsql_allocate_statement(status, &conn, &stmt);
if (status[0] == 1 && status[1] != 0) {
long errorcode = isc_sqlcode(status);
if (errorcode == -999) {
errorcode = status[1];
long *pvector = status;
errormsg[isc_interprete(errormsg, &pvector)] = 0;
} else {
strcpy(errormsg, "SQL:");
isc_sql_interprete(static_cast<short>(errorcode), errormsg, 512 - 4);
}
return new GkIBSQLResult(errorcode, errormsg, tr);
}
int numcols = 1;
XSQLDA *result = reinterpret_cast<XSQLDA*>(new char[XSQLDA_LENGTH(numcols)]);
memset(result, 0, XSQLDA_LENGTH(numcols));
result->version = SQLDA_VERSION1;
result->sqln = numcols;
isc_dsql_prepare(status, &tr, &stmt, 0, const_cast<char*>(queryStr), SQL_DIALECT_CURRENT, result);
if (status[0] == 1 && status[1] != 0) {
long errorcode = isc_sqlcode(status);
if (errorcode == -999) {
errorcode = status[1];
long *pvector = status;
errormsg[isc_interprete(errormsg, &pvector)] = 0;
} else {
strcpy(errormsg, "SQL:");
isc_sql_interprete(static_cast<short>(errorcode), errormsg, 512 - 4);
}
return new GkIBSQLResult(errorcode, errormsg, tr, stmt);
}
if (result->sqld > result->sqln) {
numcols = result->sqld;
delete [] reinterpret_cast<char*>(result);
result = reinterpret_cast<XSQLDA*>(new char[XSQLDA_LENGTH(numcols)]);
memset(result, 0, XSQLDA_LENGTH(numcols));
result->version = SQLDA_VERSION1;
result->sqln = numcols;
isc_dsql_describe(status, &stmt, SQLDA_VERSION1, result);
if (status[0] == 1 && status[1] != 0) {
long errorcode = isc_sqlcode(status);
if (errorcode == -999) {
errorcode = status[1];
long *pvector = status;
errormsg[isc_interprete(errormsg, &pvector)] = 0;
} else {
strcpy(errormsg, "SQL:");
isc_sql_interprete(static_cast<short>(errorcode), errormsg, 512 - 4);
}
delete [] reinterpret_cast<char*>(result);
return new GkIBSQLResult(errorcode, errormsg, tr, stmt);
}
}
isc_dsql_execute(status, &tr, &stmt, SQLDA_VERSION1, NULL);
if (status[0] == 1 && status[1] != 0) {
long errorcode = isc_sqlcode(status);
if (errorcode == -999) {
errorcode = status[1];
long *pvector = status;
errormsg[isc_interprete(errormsg, &pvector)] = 0;
} else {
strcpy(errormsg, "SQL:");
isc_sql_interprete(static_cast<short>(errorcode), errormsg, 512 - 4);
}
delete [] reinterpret_cast<char*>(result);
return new GkIBSQLResult(errorcode, errormsg, tr, stmt);
}
return new GkIBSQLResult(tr, stmt, result);
}
PString GkIBSQLConnection::EscapeString(
/// SQL connection to get escaping parameters from
SQLConnPtr conn,
/// string to be escaped
const char* str
)
{
return str;
}
namespace {
GkSQLCreator<GkIBSQLConnection> IBSQLCreator("Firebird");
}
#endif /* HAS_FIREBIRD */
syntax highlighted by Code2HTML, v. 0.9.1