gambas-source-code/gb.db.odbc/src/main.c
Benoît Minisini 72971105ef Support for new 'Connection.FullVersion' property in database drivers. Tells if 'RETURNING' keyword is implemented.
[GB.DB.MYSQL]
* NEW: Support for new 'Connection.FullVersion' property.
* NEW: Tells if 'RETURNING' keyword is implemented.

[GB.DB.ODBC]
* NEW: Support for new 'Connection.FullVersion' property.
* NEW: Tells if 'RETURNING' keyword is implemented.

[GB.DB.POSTGRESQL]
* NEW: Support for new 'Connection.FullVersion' property.
* NEW: Tells if 'RETURNING' keyword is implemented.

[GB.DB.SQLITE2]
* NEW: Support for new 'Connection.FullVersion' property.
* NEW: Tells if 'RETURNING' keyword is implemented.

[GB.DB.SQLITE3]
* NEW: Support for new 'Connection.FullVersion' property.
* NEW: Tells if 'RETURNING' keyword is implemented.
2023-07-14 12:08:51 +02:00

3313 lines
82 KiB
C

/***************************************************************************
main.c
(c) 2004-2007 Andrea Bortolan <andrea_bortolan@yahoo.it>
(c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>
(c) 2015-2021 zxMarce <d4t4full@gmail.com>
This program 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 2, or (at your option)
any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
***************************************************************************/
#define __MAIN_C
//#define ODBC_TYPE
//#define ODBC_DEBUG
//#define DEBUG_ODBC
//#ifndef ODBC_DEBUG_HEADER
//#define ODBC_DEBUG_HEADER
//#endif
//#define ODBC_DEBUG_MEM
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
#ifdef HAVE_SYS_TYPES_H
#undef HAVE_SYS_TYPES_H
#endif
#ifdef HAVE_UNISTD_H
#undef HAVE_UNISTD_H
#endif
#include "gb.db.proto.h"
#include "main.h"
// define unixODBC ONLY constants if they are not available
#ifndef SQLTables_TABLE_NAME
#define SQLTables_TABLE_NAME 3
#define SQLTables_TABLE_TYPE 4
#define SQLTables_REMARKS 5
#define SQLColumns_COLUMN_NAME 4
#define SQLColumns_COLUMN_SIZE 7
#define SQLColumns_SQL_DATA_TYPE 14
#endif
GB_INTERFACE GB EXPORT;
DB_INTERFACE DB EXPORT;
static char _buffer[32];
//static char _nullbuffer[10];
static DB_DRIVER _driver;
typedef struct
{
//SQLHENV odbcEnvHandle;
//SQLHDBC odbcHandle;
SQLHANDLE odbcEnvHandle; //ODBC environment handle
SQLHANDLE odbcHandle; //ODBC connection handle
SQLUSMALLINT drvrCanFetchScroll; //Flag
char *dsn_name; //DSN name
char *user_name; //Logged-in user name
}
ODBC_CONN;
typedef struct
{
char *name;
int id;
SQLSMALLINT type;
char *data;
int len;
}
ODBC_FIELD;
typedef struct
{
SQLHSTMT odbcStatHandle;
SQLUSMALLINT Function_exist; //Does the Driver support SQLFetchScroll ?
SQLUSMALLINT Cursor_Scrollable; //Is it possible to set a Scrollable cursor ?
ODBC_FIELD *fields;
SQLLEN count;
}
ODBC_RESULT;
typedef struct
{
char *tablename;
struct ODBC_TABLES *next;
}
ODBC_TABLES;
/*
* zxMarce: Routine to print to console the errors from the ODBC subsystem.
* Adapted to obey Gambas' DB.IsDebug().
* Mostly from http://www.easysoft.com/developer/interfaces/odbc/diagnostics_error_status_codes.html
*/
static void reportODBCError(const char *fn, SQLHANDLE handle, SQLSMALLINT type)
{
SQLINTEGER i = 0;
SQLINTEGER native;
SQLTCHAR state[7];
SQLTCHAR text[256];
SQLSMALLINT len;
SQLRETURN ret;
if (DB.IsDebug())
{
DB.Debug("gb.db.odbc","ERROR: %s", fn);
do
{
ret = SQLGetDiagRec(type, handle, ++i, state, &native, text, sizeof(text), &len);
if (SQL_SUCCEEDED(ret))
DB.Debug("gb.db.odbc", "%d:%s:%d:%s", (int)i, (char *)state, (int)native, (char *)text);
}
while (ret == SQL_SUCCESS);
}
}
/*
* 20170623 zxMarce: Routine to publish underlying ODBC error(s) as a normal
* Gambas error. Based on reportODBCError above, but does NOT obey DB.IsDebug(),
* as this is intended as a component-centric "official" error report routine.
* It is the intention to replace most GB.Error() calls with this routine.
* Thanks to Tobias Boege for native GB string handling tips!
*/
void throwODBCError(const char *failedODBCFunctionName,
SQLHANDLE handle,
SQLSMALLINT handleType
)
{
SQLINTEGER i = 0;
SQLINTEGER native;
SQLTCHAR state[7];
SQLTCHAR text[512];
char *errorText = NULL; //GB.NewString("gb.db.odbc: ", 12);
SQLSMALLINT len;
SQLRETURN ret;
errorText = GB.AddString(errorText, (char *)failedODBCFunctionName, 0),
errorText = GB.AddString(errorText, " failed:", 0);
do
{
ret = SQLGetDiagRec(handleType, handle, ++i, state, &native, text, sizeof(text), &len);
if (SQL_SUCCEEDED(ret))
{
errorText = GB.AddString(errorText, "\n", 1);
errorText = GB.AddString(errorText, (char *)state, 0);
errorText = GB.AddString(errorText, (char *)text, len);
}
}
while (ret == SQL_SUCCESS);
GB.Error(errorText);
GB.FreeString(&errorText);
}
/* zxMarce: This is one way -hope there's an easier one- to retrieve a rowset
* count for SELECT statements. Four steps (must have an scrollable cursor!):
* 1- Remember the current row.
* 2- Seek down to the last row in the rowset
* 3- Get the last row's index (recno)
* 4- Seek back to wherever we were at in step 1
* 20161110 zxMarce: Ok, it did not work that OK for Firebird; it looks like
* the FB driver returns one-less than the record count (record count seems to
* be zero-based), so we will instead do as follows, if we have a scrollable
* recordset:
* 1- Remember the current row.
* 2- Seek up to the first row in the rowset
* 3- Get the first row's index (firstRecNo)
* 4- Seek down to the last row in the rowset
* 5- Get the last row's index (lastRecNo)
* 6- Seek back to wherever we were at in step 1
* 7- Return (lastRecNo - firstRecNo + 1).
*/
int GetRecordCount(SQLHANDLE stmtHandle, SQLINTEGER cursorScrollable)
{
SQLRETURN retcode; //ODBC call return values
int formerRecIdx = -1; //Where we were when this all started.
SQLINTEGER myRecCnt = -1; //Default for when there's no cursor.
SQLINTEGER firstRecNo = 0; //20161111 holder for 1st recno.
SQLINTEGER lastRecNo = 0; //20161111 holder for last recno.
char mssg[128]; //Error reporting text.
//Make sure the statement has a cursor
if (!(stmtHandle && (cursorScrollable == SQL_TRUE)))
{
DB.Debug("gb.db.odbc", "GetRecordCount(): Cannot count records!");
return ((int) myRecCnt);
}
//Tell ODBC we won't be actually reading data (speeds process up).
//SQL_ATTR_RETRIEVE_DATA = [SQL_RD_ON] | SQL_RD_OFF
retcode = SQLSetStmtAttr(stmtHandle, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_OFF, 0);
if (!SQL_SUCCEEDED(retcode))
{
reportODBCError("SQLSetStmtAttr SQL_ATTR_RETRIEVE_DATA",
stmtHandle,
SQL_HANDLE_STMT);
}
//Fetch current row's index so we can return to it when done.
retcode = SQLGetStmtAttr(stmtHandle, SQL_ATTR_ROW_NUMBER, &formerRecIdx, 0, 0);
if (!SQL_SUCCEEDED(retcode))
{
reportODBCError("SQLGetStmtAttr SQL_ATTR_ROW_NUMBER",
stmtHandle,
SQL_HANDLE_STMT);
}
//Make sure the statement has a cursor
if (formerRecIdx < 0)
{
DB.Debug("gb.db.odbc", "GetRecordCount: Current record returned %d, returning -1 as count", formerRecIdx);
goto __RETURN_COUNT;
}
//Try to get (back?) to the first record, abort if not possible.
retcode = SQLFetchScroll(stmtHandle, SQL_FETCH_FIRST, (SQLINTEGER) 0);
if (!SQL_SUCCEEDED(retcode))
{
reportODBCError("SQLFetchScroll SQL_FETCH_FIRST", stmtHandle, SQL_HANDLE_STMT);
retcode = SQLSetStmtAttr(stmtHandle, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_ON, 0);
return ((int) myRecCnt);
} else {
//Fetch the first record's index
retcode = SQLGetStmtAttr(stmtHandle, SQL_ATTR_ROW_NUMBER, &firstRecNo, 0, 0);
if (SQL_SUCCEEDED(retcode))
{
//Inform first recno if in Debug mode and carry on
DB.Debug("gb.db.odbc", "GetRecordCount: First recno=%d", (int) firstRecNo);
} else {
//Could not fetch the first recno: Abort!
reportODBCError("SQLFetchScroll SQL_ATTR_ROW_NUMBER (first recno)", stmtHandle, SQL_HANDLE_STMT);
retcode = SQLSetStmtAttr(stmtHandle, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_ON, 0);
return ((int) myRecCnt);
}
}
//Advance the cursor to the last record.
retcode = SQLFetchScroll(stmtHandle, SQL_FETCH_LAST, (SQLINTEGER) 0);
if (SQL_SUCCEEDED(retcode))
{
//Fetch the last record's index
retcode = SQLGetStmtAttr(stmtHandle, SQL_ATTR_ROW_NUMBER, &lastRecNo, 0, 0);
if (SQL_SUCCEEDED(retcode))
{
//Set ret value
DB.Debug("gb.db.odbc", "GetRecordCount: Last recno=%d", (int) lastRecNo);
} else {
reportODBCError("SQLGetStmtAttr SQL_ATTR_ROW_NUMBER (last recno)", stmtHandle, SQL_HANDLE_STMT);
}
//Return cursor to original row.
retcode = SQLFetchScroll(stmtHandle, SQL_FETCH_ABSOLUTE, (SQLINTEGER) formerRecIdx);
//Since we have set the "do not read data" statement attribute, this call (may) return
//code 100 (SQL_NO_DATA) but that's OK for our purposes of just counting rows.
if (!SQL_SUCCEEDED(retcode) && (retcode != SQL_NO_DATA))
{
snprintf(mssg, sizeof(mssg), "SQLFetchScroll SQL_FETCH_ABSOLUTE (code %d) (rec %d)",
(int)retcode, formerRecIdx);
reportODBCError(mssg, stmtHandle, SQL_HANDLE_STMT);
}
} else {
reportODBCError("SQLFetchScroll SQL_FETCH_LAST", stmtHandle, SQL_HANDLE_STMT);
}
myRecCnt = (lastRecNo - firstRecNo + 1);
DB.Debug("gb.db.odbc", "GetRecordCount: Record count=%d", (int) myRecCnt);
__RETURN_COUNT:
//Tell ODBC we will be reading data now.
//SQL_ATTR_RETRIEVE_DATA = [SQL_RD_ON] | SQL_RD_OFF
retcode = SQLSetStmtAttr(stmtHandle, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_ON, 0);
if (!SQL_SUCCEEDED(retcode))
{
reportODBCError("SQLSetStmtAttr SQL_ATTR_RETRIEVE_DATA", stmtHandle, SQL_HANDLE_STMT);
}
return ((int) myRecCnt);
}
/* BM: Replaces malloc() and free() by GB.Alloc() and GB.Free() */
static void *my_malloc(size_t size)
{
void *ptr;
GB.Alloc(&ptr, (int)size);
return ptr;
}
static void my_free(void *_ptr)
{
GB.Free(POINTER(&_ptr));
}
#define malloc(_size) my_malloc(_size)
#define free(_ptr) my_free(_ptr)
#if 0
static void quote(char *data, int len, DB_FORMAT_CALLBACK add)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tquote\n");
fflush(stderr);
#endif
int i;
unsigned char c;
//char buffer[8];
(*add)("'", 1);
for (i = 0; i < len; i++)
{
c = (unsigned char)data[i];
if (c == '\\')
(*add)("\\\\", 2);
else if (c == '\'')
(*add)("''", 2);
else
(*add)((char *)&c, 1);
}
(*add)("'", 1);
}
#endif
/* internal function to quote a value stored as a blob */
static void quote_blob(char *data, int len, DB_FORMAT_CALLBACK add)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tquote_blob\n");
fflush(stderr);
#endif
int i;
unsigned char c;
//char buffer[8];
(*add)("'", 1);
for (i = 0; i < len; i++)
{
c = (unsigned char)data[i];
if (c == '\\')
(*add)("\\\\\\\\", 4);
else if (c == '\'')
(*add)("''", 2);
else if (c == 0)
(*add)("\\\\000", 5);
/*else if (c < 32 || c == 127)
{
int n = sprintf(buffer, "\\\\%03o", c);
(*add)(buffer, n);
}*/
else
(*add)((char *)&c, 1);
}
(*add)("'", 1);
}
/* internal function to unquote a value stored as a string */
#if 0
static int unquote(char *data, int len, DB_FORMAT_CALLBACK add)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tunquote\n");
fflush(stderr);
#endif
int i;
char c;
if (!data || *data != '\'')
return TRUE;
for (i = 1;; i++)
{
c = data[i];
if (c == '\'')
break;
if (c == '\\')
i++;
(*add)(&data[i], 1);
}
return FALSE;
}
#endif
/* internal function to unquote a value stored as a blob */
#if 0
static int unquote_blob(char *data, int len, DB_FORMAT_CALLBACK add)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tunquote_blob\n");
fflush(stderr);
#endif
int i;
char c;
for (i = 0; i < len; i++)
{
c = data[i];
if (c == '\\')
{
i++;
c = data[i];
if (c >= '0' && c <= '9' && i < (len - 2))
{
c = ((data[i] - '0') << 6) + ((data[i + 1] - '0') << 3) + (data[i + 2] - '0');
i += 2;
(*add)(&c, 1);
continue;
}
}
(*add)(&data[i], 1);
}
return FALSE;
}
#endif
/*
Internal function to check if the .Host property is actually an ODBC connection string.
ODBC ConnStrings have one or more "ParamName=ParamValue" pairs, delimited by semicolons.
The function helps the component know whether to call SQLConnect (when a host/DSN),
or SQLDriverConnect (when a ConnString).
I know there are C functions to locate CHARs in a CHAR[], but I'm not well versed
in C and less in what's available in a Gambas component, so I stuck to whatever other
routines use in this module.
zxMarce, 20150826
*/
static bool is_host_a_connstring(const char *host_or_cs)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tis_host_a_connstring: '%s'\n", host_or_cs);
fflush(stderr);
#endif
int counter;
char curChar;
if (!host_or_cs)
return FALSE;
for (counter = 0; counter < strlen(host_or_cs); counter++)
{
curChar = host_or_cs[counter];
if ((curChar == '=') || (curChar == ';'))
return TRUE;
}
return FALSE;
}
/* Internal function to convert a database type into a Gambas type */
static GB_TYPE conv_type(int type)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tconv_type: Field type: %d\n",type);
fflush(stderr);
#endif
switch (type)
{
case SQL_TINYINT:
return GB_T_BOOLEAN;
case SQL_DECIMAL:
case SQL_INTEGER:
case SQL_SMALLINT:
return GB_T_INTEGER;
case SQL_BIGINT:
// New datatype bigint 64 bits
return GB_T_LONG;
case SQL_NUMERIC:
case SQL_FLOAT:
case SQL_REAL:
case SQL_DOUBLE:
return GB_T_FLOAT;
case SQL_DATETIME:
case SQL_TYPE_DATE:
case SQL_TYPE_TIME:
case SQL_TYPE_TIMESTAMP:
return GB_T_DATE;
case SQL_LONGVARCHAR:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
// Data type for BLOB
return DB_T_BLOB;
case SQL_CHAR:
default:
return GB_T_STRING;
}
}
/* Internal function to convert a database value into a Gambas variant value */
static void conv_data(char *data, int len, GB_VARIANT_VALUE * val, int type)
{
GB_VALUE conv;
switch (type)
{
//case SQL_NUMERIC:
case SQL_INTEGER:
case SQL_SMALLINT:
val->type = GB_T_INTEGER;
if (GB.NumberFromString(GB_NB_READ_INTEGER, data, len, &conv))
val->value._integer = 0;
else
val->value._integer = conv._integer.value;
break;
case SQL_DECIMAL:
case SQL_NUMERIC:
case SQL_FLOAT:
case SQL_REAL:
case SQL_DOUBLE:
val->type = GB_T_FLOAT;
if (GB.NumberFromString(GB_NB_READ_FLOAT | GB_NB_LOCAL, data, len, &conv) && GB.NumberFromString(GB_NB_READ_FLOAT, data, len, &conv))
{
fprintf(stderr, "gb.db.odbc: unable to convert float: %.*s\n", len, data);
val->value._float = 0;
}
else
val->value._float = conv._float.value;
break;
case SQL_BIGINT: // Data type bigint 64 bits
val->type = GB_T_LONG;
if (GB.NumberFromString(GB_NB_READ_LONG, data, len, &conv))
val->value._long = 0;
else
val->value._long = conv._long.value;
break;
case SQL_LONGVARCHAR:
case SQL_VARBINARY:
case SQL_LONGVARBINARY: // Data type BLOB
// The BLOB are read by the blob_read() driver function
// You must set NULL there.
val->type = GB_T_NULL;
break;
case SQL_TYPE_DATE:
case SQL_TYPE_TIME:
case SQL_TYPE_TIMESTAMP:
case SQL_DATETIME: // Data type for Time
{
GB_DATE_SERIAL date = { 0 };
double sec;
int n;
conv._date.value.date = conv._date.value.time = 0;
if (len > 0)
{
n = sscanf(data, "%4d-%2d-%2d %2d:%2d:%lf", &date.year, &date.month, &date.day, &date.hour, &date.min, &sec);
if (n >= 3)
{
bool bc = (len > 3) && (strcmp(&data[len - 2], "BC") == 0);
if (n == 6)
{
date.sec = (short)sec;
date.msec = (short)((sec - date.sec) * 1000 + 0.5);
}
if (bc)
date.year = (-date.year);
GB.MakeDate(&date, (GB_DATE *)&conv);
}
else if (GB.DateFromString(data, len, &conv, TRUE))
{
fprintf(stderr, "gb.db.odbc: unable to convert date: %.*s\n", len, data);
}
}
val->type = GB_T_DATE;
val->value._date.date = conv._date.value.date;
val->value._date.time = conv._date.value.time;
break;
}
case SQL_TINYINT:
val->type = GB_T_BOOLEAN;
val->value._boolean = atoi(data) ? -1 : 0;
break;
case SQL_CHAR:
default:
val->type = GB_T_CSTRING;
val->value._string = data;
break;
}
}
/*****************************************************************************
get_quote()
Returns the character used for quoting object names.
*****************************************************************************/
static const char *get_quote(void)
{
return QUOTE_STRING;
}
/*****************************************************************************
zxMarce: Added 20160928 to retrieve the connected Database (or 'Catalog')
name directly from unixODBC/driver.
Actually, this routine *should* be called once after every operation,
because SPs or even plaintext queries could change the Catalog (for example
by using the "USE <newcatalog>" command), but we don't have access to a
DB_DESC structure from the query-running code.
*****************************************************************************/
void GetConnectedDBName(DB_DESC *desc, ODBC_CONN *odbc)
{
SQLRETURN retcode;
SQLINTEGER charsNeeded = 0;
char *dbName;
/*zxMarce: Attribute to fetch is SQL_ATTR_CURRENT_CATALOG
We call the function first with a NULL buffer pointer so as to
retrieve the necessary buffer size.
*/
retcode = SQLGetConnectAttrA(odbc->odbcHandle, SQL_ATTR_CURRENT_CATALOG,
NULL, (SQLINTEGER) 0,
(SQLINTEGER *) &charsNeeded
);
if (SQL_SUCCEEDED(retcode))
{
dbName = GB.NewString(NULL, charsNeeded);
/*zxMarce: We call the function again, this time specifying a
hopefully big enough buffer for storing the catalog name.
*/
retcode = SQLGetConnectAttr(odbc->odbcHandle, SQL_ATTR_CURRENT_CATALOG,
dbName, charsNeeded,
&charsNeeded
);
if (SQL_SUCCEEDED(retcode))
{
GB.FreeString(&desc->name);
desc->name = dbName;
}
}
if (desc->name)
DB.Debug("gb.db.odbc", "GetConnectedDBName: desc->name (%d chars): '%s'", (int)charsNeeded, desc->name);
else
DB.Debug("gb.db.odbc", "GetConnectedDBName: desc->name: NULL");
}
/*****************************************************************************
open_database()
Connect to a database.
<desc> points at a structure describing each connection parameter.
<db> points at the DB_DATABASE structure that must be initialized.
This function must return TRUE if the connection has failed.
The name of the database can be NULL, meaning a default database.
*****************************************************************************/
static int open_database(DB_DESC *desc, DB_DATABASE *db)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\topen_database\n");
fflush(stderr);
#endif
//int V_OD_erg;
SQLRETURN retcode;
ODBC_CONN *odbc;
bool hostIsAConnString;
char *host;
char *user;
host = desc->host;
if (!host)
host = "";
user = desc->user;
if (!user)
user = "";
hostIsAConnString = is_host_a_connstring(host);
/* Allocate the ODBC handle */
odbc = (ODBC_CONN *)malloc(sizeof(ODBC_CONN));
odbc->odbcHandle = NULL;
odbc->odbcEnvHandle = NULL;
odbc->dsn_name = NULL;
/* Allocate the Environment handle */
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &odbc->odbcEnvHandle);
if (!SQL_SUCCEEDED(retcode))
{
free(odbc);
GB.Error("Unable to allocate ODBC environment handle");
return TRUE;
}
/* Set the Envoronment attributes */
retcode = SQLSetEnvAttr(odbc->odbcEnvHandle, SQL_ATTR_ODBC_VERSION,(void *) SQL_OV_ODBC3, 0);
if (!SQL_SUCCEEDED(retcode))
{
SQLFreeHandle(SQL_HANDLE_ENV, odbc->odbcEnvHandle);
free(odbc);
GB.Error("Unable to set ODBC environment attributes");
return TRUE;
}
/* Allocate the Database Connection handle */
retcode = SQLAllocHandle(SQL_HANDLE_DBC, odbc->odbcEnvHandle, &odbc->odbcHandle);
if (!SQL_SUCCEEDED(retcode))
{
SQLFreeHandle(SQL_HANDLE_ENV, odbc->odbcEnvHandle);
free(odbc);
GB.Error("Unable to allocate ODBC database handle");
return TRUE;
}
/* 20170818 zxMarce: The following timeout was incorrectly set. The right timeout is the CONNECT
* timeout. The LOGIN timeout is actually used once the connection is established. Also took the
* opportunity to make sure ODBC uses cursors either from the Driver or the Driver Manager, thus
* almost ensuring cursors are available.
*/
//SQLSetConnectAttr(odbc->odbcHandle, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)(intptr_t)db->timeout, 0);
SQLSetConnectAttr(odbc->odbcHandle, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER)(intptr_t)db->timeout, 0);
/*
* 20210404: Watch out, by using SQL_CUR_USE_IF_NEEDED, if the driver does not provide cursors,
* unixODBC seems to need bound columns. This has the nasty side effect to raise SQL errors. Do
* not yet know how to workaround this.
* The SQL_CUR_USE_IF_NEEDED constant tells ODBC to provide its own cursors if the driver doesn't.
*/
//SQLSetConnectAttr(odbc->odbcHandle, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_IF_NEEDED, 0);
SQLSetConnectAttr(odbc->odbcHandle, SQL_ATTR_ODBC_CURSORS, (SQLPOINTER)SQL_CUR_USE_DRIVER, 0);
if (hostIsAConnString)
{
/* zxMarce: Connect to Database (desc->host is an ODBC Connection String) */
retcode = SQLDriverConnect(odbc->odbcHandle, 0, (SQLCHAR *)host, SQL_NTS, 0, 0, 0, SQL_DRIVER_NOPROMPT);
/* The last three zero params in the call above can be used to retrieve the actual connstring used,
should unixODBC "complete" the passed ConnString with data from a matching defined DSN. Not
doing it here, but maybe useful to fill in the other Gambas Connection object properties (user,
pass, etc) after parsing it. Also note that the ConnString MAY refer to a DSN, and include
user/pass, if desired.
Example - ODBC-ConnString for FreeTDS, all one line (must assign this to the Connection.Host
property in Gambas code and then call Connection.Open):
"Driver=FreeTDS;
TDS_Version=<useNormally'7.2'>;
Server=<serverNameOrIP>;
Port=<serverTcpPort>;
Database=<defaultDatabase>"
UId=<userName>;
Pwd=<password>;
*/
} else {
/* Connect to Database (desc->host is an ODBC Data Source Name) */
retcode = SQLConnect(odbc->odbcHandle, (SQLCHAR *)host, SQL_NTS, (SQLCHAR *)user, SQL_NTS, (SQLCHAR *) desc->password, SQL_NTS);
}
//zxMarce: Must bail out NOW if failed to connect, or nonsense errors will appear.
if (!SQL_SUCCEEDED(retcode))
{
throwODBCError((hostIsAConnString ? "SQLDriverConnect" : "SQLConnect"), odbc->odbcHandle, SQL_HANDLE_DBC);
free(odbc);
//GB.Error("Error connecting to database");
return TRUE;
}
retcode = SQLSetConnectAttr(odbc->odbcHandle, SQL_ATTR_AUTOCOMMIT, (void *) SQL_AUTOCOMMIT_ON, SQL_NTS);
/* desc->name is a pointer intended to point to the database name ('catalog', in
* MSSQL parlance) to which we are connected. But that is NOT the actual database
* name when we use a connstring. When we use a connstring, the '.Database' connection
* property would then have the whole connection string, so we tweak it here to make it
* point to the actual database (catalog?) name.
*/
//odbc->dsn_name = malloc(sizeof(char) * strlen(host));
//strcpy(odbc->dsn_name, host);
GetConnectedDBName(desc, odbc);
odbc->user_name = malloc(sizeof(char) * strlen(user));
strcpy(odbc->user_name, user);
// TODO: Use SQLGetInfo() to retrieve the DBMS version string
db->version = 3;
db->full_version = GB.NewZeroString("3");
retcode = SQLGetFunctions(odbc->odbcHandle, SQL_API_SQLFETCHSCROLL, &odbc->drvrCanFetchScroll);
if (!SQL_SUCCEEDED(retcode))
{
throwODBCError("SQLGetFunctions SQL_API_SQLFETCHSCROLL", odbc->odbcHandle, SQL_HANDLE_DBC);
free(odbc);
//GB.Error("Error calling the ODBC SQLGetFunctions API");
return TRUE;
}
/* flags */
db->flags.no_table_type = TRUE;
db->flags.no_seek = (odbc->drvrCanFetchScroll == SQL_FALSE);
db->flags.no_serial = TRUE; // Need to be done!
db->flags.no_blob = FALSE; // Need to be done!
db->flags.no_collation = TRUE;
db->handle = odbc;
return FALSE;
}
/*****************************************************************************
close_database()
Terminates the database connection.
<handle> contains the database handle.
*****************************************************************************/
static void close_database(DB_DATABASE *db)
{
//SQLRETURN retcode;
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tclose_database\n");
fflush(stderr);
#endif
ODBC_CONN *conn = (ODBC_CONN *)db->handle;
//SQLRETURN retcode = 0;
if (conn->odbcHandle)
SQLDisconnect(conn->odbcHandle);
else
GB.Error("ODBC module internal error disconnecting hDBC");
if (conn->odbcHandle)
{
SQLFreeHandle(SQL_HANDLE_DBC, conn->odbcHandle);
conn->odbcHandle = NULL;
}
else
GB.Error("ODBC module internal error freeing hDBC");
if (conn->odbcEnvHandle)
{
SQLFreeHandle(SQL_HANDLE_ENV, conn->odbcEnvHandle);
conn->odbcEnvHandle = NULL;
}
else
GB.Error("ODBC module internal error freeing hENV");
if (conn->dsn_name)
{
free(conn->dsn_name);
conn->dsn_name = NULL;
}
if (conn->user_name)
{
free(conn->user_name);
conn->user_name = NULL;
}
if (conn)
{
free(conn);
db->handle = NULL;
}
}
/*****************************************************************************
get_collations()
Return the available collations as a Gambas string array.
*****************************************************************************/
static GB_ARRAY get_collations(DB_DATABASE *db)
{
return NULL;
}
/*****************************************************************************
format_value()
This function transforms a gambas value into a string value that can
be inserted into a SQL query.
<arg> points to the value.
<add> is a callback called to insert the string into the query.
This function must return TRUE if it translates the value, and FALSE if
it does not.
If the value is not translated, then a default translation is used.
*****************************************************************************/
static int format_value(GB_VALUE * arg, DB_FORMAT_CALLBACK add)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tformat_value\n");
fflush(stderr);
#endif
int l;
GB_DATE_SERIAL *date;
switch (arg->type)
{
case GB_T_BOOLEAN:
/*Note this is likely to go to a tinyint */
if (VALUE((GB_BOOLEAN *) arg))
add("'1'", 3);
else
add("'0'", 3);
return TRUE;
case GB_T_STRING:
case GB_T_CSTRING:
return FALSE; // default
case GB_T_DATE:
date = GB.SplitDate((GB_DATE *) arg);
l = sprintf(_buffer, "'%04d-%02d-%02d-%02d.%02d.%02d",
date->year, date->month, date->day,
date->hour, date->min, date->sec);
add(_buffer, l);
if (date->msec)
{
l = sprintf(_buffer, ".%03d", date->msec);
add(_buffer, l);
}
add("'", 1);
return TRUE;
default:
return FALSE;
}
}
/*****************************************************************************
format_blob()
This function transforms a blob value into a string value that can
be inserted into a SQL query.
<blob> points to the DB_BLOB structure.
<add> is a callback called to insert the string into the query.
*****************************************************************************/
static void format_blob(DB_BLOB *blob, DB_FORMAT_CALLBACK add)
{
// BM: must be done!
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tformat_blob\n");
fflush(stderr);
#endif
quote_blob(blob->data, blob->length, add);
}
static char *query_param[3];
static void query_get_param(int index, char **str, int *len, char quote)
{
//DB.Debug("gb.db.odbc", "query_get_param() invoked.");
if (index > 3)
return;
index--;
*str = query_param[index];
*len = strlen(*str);
if (quote == '\'' || quote == '`')
{
*str = DB.QuoteString(*str, *len, quote);
*len = GB.StringLength(*str);
}
}
/*
Reverse the function call order to make C happy:
2021-04-03 21:03:10.117 gb.db.odbc: query_fill.SQLFetch(): retcode2=-1
2021-04-03 21:03:10.117 gb.db.odbc: query_fill() invoked.
2021-04-03 21:03:10.117 gb.db.odbc: query_make_result() invoked.
2021-04-03 21:03:10.117 gb.db.odbc: query_init() invoked.
2021-04-03 21:03:10.117 gb.db.odbc: cannot do GetRecordCount()!
2021-04-03 21:03:10.111 gb.db.odbc: 0x55dfb1cfbca8: SELECT * FROM Employees
2021-04-03 21:03:10.111 gb.db.odbc: do_query() invoked.
2021-04-03 21:03:10.111 gb.db.odbc: exec_query() invoked.
*/
static int get_num_columns(ODBC_RESULT *result)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tget_num_columns\n");
fflush(stderr);
#endif
SQLSMALLINT colsNum = 0;
SQLRETURN retcode;
retcode = SQLNumResultCols(result->odbcStatHandle, &colsNum);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
GB.Error("ODBC error: Unable to get the number of columns");
return colsNum;
}
/*****************************************************************************
query_fill()
Fill a result buffer with the value of each field of a record.
<db> is the database handle, as returned by open_database()
<result> is the handle of the result.
<pos> is the index of the record in the result.
<buffer> points to an array having one element for each field in the
result.
<next> is a boolean telling if we want the next row.
This function must return DB_OK, DB_ERROR or DB_NO_DATA
This function must use GB.StoreVariant() to store the value in the
buffer.
*****************************************************************************/
static int query_fill(DB_DATABASE *db, DB_RESULT result, int pos, GB_VARIANT_VALUE *buffer, int next)
{
ODBC_RESULT *res = (ODBC_RESULT *) result;
GB_VARIANT value;
SQLRETURN retcode2;
SQLINTEGER i;
ODBC_FIELD *field;
int nResultCols;
SQLLEN len_read;
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tquery_fill result %p,result->odbcStatHandle %p, pos %d\n",res,res->odbcStatHandle,pos);
fflush(stderr);
#endif
/*nResultCols = get_num_columns(res);
if (nResultCols == 0)
return DB_ERROR;*/
nResultCols = GB.Count(res->fields);
DB.Debug("gb.odbc","query_fill: %p: %d (%d)", result, pos, next);
/*current = res->fields;
for (i = 0; i < nResultCols; i++)
{
if(current->next)
current = (ODBC_FIELDS *) current->next;
}*/
if (res->Function_exist == SQL_TRUE) //Does driver support SQLFetchScroll?
{
if (res->Cursor_Scrollable == SQL_TRUE && !next) //Does the query support scrolling?
{
retcode2 = SQLFetchScroll(
res->odbcStatHandle,
SQL_FETCH_ABSOLUTE,
pos + 1
);
}
else
{
retcode2 = SQLFetchScroll(
res->odbcStatHandle,
SQL_FETCH_NEXT,
pos + 1
);
}
}
else
{
/**
* 20210409 zxMarce: The next IF makes sure the query is not
* forced to fetch-back, as the first fetch is issued with:
* (next == false) && (pos == 0)
* Subsequent valid (forward) fetches are issued with:
* (next == true) && (pos != 0)
* Any invalid fetch is detected by comparing against:
* (!next) && (pos != 0)
* which will trigger a descriptive error message.
*/
if ((!next) && (pos != 0))
{
GB.Error("Forward-only result cannot fetch backwards");
return DB_ERROR;
}
retcode2 = SQLFetch(res->odbcStatHandle);
}
if (
(retcode2 != SQL_SUCCESS) &&
(retcode2 != SQL_SUCCESS_WITH_INFO) &&
(retcode2 != SQL_NO_DATA_FOUND) &&
(retcode2 != SQL_NO_DATA)
)
{
DB.Debug("gb.db.odbc","SQLFetchScroll()/SQLFetch() returned code %d, cannot fetch a row.", (int)retcode2);
GB.Error("Unable to fetch row");
return DB_ERROR;
}
if (retcode2 == SQL_NO_DATA_FOUND || retcode2 == SQL_NO_DATA)
return DB_NO_DATA;
for (i = 0; i < nResultCols; i++)
{
field = &res->fields[i];
#if 0
char * fieldata;
SQLULEN precision = 0;
SQLSMALLINT colnamelen = 0, scale = 0, type;
SQLLEN read = 0;
SQLCHAR namebuff[25];
displaySize = 0;
SQLDescribeCol(
res->odbcStatHandle,
i + 1,
namebuff,
sizeof(namebuff),
&colnamelen,
&type,
&precision,
&scale,
NULL
);
fprintf(stderr, "[%d] %s\n", i, namebuff);
/*
* 20210405 zxMarce: The line below asked for the attribute
* SQL_DESC_LENGTH, but it should have asked instead for
* SQL_DESC_DISPLAY_SIZE. It messed up drivers like MDBTools,
* where SQL_DESC_LENGTH seems to return the field name length
* instead of the max field data length. Sources:
* https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlcolattribute-function
* https://www.ibm.com/support/producthub/db2/docs/content/SSEPGG_11.5.0/com.ibm.db2.luw.apdv.cli.doc/doc/r0000569.html
*/
SQLColAttribute(
res->odbcStatHandle,
i + 1,
SQL_DESC_DISPLAY_SIZE,
"",
0,
NULL,
(SQLPOINTER) &displaySize
);
read = 0;
if (displaySize >= strlen((char *) namebuff))
{
displaySize = displaySize + 1;
}
else
{
displaySize = strlen((char *) namebuff) + 1;
}
if (displaySize > 0)
{
if (displaySize < 2)
{
displaySize = 2;
}
if (
(type != SQL_LONGVARCHAR) &&
(type != SQL_VARBINARY) &&
(type != SQL_LONGVARBINARY)
)
{
fieldata = malloc(sizeof(char) * (displaySize));
SQLGetData(
res->odbcStatHandle,
i + 1,
SQL_C_CHAR,
fieldata,
displaySize,
&read
);
}
else
{
//BLOB field, not retrieved here
//the BLOB field hasn't the string terminator
//displaySize = displaySize-1;
displaySize--;
}
current->outlen = displaySize;
}
#endif
value.type = GB_T_VARIANT;
value.value.type = GB_T_NULL;
if (
(field->type != SQL_LONGVARCHAR) &&
(field->type != SQL_VARBINARY) &&
(field->type != SQL_LONGVARBINARY)
)
{
*field->data = 0; // If SQLGetData returns nothing
len_read = 0;
if (!SQL_SUCCEEDED(SQLGetData(res->odbcStatHandle, i + 1, SQL_C_CHAR, field->data, field->len, &len_read)))
reportODBCError("SQLGetData", res->odbcStatHandle, SQL_HANDLE_STMT);
DB.Debug("gb.db.odbc", "query_fill: %s (%d) = %.*s", field->name, field->type, (int)len_read, field->data);
if (len_read > 0)
conv_data((char *)field->data, len_read, &value.value, (int)field->type);
GB.StoreVariant(&value, &buffer[i]);
}
/*if (current == NULL)
{
GB.Error("ODBC internal error 4");
return DB_ERROR;
}
//fprintf(stderr, "Lunghezza letta = %d\n", read);
if (current)
{
if(current->fieldata == NULL)
{
GB.Error("ODBC internal error 5");
return DB_ERROR;
}
if (read == -1)
{
fieldata[0] = ' ';
fieldata[1] = '\0';
current->type = SQL_CHAR;
}
conv_data((char *) fieldata, &value.value, (int) current->type);
}
GB.StoreVariant(&value, &buffer[i]);
if(
(displaySize > 0) &&
(fieldata != NULL)
)
{
free(fieldata);
}
current = (ODBC_FIELDS *) current->next;
fieldata = NULL;*/
}/* for all columns in this row */
return DB_OK;
}
/* Internal function - create the space for the result and bind the column to each field-space allocated */
static void query_make_result(ODBC_RESULT * result)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tquery_make_result result %p, result->odbcStatHandle %p\n", result, result->odbcStatHandle);
fflush(stderr);
#endif
//SQLCHAR colname[32];
SQLSMALLINT colnamelen;
SQLULEN precision;
SQLSMALLINT scale;
SQLINTEGER i;
SQLLEN displaySize;
ODBC_FIELD *field;
SQLINTEGER collen;
int nResultCols;
SQLSMALLINT type;
nResultCols = get_num_columns(result);
DB.Debug("gb.db.odbc", "query_make_result: %p (%d columns)", result, nResultCols);
GB.NewArray(POINTER(&result->fields), sizeof(ODBC_FIELD), nResultCols);
for (i = 0; i < nResultCols; i++)
{
field = &result->fields[i];
SQLDescribeCol(
result->odbcStatHandle,
i + 1,
NULL,
0,
&colnamelen,
&type,
&precision,
&scale,
NULL
);
field->name = GB.NewString(NULL, colnamelen);
SQLDescribeCol(
result->odbcStatHandle,
i + 1,
(SQLCHAR *)field->name,
colnamelen + 1,
&colnamelen,
&type,
&precision,
&scale,
NULL
);
if (type < 0)
{
DB.Debug("gb.db.odbc", "field '%s' has datatype: %d, assuming SQLCHAR instead", field->name, type);
type = SQL_CHAR;
}
field->type = type;
DB.Debug("gb.db.odbc", "query_make_result: '%s' -> type = %d", field->name, field->type);
collen = precision;
/* Get display length for column */
SQLColAttribute(
result->odbcStatHandle,
i + 1,
SQL_COLUMN_DISPLAY_SIZE,
NULL,
0,
NULL,
&displaySize
);
/*
* Set column length to max of display length, and column name
* length. Plus one byte for null terminator
*/
//fprintf(stderr, "%s: collen : %ld, display len %ld\n", field->name, strlen(field->name), displaySize);
/*if (displaySize >= colnamelen)
{
collen = displaySize + 1;
}
else
{
collen = colnamelen + 1;
}*/
collen = Max(displaySize, colnamelen) + 1;
if (collen <= 0)
collen = 1;
field->data = malloc(collen);
field->len = collen;
}
}
/*****************************************************************************
query_init()
Initialize an info structure from a query result.
<result> is the handle of the query result.
<info> points to the info structure.
<count> will receive the number of records returned by the query.
This function must initialize the info->nfield field with the number of
fields in the query result.
If <count> receives -1, that will mean that the result is "move forward"
only
*****************************************************************************/
static void query_init(DB_RESULT result, DB_INFO * info, int *count)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tquery_init\n");
fflush(stderr);
#endif
ODBC_RESULT *res = (ODBC_RESULT *) result;
SQLSMALLINT colsNum = 0;
colsNum = get_num_columns(res);
DB.Debug("gb.db.odbc", "query_init: %p -> %d columns", result, colsNum);
if (colsNum == 0)
return;
*count = res->count;
info->nfield = colsNum;
query_make_result(res);
}
/* Internal function to implement the query execution */
static int do_query(DB_DATABASE *db, const char *error, ODBC_RESULT **res, const char *qtemp, int nsubst, ...)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tdo_query db %p, ODBC_result res %p, db->handle %p, query = '%s'\n", db, res, db->handle, qtemp);
fflush(stderr);
#endif
va_list args;
ODBC_CONN *handle = (ODBC_CONN *)db->handle;
SQLRETURN retcode = SQL_SUCCESS;
ODBC_RESULT *odbcres;
const char *query;
int i;
if (nsubst)
{
va_start(args, nsubst);
if (nsubst > 3)
{
nsubst = 3;
}
for (i = 0; i < nsubst; i++)
{
query_param[i] = va_arg(args, char *);
}
query = DB.SubstString(qtemp, 0, query_get_param);
}
else
{
query = qtemp;
}
//DB.Debug("gb.db.odbc", "do_query() db->handle=%p, query='%s'", handle, query);
GB.AllocZero(POINTER(&odbcres), sizeof(ODBC_RESULT));
/* Allocate the space for the result structure */
retcode = SQLAllocHandle(SQL_HANDLE_STMT, handle->odbcHandle, &odbcres->odbcStatHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
{
//GB.Error("Cannot allocate statement handle");
throwODBCError("SQLAllocHandle", handle->odbcHandle, SQL_HANDLE_DBC);
return retcode;
}
retcode = SQLSetStmtAttr(odbcres->odbcStatHandle, SQL_ATTR_CURSOR_SCROLLABLE, (SQLPOINTER) SQL_SCROLLABLE, 0);
odbcres->Cursor_Scrollable = ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) ? SQL_FALSE : SQL_TRUE;
odbcres->Function_exist = handle->drvrCanFetchScroll;
DB.Debug("gb.db.odbc", "do_query() Cursor_Scrollable = %d, drvrCanFetchScroll %d", odbcres->Cursor_Scrollable, handle->drvrCanFetchScroll);
/* Execute the query */
retcode = SQLExecDirect(odbcres->odbcStatHandle, (SQLCHAR *) query, SQL_NTS);
//DB.Debug("gb.db.odbc", "do_query() SQLExecDirect()=%d [SQL_SUCCESS=%d]", (int)retcode, (int)SQL_SUCCESS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO) && (retcode != SQL_NO_DATA))
{
DB.Debug("gb.db.odbc", "do_query: SQLExecDirect() returned code %d", (int)retcode);
throwODBCError("SQLExecDirect", odbcres->odbcStatHandle, SQL_HANDLE_STMT);
SQLFreeHandle(SQL_HANDLE_STMT, odbcres->odbcStatHandle);
//GB.Error("Error while executing the statement");
return retcode;
}
if (res)
{
if (retcode == SQL_NO_DATA)
{
odbcres->count = 0;
retcode = SQL_SUCCESS;
}
else
{
odbcres->count = GetRecordCount(odbcres->odbcStatHandle, odbcres->Cursor_Scrollable);
}
*res = odbcres;
DB.Debug("gb.db.odbc", "do_query: create handle %p", odbcres->odbcStatHandle);
}
else
{
SQLFreeHandle(SQL_HANDLE_STMT, odbcres->odbcStatHandle);
GB.Free(POINTER(&odbcres));
}
return retcode;
}
/*****************************************************************************
exec_query()
Send a query to the server and gets the result.
<handle> is the database handle, as returned by open_database()
<query> is the query string.
<result> will receive the result handle of the query.
<err> is an error message used when the query failed.
<result> can be NULL, when we don't care getting the result.
*****************************************************************************/
static int exec_query(DB_DATABASE *db, const char *query, DB_RESULT * result, const char *err)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\texec_query\n");
fflush(stderr);
#endif
return do_query(db, err, (ODBC_RESULT **) result, query, 0);
}
/* Internal function - free the result structure create to allocate the result row */
static void query_free_result(ODBC_RESULT * result)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tquery_free_result %p\n",result);
fflush(stderr);
#endif
int i;
ODBC_FIELD *field;
for (i = 0; i < GB.Count(result->fields); i++)
{
field = &result->fields[i];
GB.FreeString(&field->name);
free(field->data);
}
GB.FreeArray(POINTER(&result->fields));
free(result);
}
/*****************************************************************************
query_release()
Free the info structure filled by query_init() and the result handle.
<result> is the handle of the query result.
<info> points to the info structure.
<invalid> tells if the associated connection has been closed.
*****************************************************************************/
static void query_release(DB_RESULT result, DB_INFO *info, bool invalid)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tquery_release\n");
fflush(stderr);
#endif
ODBC_RESULT *res = (ODBC_RESULT *) result;
/*if (res != NULL)*/ //query_free_result(res);
if (!invalid)
{
SQLFreeHandle(SQL_HANDLE_STMT, res->odbcStatHandle);
DB.Debug("gb.db.odbc", "query_release: %p: free handle %p", result, res->odbcStatHandle);
}
else
DB.Debug("gb.db.odbc", "query_release: %p: database is closed, do not free the handle", result);
//free(res->odbcStatHandle);
query_free_result(res);
//free(res->odbcStatHandle);
//res->odbcStatHandle=NULL;
}
/*****************************************************************************
get_last_insert_id()
Return the value of the last serial field used in an INSERT statement
<db> is the database handle, as returned by open_database()
*****************************************************************************/
static int64_t get_last_insert_id(DB_DATABASE *db)
{
GB.Error("Unsupported feature");
return -1;
}
/*****************************************************************************
blob_read()
Returns the value of a BLOB field.
<result> is the handle of the result.
<pos> is the index of the record in the result.
<blob> points at a DB_BLOB structure that will receive a pointer to the
data and its length.
*****************************************************************************/
static void blob_read(DB_RESULT result, int pos, int field, DB_BLOB *blob)
{
ODBC_RESULT *res = (ODBC_RESULT *)result;
SQLCHAR buffer[1024];
SQLLEN len_read;
int old_length;
SQLRETURN ret;
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tblob_read DB_RESULT %p, dbresult->stathandle %p, pos %d , field %d\n",res,res->odbcStatHandle ,pos,field);
fflush(stderr);
#endif
blob->data = NULL;
blob->length = 0;
for(;;)
{
len_read = 0;
ret = SQLGetData(res->odbcStatHandle, field + 1, SQL_C_BINARY, buffer, sizeof(buffer), &len_read);
if (ret == SQL_ERROR || ret == SQL_NO_DATA || len_read <= 0)
break;
fprintf(stderr, "blob_read: %d %ld\n", blob->length, len_read);
if (len_read > sizeof(buffer) || len_read == SQL_NO_TOTAL)
len_read = sizeof(buffer);
old_length = blob->length;
blob->length += len_read;
GB.Realloc(POINTER(&blob->data), blob->length);
memcpy(&blob->data[old_length], buffer, len_read);
}
if (ret != SQL_NO_DATA)
DB.Debug("gb.db.odbc", "unable to read blob from field '%s'", res->fields[field].name);
}
/*****************************************************************************
field_name()
Return the name of a field in a result from its index.
<result> is the result handle.
<field> is the field index.
*****************************************************************************/
static char *field_name(DB_RESULT result, int field)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tfield_name\n");
fflush(stderr);
#endif
ODBC_RESULT *res = (ODBC_RESULT *)result;
return res->fields[field].name;
}
/*****************************************************************************
field_index()
Return the index of a field in a result from its name.
<result> is the result handle.
<name> is the field name.
<handle> is needed by this driver to enable table.field syntax
*****************************************************************************/
static int field_index(DB_RESULT result, const char *name, DB_DATABASE *db)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tfield_index\n");
fflush(stderr);
#endif
ODBC_RESULT *res = (ODBC_RESULT *) result;
int i;
for (i = 0; i < GB.Count(res->fields); i++)
{
if (strcmp(res->fields[i].name, name) == 0)
return i;
}
return (-1);
}
/*****************************************************************************
field_type()
Return the Gambas type of a field in a result from its index.
<result> is the result handle.
<field> is the field index.
*****************************************************************************/
static GB_TYPE field_type(DB_RESULT result, int field)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tfield_type id %d\n",field);
fflush(stderr);
#endif
ODBC_RESULT *res = (ODBC_RESULT *)result;
return conv_type(res->fields[field].type);
}
/*****************************************************************************
field_length()
Return the length of a field in a result from its index.
<result> is the result handle.
<field> is the field index.
*****************************************************************************/
static int field_length(DB_RESULT result, int field)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tfield_length\n");
fflush(stderr);
#endif
SQLCHAR colname[32];
SQLSMALLINT coltype;
SQLSMALLINT colnamelen;
SQLULEN precision;
SQLSMALLINT scale;
ODBC_RESULT *res = (ODBC_RESULT *) result;
SQLDescribeCol(res->odbcStatHandle, field + 1, colname, sizeof(colname), &colnamelen, &coltype, &precision, &scale, NULL);
return colnamelen;
}
/*****************************************************************************
begin_transaction()
Begin a transaction.
<handle> is the database handle.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int begin_transaction(DB_DATABASE *db)
{
return (do_query(db, "Unable to begin transaction: &1", NULL, "BEGIN", 0));
}
/*****************************************************************************
commi_transaction()
Commit a transaction.
<handle> is the database handle.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int commit_transaction(DB_DATABASE *db)
{
return (do_query(db, "Unable to commit transaction: &1", NULL, "COMMIT", 0));
}
/*****************************************************************************
rollback_transaction()
Rollback a transaction.
<handle> is the database handle.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int rollback_transaction(DB_DATABASE *db)
{
return do_query(db, "Unable to rollback transaction: &1", NULL, "ROLLBACK", 0);
}
/*****************************************************************************
table_init()
Initialize an info structure from table fields.
<handle> is the database handle.
<table> is the table name.
<info> points at the info structure.
This function must initialize the following info fields:
- info->nfield must contain the number of fields in the table.
- info->field is an array of DB_FIELD, one element for each field.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int table_init(DB_DATABASE *db, const char *table, DB_INFO * info)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\ttable_init\n");
fflush(stderr);
#endif
SQLCHAR field_name[256];
SQLCHAR coltype[100];
SQLCHAR precision[100];
SQLSMALLINT colsNum;
SQLHSTMT statHandle;
//SQLRETURN V_OD_erg;
SQLRETURN retcode;
int i;
DB_FIELD *f;
ODBC_FIELD *fields, *field;
ODBC_CONN *han = (ODBC_CONN *)db->handle;
info->table = GB.NewZeroString(table);
retcode = SQLAllocHandle(SQL_HANDLE_STMT, (ODBC_CONN *) han->odbcHandle, &statHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return TRUE;
if (!SQL_SUCCEEDED
(colsNum =
SQLColumns(statHandle, NULL, 0, NULL, 0, (SQLCHAR *) table, SQL_NTS, NULL,
0)))
goto __ERROR;
GB.NewArray(POINTER(&fields), sizeof(ODBC_FIELD), 0);
while (SQL_SUCCEEDED(SQLFetch(statHandle)))
{
field = GB.Add(POINTER(&fields));
SQLGetData(statHandle, SQLColumns_COLUMN_NAME, SQL_C_CHAR, field_name, sizeof(field_name), 0);
field->name = GB.NewZeroString((char *)field_name);
if (!SQL_SUCCEEDED
(SQLGetData
(statHandle, SQLColumns_SQL_DATA_TYPE, SQL_C_CHAR, coltype,
sizeof(coltype), 0)))
goto __ERROR;
fprintf(stderr, "table_init: %s -> %s\n", field->name, coltype);
field->type = atol((char *)coltype);
if (!SQL_SUCCEEDED
(SQLGetData
(statHandle, SQLColumns_COLUMN_SIZE, SQL_C_CHAR, precision,
sizeof(precision), 0)))
goto __ERROR;
field->len = atol((char *)precision);
}
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
info->nfield = GB.Count(fields);
GB.Alloc(POINTER(&info->field), sizeof(DB_FIELD) * info->nfield);
i = 0;
for (i = 0; i < colsNum; i++)
{
field = &fields[i];
f = &info->field[i];
f->name = field->name;
f->type = conv_type(field->type);
f->length = 0;
if (f->type == GB_T_STRING)
f->length = field->len;
}
GB.FreeArray(POINTER(&fields));
return FALSE;
__ERROR:
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return TRUE;
}
/*****************************************************************************
table_index()
Initialize an info structure from table primary index.
<handle> is the database handle.
<table> is the table name.
<info> points at the info structure.
This function must initialize the following info fields:
- info->nindex must contain the number of fields in the primary index.
- info->index is a int[] giving the index of each index field in
info->fields.
This function must be called after table_init().
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int table_index(DB_DATABASE *db, const char *table, DB_INFO * info)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\ttable_index - info %p,info->nindex %d\n",info,info->nindex);
fflush(stderr);
#endif
SQLCHAR field_name[256];
int inx[256];
SQLHSTMT statHandle, statHandle2;
//SQLRETURN V_OD_erg;
SQLRETURN nReturn = -1;
SQLRETURN retcode;
SQLCHAR szKeyName[256];
SQLCHAR szColumnName[256];
SQLCHAR query[101] = "SELECT * FROM ";
SQLSMALLINT colsNum;
int i, n;
ODBC_CONN *han = (ODBC_CONN *)db->handle;
ODBC_FIELD *fields, *field;
ODBC_RESULT *res;
strcpy((char *)&query[14], table);
colsNum = 0;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, (ODBC_CONN *) han->odbcHandle, &statHandle2);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return TRUE;
retcode = SQLColumns(statHandle2, NULL, 0, NULL, 0, (SQLCHAR *) table, SQL_NTS, NULL, 0);
if (!SQL_SUCCEEDED(retcode))
goto __ERROR_2;
GB.NewArray(POINTER(&fields), sizeof(ODBC_FIELD), 0);
while (SQL_SUCCEEDED(SQLFetch(statHandle2)))
{
field = GB.Add(&fields);
if (!SQL_SUCCEEDED(SQLGetData(statHandle2, SQLColumns_COLUMN_NAME, SQL_C_CHAR, field_name, sizeof(field_name), 0)))
strcpy((char *)field_name, "?");
field->name = GB.NewZeroString((char *)field_name);
}
retcode = SQLNumResultCols(statHandle2, &colsNum);
SQLFreeHandle(SQL_HANDLE_STMT, statHandle2);
retcode = SQLAllocHandle(SQL_HANDLE_STMT, han->odbcHandle, &statHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return TRUE;
res = malloc(sizeof(ODBC_RESULT));
if (!SQL_SUCCEEDED(nReturn = SQLPrimaryKeys(statHandle, NULL, 0, NULL, 0, (SQLCHAR *)table, SQL_NTS)))
goto __ERROR;
retcode = SQLNumResultCols(statHandle, &colsNum);
i = 0;
while (SQL_SUCCEEDED(SQLFetch(statHandle)))
{
if (!SQL_SUCCEEDED(SQLGetData (statHandle, 4, SQL_C_CHAR, szColumnName, sizeof(szColumnName), 0)))
strcpy((char *) szColumnName, "?");
if (!SQL_SUCCEEDED(SQLGetData(statHandle, 6, SQL_C_CHAR, szKeyName, sizeof(szKeyName), 0)))
strcpy((char *) szKeyName, "?");
for (n = 0; n < colsNum; n++)
{
if (strcmp(fields[i].name, (char *)szColumnName) == 0)
{
inx[i] = n;
break;
}
}
i++;
}
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
GB.Alloc(POINTER(&info->index), sizeof(int) * i);
info->nindex = i;
for (n = 0; n < i; n++)
info->index[n] = inx[n];
free(res);
for (i = 0; i < GB.Count(fields); i++)
GB.FreeString(&fields[i].name);
GB.FreeArray(POINTER(&fields));
return FALSE;
__ERROR_2:
SQLFreeHandle(SQL_HANDLE_STMT, statHandle2);
return TRUE;
__ERROR:
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
free(res);
return TRUE;
}
/*****************************************************************************
table_release()
Free the info structure filled by table_init() and/or table_index()
<handle> is the database handle.
<info> points at the info structure.
*****************************************************************************/
static void table_release(DB_DATABASE *db, DB_INFO * info)
{
/* All is done outside the driver */
}
/*****************************************************************************
table_exist()
Returns if a table exists
<handle> is the database handle.
<table> is the table name.
This function returns TRUE if the table exists, and FALSE if not.
*****************************************************************************/
static int table_exist(DB_DATABASE *db, const char *table)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\ttable_exist\n");
fflush(stderr);
#endif
SQLHSTMT statHandle;
//SQLRETURN V_OD_erg;
SQLRETURN nReturn = -1;
SQLRETURN retcode;
SQLCHAR szTableName[101] = "";
SQLCHAR szTableType[101] = "";
SQLCHAR szTableRemarks[301] = "";
SQLLEN nIndicatorName;
SQLLEN nIndicatorType;
SQLLEN nIndicatorRemarks;
int compare = -1;
ODBC_CONN *han = (ODBC_CONN *)db->handle;
int len;
len = strlen(table);
if (len == 0)
return FALSE;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, han->odbcHandle, &statHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return FALSE; //V_OD_erg;
// EXECUTE OUR SQL/CALL
if (SQL_SUCCESS != (nReturn = SQLTables(statHandle, 0, 0, 0, 0, 0, 0, 0, 0)))
return FALSE; //nReturn;
SQLBindCol(statHandle, SQLTables_TABLE_NAME, SQL_C_CHAR, szTableName,
sizeof(szTableName), &nIndicatorName);
SQLBindCol(statHandle, SQLTables_TABLE_TYPE, SQL_C_CHAR, szTableType,
sizeof(szTableType), &nIndicatorType);
SQLBindCol(statHandle, SQLTables_REMARKS, SQL_C_CHAR, szTableRemarks,
sizeof(szTableRemarks), &nIndicatorRemarks);
// GET RESULTS
nReturn = SQLFetch(statHandle);
while ((nReturn == SQL_SUCCESS || nReturn == SQL_SUCCESS_WITH_INFO)
&& compare != 0)
{
//printf("le tabelle in comparazione %s : %s\n",szTableName,table);
compare = strncmp((char *)szTableName, table, len);
szTableName[0] = '\0';
szTableType[0] = '\0';
szTableRemarks[0] = '\0';
nReturn = SQLFetch(statHandle);
}
// FREE STATEMENT
nReturn = SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
if (compare == 0)
return TRUE;
else
return FALSE;
}
/*****************************************************************************
table_list()
Returns an array containing the name of each table in the database
<handle> is the database handle.
<tables> points to a variable that will receive the char* array.
This function returns the number of tables, or -1 if the command has
failed.
Be careful: <tables> can be NULL, so that just the count is returned.
*****************************************************************************/
static int table_list(DB_DATABASE *db, char ***tables)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\ttable_list\n");
fflush(stderr);
#endif
ODBC_TABLES tablelist, *curtable;
SQLHSTMT statHandle;
//SQLRETURN V_OD_erg;
SQLRETURN nReturn = -1;
SQLRETURN retcode;
SQLCHAR szTableName[101] = "";
SQLCHAR szTableType[101] = "";
SQLCHAR szTableRemarks[301] = "";
SQLLEN nIndicatorName;
SQLLEN nIndicatorType;
SQLLEN nIndicatorRemarks;
int tablenum = 0;
int i;
ODBC_CONN *han = (ODBC_CONN *)db->handle;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, han->odbcHandle, &statHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return (-1);
curtable = &tablelist;
// EXECUTE OUR SQL/CALL
if (SQL_SUCCESS != (nReturn = SQLTables(statHandle, 0, 0, 0, 0, 0, 0, 0, 0)))
goto __ERROR;
SQLBindCol(statHandle, SQLTables_TABLE_NAME, SQL_C_CHAR, szTableName,
sizeof(szTableName), &nIndicatorName);
SQLBindCol(statHandle, SQLTables_TABLE_TYPE, SQL_C_CHAR, szTableType,
sizeof(szTableType), &nIndicatorType);
SQLBindCol(statHandle, SQLTables_REMARKS, SQL_C_CHAR, szTableRemarks,
sizeof(szTableRemarks), &nIndicatorRemarks);
// GET RESULTS
nReturn = SQLFetch(statHandle);
if (nReturn != SQL_SUCCESS && nReturn != SQL_SUCCESS_WITH_INFO)
goto __ERROR;
while (nReturn == SQL_SUCCESS || nReturn == SQL_SUCCESS_WITH_INFO)
{
if (strcmp((char *)szTableType, "TABLE") == 0)
{
tablenum = tablenum + 1;
curtable->tablename = malloc(sizeof(szTableName));
curtable->next = malloc(sizeof(ODBC_TABLES));
strcpy(curtable->tablename, (char *)szTableName);
curtable = (ODBC_TABLES *) curtable->next;
}
szTableName[0] = '\0';
szTableType[0] = '\0';
szTableRemarks[0] = '\0';
nReturn = SQLFetch(statHandle);
}
// FREE STATEMENT
nReturn = SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
GB.NewArray(tables, sizeof(char *), tablenum);
curtable = &tablelist;
for (i = 0; i < tablenum; i++)
{
(*tables)[i] = GB.NewZeroString(curtable->tablename);
free(curtable->tablename);
curtable = (ODBC_TABLES *) curtable->next;
}
curtable = &tablelist;
int g;
for (i = tablenum; i > 0; i--)
{
for (g = 0; g < i; g++)
curtable = (ODBC_TABLES *) curtable->next;
free(curtable);
curtable = &tablelist;
}
return (tablenum);
__ERROR:
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return (-1);
}
/*****************************************************************************
table_primary_key()
Returns a string representing the primary key of a table.
<handle> is the database handle.
<table> is the table name.
<key> points to a string that will receive the primary key.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int table_primary_key(DB_DATABASE *db, const char *table, char ***primary)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\ttable_primary_key\n");
fflush(stderr);
#endif
SQLHSTMT statHandle;
SQLRETURN retcode;
//SQLRETURN V_OD_erg;
SQLRETURN nReturn = -1;
SQLCHAR szKeyName[101] = "";
SQLCHAR szColumnName[101] = "";
SQLCHAR query[101] = "SELECT * FROM ";
SQLSMALLINT colsNum;
int i;
ODBC_CONN *han = (ODBC_CONN *)db->handle;
// CREATE A STATEMENT
ODBC_RESULT *res;
strcpy((char *)&query[14], table);
retcode = SQLAllocHandle(SQL_HANDLE_STMT, han->odbcHandle, &statHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return TRUE;
res = malloc(sizeof(ODBC_RESULT));
/*if (!SQL_SUCCEEDED
(nReturn = SQLPrimaryKeys(statHandle, 0, 0, 0, SQL_NTS, (SQLCHAR *)table, SQL_NTS)))*/
if (!SQL_SUCCEEDED(nReturn = SQLPrimaryKeys(statHandle, (SQLCHAR *)"", 0, (SQLCHAR *)"", 0, (SQLCHAR *)table, SQL_NTS)))
goto __ERROR;
// GET RESULTS
SQLNumResultCols(statHandle, &colsNum);
GB.NewArray(primary, sizeof(char *), 0);
i = 0;
while (SQL_SUCCEEDED(SQLFetch(statHandle)))
{
if (!SQL_SUCCEEDED
(SQLGetData
(statHandle, 4, SQL_C_CHAR, &szColumnName[0], sizeof(szColumnName), 0)))
strcpy((char *) szColumnName, "?");
if (!SQL_SUCCEEDED
(SQLGetData
(statHandle, 6, SQL_C_CHAR, &szKeyName[0], sizeof(szKeyName), 0)))
strcpy((char *) szKeyName, "?");
*(char **)GB.Add(primary) = GB.NewZeroString((char *)szColumnName);
i++;
}
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
free(res);
return FALSE;
__ERROR:
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
free(res);
return TRUE;
}
/*****************************************************************************
table_is_system()
Returns if a table is a system table.
<handle> is the database handle.
<table> is the table name.
This function returns TRUE if the table is a system table, and FALSE if
not.
*****************************************************************************/
static int table_is_system(DB_DATABASE *db, const char *table)
{
return FALSE;
}
/*****************************************************************************
table_delete()
Deletes a table.
<handle> is the database handle.
<table> is the table name.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int table_delete(DB_DATABASE *db, const char *table)
{
int exit;
const char *query = "DROP TABLE &1";
exit = do_query(db, "Cannot delete table: &1", NULL, query, 1, table);
/* BM: What's that ???
if (exit == 0)
{
exit = do_query(db, "Cannot delete table:&1", NULL, "COMMIT", 0);
}*/
return (exit);
}
/*****************************************************************************
table_create()
Creates a table.
<handle> is the database handle.
<table> is the table name.
<fields> points to a linked list of field descriptions.
<key> is the primary key.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int table_create(DB_DATABASE *db, const char *table, DB_FIELD *fields, char **primary, const char *type_not_used)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\ttable_create\n");
fflush(stderr);
#endif
DB_FIELD *fp;
int comma;
char *type;
int i, exit;
DB.Query.Init();
DB.Query.Add("CREATE TABLE ");
DB.Query.Add(table);
DB.Query.Add(" ( ");
comma = FALSE;
for (fp = fields; fp; fp = fp->next)
{
if (comma)
DB.Query.Add(", ");
else
comma = TRUE;
DB.Query.Add(fp->name);
//AB autoincrement field is mapped to Integer because this is Database dependent
if (fp->type == DB_T_SERIAL)
DB.Query.Add(" INTEGER ");
//AB Blob field
else if (fp->type == DB_T_BLOB)
DB.Query.Add(" LONG VARBINARY ");
switch (fp->type)
{
case GB_T_BOOLEAN:
type = "TINYINT"; //
break;
case GB_T_INTEGER:
type = "INTEGER";
break;
case GB_T_FLOAT:
type = "DOUBLE";
break;
case GB_T_DATE:
type = "DATE"; // AB changed the field name used by default from Timestamp to Date
break;
// New datatype BIGINT 64 bits
case GB_T_LONG:
type = "BIGINT";
break;
case GB_T_STRING:
if (fp->length <= 0)
type = "TEXT";
else
{
sprintf(_buffer, "VARCHAR(%d)", fp->length);
type = _buffer;
}
break;
default:
type = "TEXT";
break;
}
DB.Query.Add(" ");
DB.Query.Add(type);
if (fp->def.type != GB_T_NULL)
{
DB.Query.Add(" NOT NULL DEFAULT ");
DB.FormatVariant(&_driver, &fp->def, DB.Query.AddLength);
}
else if (DB.StringArray.Find(primary, fp->name) >= 0)
{
DB.Query.Add(" NOT NULL ");
}
}
if (primary)
{
DB.Query.Add(", PRIMARY KEY (");
for (i = 0; i < GB.Count(primary); i++)
{
if (i > 0)
DB.Query.Add(",");
DB.Query.Add(primary[i]);
}
DB.Query.Add(")");
}
DB.Query.Add(" )");
exit = do_query(db, "Cannot create table: &1", NULL, DB.Query.Get(), 0);
if (exit == 0)
{
exit = do_query(db, "Cannot create table:&1", NULL, "COMMIT", 0);
}
return (exit);
}
/*****************************************************************************
field_exist()
Returns if a field exists in a given table
<handle> is the database handle.
<table> is the table name.
<field> is the field name.
This function returns TRUE if the field exists, and FALSE if not.
*****************************************************************************/
static int field_exist(DB_DATABASE *db, const char *table, const char *field)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tfield_exist\n");
fflush(stderr);
#endif
SQLCHAR colname[256];
SQLSMALLINT colsNum;
SQLHSTMT statHandle;
SQLRETURN retcode;
//SQLRETURN V_OD_erg;
ODBC_CONN *han = (ODBC_CONN *)db->handle;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, (ODBC_CONN *) han->odbcHandle, &statHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return FALSE; //V_OD_erg;
//printf("field exist dopo l'handler\n");
if (!SQL_SUCCEEDED
(colsNum =
SQLColumns(statHandle, NULL, 0, NULL, 0, (SQLCHAR *) table, SQL_NTS, NULL,
0)))
goto __ERROR;
//printf("field exist dopo la SQLColumn : %u\n",colsNum);
while (SQL_SUCCEEDED(SQLFetch(statHandle)))
{
SQLGetData(statHandle, SQLColumns_COLUMN_NAME, SQL_C_CHAR, colname, sizeof(colname), 0);
if (strcmp((char *)colname, field) == 0)
{
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return TRUE;
}
}
__ERROR:
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return FALSE;
}
/*****************************************************************************
field_list()
Returns an array containing the name of each field in a given table
<handle> is the database handle.
<table> is the table name.
<fields> points to a variable that will receive the char* array.
This function returns the number of fields, or -1 if the command has
failed.
Be careful: <fields> can be NULL, so that just the count is returned.
*****************************************************************************/
static int field_list(DB_DATABASE *db, const char *table, char ***fields)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tfield_list\n");
fflush(stderr);
#endif
SQLCHAR field_name[256];
SQLSMALLINT colsNum;
SQLHSTMT statHandle;
SQLRETURN retcode;
//SQLRETURN V_OD_erg;
ODBC_CONN *han = (ODBC_CONN *)db->handle;
colsNum = 0;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, (ODBC_CONN *) han->odbcHandle, &statHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return (-1);
retcode = SQLColumns(statHandle, NULL, 0, NULL, 0, (SQLCHAR *) table, SQL_NTS, NULL, 0);
if (!SQL_SUCCEEDED(retcode))
goto __ERROR;
if (fields)
GB.NewArray(fields, sizeof(char *), 0);
while (SQL_SUCCEEDED(SQLFetch(statHandle)))
{
if (!SQL_SUCCEEDED
(SQLGetData
(statHandle, SQLColumns_COLUMN_NAME, SQL_C_CHAR, field_name,
sizeof(field_name), 0)))
strcpy((char *)field_name, "?");
if (fields)
*((char **)GB.Add(fields)) = GB.NewZeroString((char *)field_name);
colsNum++;
}
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return colsNum;
__ERROR:
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return -1;
}
/*****************************************************************************
field_info()
Get field description
<handle> is the database handle.
<table> is the table name.
<field> is the field name.
<info> points to a structure filled by the function.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int field_info(DB_DATABASE *db, const char *table, const char *field, DB_FIELD * info)
{
#ifdef ODBC_DEBUG_HEADER
fprintf(stderr,"[ODBC][%s][%d]\n",__FILE__,__LINE__);
fprintf(stderr,"\tfield_info\n");
fflush(stderr);
#endif
SQLCHAR colname[128];
SQLCHAR coltype[100];
SQLCHAR precision[100];
SQLHSTMT statHandle;
SQLHSTMT statHandle1;
SQLRETURN retcode;
//SQLRETURN V_OD_erg;
SQLLEN auton=SQL_FALSE;
int i;
int size_query = 32 + strlen(table) + strlen(field);
char query[size_query];
ODBC_CONN *han = (ODBC_CONN *)db->handle;
ODBC_CONN *han1 = (ODBC_CONN *)db->handle;
*precision = 0;
/*strncpy((char *)&query[0], "SELECT ",7);
strncpy((char *)&query[7], field,strlen(field));
strncpy((char *)&query[strlen(field)+7], " FROM ",6);
strncpy((char *)&query[strlen(field)+13], table,strlen(table));
query[strlen(field)+14+strlen(table)]='\0';
strncpy((char *)&query[strlen(field)+13+strlen(table)],"\0\n\0\n",4);*/
snprintf(query, size_query, "SELECT %s FROM %s", field, table);
for (i = 0; i < 100; i++)
coltype[i] = '\0';
retcode = SQLAllocHandle(SQL_HANDLE_STMT, (ODBC_CONN *) han->odbcHandle, &statHandle);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
return TRUE;
retcode = SQLAllocHandle(SQL_HANDLE_STMT, (ODBC_CONN *) han1->odbcHandle, &statHandle1);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
{
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return TRUE;
}
retcode = SQLExecDirect(statHandle1, (SQLCHAR *) query , SQL_NTS);
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
goto __ERROR;
retcode = SQLColAttribute (statHandle1,1,SQL_DESC_AUTO_UNIQUE_VALUE,NULL,0,NULL,&auton);
if (!SQL_SUCCEEDED(retcode = SQLColumns(statHandle, NULL, 0, NULL, 0, (SQLCHAR *) table, SQL_NTS, NULL,0)))
goto __ERROR;
while (SQL_SUCCEEDED(SQLFetch(statHandle)))
{
SQLGetData(statHandle, SQLColumns_COLUMN_NAME, SQL_C_CHAR, colname, sizeof(colname), 0);
if (strcmp((char *)colname, field) == 0)
{
SQL_SUCCEEDED(SQLGetData(statHandle, SQLColumns_SQL_DATA_TYPE, SQL_C_CHAR, coltype, sizeof(coltype), 0));
SQL_SUCCEEDED(SQLGetData(statHandle, SQLColumns_COLUMN_SIZE, SQL_C_CHAR, precision, sizeof(precision), 0));
break;
}
}
info->name = NULL;
info->type = conv_type(atol((char *)coltype));
info->length = 0;
if (*precision)
info->length = atol((char *) precision);
if (info->type == GB_T_STRING)
{
if (info->length < 0)
info->length = 0;
}
if (auton == SQL_TRUE)
info->type = DB_T_SERIAL;
info->def.type = GB_T_NULL;
info->collation = NULL;
SQLFreeHandle(SQL_HANDLE_STMT, statHandle1);
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return FALSE;
__ERROR:
SQLFreeHandle(SQL_HANDLE_STMT, statHandle1);
SQLFreeHandle(SQL_HANDLE_STMT, statHandle);
return TRUE;
}
/*****************************************************************************
index_exist()
Returns if an index exists in a given table
<handle> is the database handle.
<table> is the table name.
<field> is the index name.
This function returns TRUE if the index exists, and FALSE if not.
*****************************************************************************/
static int index_exist(DB_DATABASE *db, const char *table, const char *index)
{
return FALSE;
}
/*****************************************************************************
index_list()
Returns an array containing the name of each index in a given table
<handle> is the database handle.
<table> is the table name.
<indexes> points to a variable that will receive the char* array.
This function returns the number of indexes, or -1 if the command has
failed.
Be careful: <indexes> can be NULL, so that just the count is returned.
*****************************************************************************/
static int index_list(DB_DATABASE *db, const char *table, char ***indexes)
{
return (-1);
}
/*****************************************************************************
index_info()
Get index description
<handle> is the database handle.
<table> is the table name.
<field> is the index name.
<info> points to a structure filled by the function.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int index_info(DB_DATABASE *db, const char *table, const char *index, DB_INDEX * info)
{
return TRUE;
}
/*****************************************************************************
index_delete()
Deletes an index.
<handle> is the database handle.
<table> is the table name.
<index> is the index name.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int index_delete(DB_DATABASE *db, const char *table, const char *index)
{
return TRUE;
}
/*****************************************************************************
index_create()
Creates an index.
<handle> is the database handle.
<table> is the table name.
<index> is the index name.
<info> points to a structure describing the index.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int index_create(DB_DATABASE *db, const char *table, const char *index, DB_INDEX * info)
{
return TRUE;
}
/*****************************************************************************
database_exist()
Returns if a database exists
<handle> is any database handle.
<name> is the database name.
This function returns TRUE if the database exists, and FALSE if not.
*****************************************************************************/
static int database_exist(DB_DATABASE *db, const char *name)
{
ODBC_CONN *han = (ODBC_CONN *)db->handle;
return han->dsn_name && strcmp(han->dsn_name, name) == 0;
}
/*****************************************************************************
database_list()
Returns an array containing the name of each database
<handle> is any database handle.
<databases> points to a variable that will receive the char* array.
This function returns the number of databases, or -1 if the command has
failed.
Be careful: <databases> can be NULL, so that just the count is returned.
*****************************************************************************/
static int database_list(DB_DATABASE *db, char ***databases)
{
ODBC_CONN *han = (ODBC_CONN *)db->handle;
if (!han->dsn_name)
return 0;
if (databases)
{
GB.NewArray(databases, sizeof(char *), 1);
(*databases)[0] = GB.NewZeroString(han->dsn_name);
}
return (1);
}
/*****************************************************************************
database_is_system()
Returns if a database is a system database.
<handle> is any database handle.
<name> is the database name.
This function returns TRUE if the database is a system database, and
FALSE if not.
*****************************************************************************/
static int database_is_system(DB_DATABASE *db, const char *name)
{
//GB.Error("ODBC does not implement this function");
return FALSE;
}
/*****************************************************************************
table_type()
Not Valid in postgresql
<handle> is the database handle.
<table> is the table name.
*****************************************************************************/
static char *table_type(DB_DATABASE *db, const char *table, const char *type)
{
if (type)
GB.Error("ODBC does not have any table types");
return NULL;
}
/*****************************************************************************
database_delete()
Deletes a database.
<handle> is the database handle.
<name> is the database name.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int database_delete(DB_DATABASE *db, const char *name)
{
//GB.Error("ODBC does not implement this function");
return TRUE;
}
/*****************************************************************************
database_create()
Creates a database.
<handle> is the database handle.
<name> is the database name.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int database_create(DB_DATABASE *db, const char *name)
{
//GB.Error("ODBC does not implement this function");
return TRUE;
}
/*****************************************************************************
user_exist()
Returns if a user exists.
<handle> is any database handle.
<name> is the user name.
This function returns TRUE if the user exists, and FALSE if not.
*****************************************************************************/
static int user_exist(DB_DATABASE *db, const char *name)
{
ODBC_CONN *han = (ODBC_CONN *)db->handle;
return strcmp(han->user_name, name) == 0;
// GB.Error("ODBC does not implement this function");
}
/*****************************************************************************
user_list()
Returns an array containing the name of each user.
<handle> is the database handle.
<users> points to a variable that will receive the char* array.
This function returns the number of users, or -1 if the command has
failed.
Be careful: <users> can be NULL, so that just the count is returned.
*****************************************************************************/
static int user_list(DB_DATABASE *db, char ***users)
{
ODBC_CONN *han = (ODBC_CONN *)db->handle;
if (users)
{
GB.NewArray(users, sizeof(char *), 1);
(*users)[0] = GB.NewZeroString(han->user_name);
}
//GB.Error("ODBC does not implement this function");
return (1);
}
/*****************************************************************************
user_info()
Get user description
<handle> is the database handle.
<name> is the user name.
<info> points to a structure filled by the function.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int user_info(DB_DATABASE *db, const char *name, DB_USER *info)
{
//GB.Error("ODBC does not implement this function");
return TRUE;
}
/*****************************************************************************
user_delete()
Deletes a user.
<handle> is any database handle.
<name> is the user name.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int user_delete(DB_DATABASE *db, const char *name)
{
//GB.Error("ODBC can't delete users");
return TRUE;
}
/*****************************************************************************
user_create()
Creates a user.
<handle> is the database handle.
<name> is the user name.
<info> points to a structure describing the user.
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int user_create(DB_DATABASE *db, const char *name, DB_USER * info)
{
//GB.Error("ODBC can't create users");
return TRUE;
}
/*****************************************************************************
user_set_password()
Change the user password.
<handle> is the database handle.
<name> is the user name.
<password> is the new password
This function returns TRUE if the command has failed, and FALSE if
everything was OK.
*****************************************************************************/
static int user_set_password(DB_DATABASE *db, const char *name, const char *password)
{
//GB.Error("ODBC can't set user's password");
return TRUE;
}
/*****************************************************************************
The driver interface
*****************************************************************************/
DECLARE_DRIVER(_driver, "odbc");
/*****************************************************************************
The component entry and exit functions.
*****************************************************************************/
int EXPORT GB_INIT(void)
{
GB.GetInterface("gb.db", DB_INTERFACE_VERSION, &DB);
DB.Register(&_driver);
return -1;
}
void EXPORT GB_EXIT()
{
}