summaryrefslogtreecommitdiffstats
path: root/storage/connect/tabodbc.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:07:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:07:14 +0000
commita175314c3e5827eb193872241446f2f8f5c9d33c (patch)
treecd3d60ca99ae00829c52a6ca79150a5b6e62528b /storage/connect/tabodbc.cpp
parentInitial commit. (diff)
downloadmariadb-10.5-9e4947182e0b875da38088fdd168e775f473b8ad.tar.xz
mariadb-10.5-9e4947182e0b875da38088fdd168e775f473b8ad.zip
Adding upstream version 1:10.5.12.upstream/1%10.5.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'storage/connect/tabodbc.cpp')
-rw-r--r--storage/connect/tabodbc.cpp1411
1 files changed, 1411 insertions, 0 deletions
diff --git a/storage/connect/tabodbc.cpp b/storage/connect/tabodbc.cpp
new file mode 100644
index 00000000..4e8b4417
--- /dev/null
+++ b/storage/connect/tabodbc.cpp
@@ -0,0 +1,1411 @@
+/************* Tabodbc C++ Program Source Code File (.CPP) *************/
+/* PROGRAM NAME: TABODBC */
+/* ------------- */
+/* Version 3.2 */
+/* */
+/* COPYRIGHT: */
+/* ---------- */
+/* (C) Copyright to the author Olivier BERTRAND 2000-2018 */
+/* */
+/* WHAT THIS PROGRAM DOES: */
+/* ----------------------- */
+/* This program are the TABODBC class DB execution routines. */
+/* */
+/* WHAT YOU NEED TO COMPILE THIS PROGRAM: */
+/* -------------------------------------- */
+/* */
+/* REQUIRED FILES: */
+/* --------------- */
+/* TABODBC.CPP - Source code */
+/* PLGDBSEM.H - DB application declaration file */
+/* TABODBC.H - TABODBC classes declaration file */
+/* GLOBAL.H - Global declaration file */
+/* */
+/* REQUIRED LIBRARIES: */
+/* ------------------- */
+/* Large model C library */
+/* */
+/* REQUIRED PROGRAMS: */
+/* ------------------ */
+/* IBM, Borland, GNU or Microsoft C++ Compiler and Linker */
+/* */
+/***********************************************************************/
+
+/***********************************************************************/
+/* Include relevant MariaDB header file. */
+/***********************************************************************/
+#include "my_global.h"
+#include "sql_class.h"
+#if defined(_WIN32)
+#include <io.h>
+#include <fcntl.h>
+#if defined(__BORLANDC__)
+#define __MFC_COMPAT__ // To define min/max as macro
+#endif
+//#include <windows.h>
+#include <sqltypes.h>
+#else
+#if defined(UNIX)
+#include <errno.h>
+#define NODW
+#include "osutil.h"
+#else
+#include <io.h>
+#endif
+#include <fcntl.h>
+#endif
+
+/***********************************************************************/
+/* Include application header files: */
+/* global.h is header containing all global declarations. */
+/* plgdbsem.h is header containing the DB application declarations. */
+/* kindex.h is kindex header that also includes tabdos.h. */
+/* tabodbc.h is header containing the TABODBC class declarations. */
+/* odbconn.h is header containing ODBC connection declarations. */
+/***********************************************************************/
+#include "global.h"
+#include "plgdbsem.h"
+#include "mycat.h"
+#include "xtable.h"
+#include "tabext.h"
+#include "odbccat.h"
+#include "tabodbc.h"
+#include "tabmul.h"
+//#include "reldef.h"
+#include "tabcol.h"
+#include "valblk.h"
+#include "ha_connect.h"
+
+#include "sql_string.h"
+
+/***********************************************************************/
+/* DB static variables. */
+/***********************************************************************/
+// int num_read, num_there, num_eq[2], num_nf; // Statistics
+extern int num_read, num_there, num_eq[2]; // Statistics
+
+/***********************************************************************/
+/* External function. */
+/***********************************************************************/
+bool ExactInfo(void);
+
+/* -------------------------- Class ODBCDEF -------------------------- */
+
+/***********************************************************************/
+/* Constructor. */
+/***********************************************************************/
+ODBCDEF::ODBCDEF(void)
+{
+ Connect = NULL;
+ Catver = 0;
+ UseCnc = false;
+} // end of ODBCDEF constructor
+
+/***********************************************************************/
+/* DefineAM: define specific AM block values from XDB file. */
+/***********************************************************************/
+bool ODBCDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
+{
+ Desc = Connect = GetStringCatInfo(g, "Connect", NULL);
+
+ if (!Connect && !Catfunc) {
+ sprintf(g->Message, "Missing connection for ODBC table %s", Name);
+ return true;
+ } // endif Connect
+
+ if (EXTDEF::DefineAM(g, am, poff))
+ return true;
+
+ Catver = GetIntCatInfo("Catver", 2);
+ Options = ODBConn::noOdbcDialog;
+//Options = ODBConn::noOdbcDialog | ODBConn::useCursorLib;
+ Cto= GetIntCatInfo("ConnectTimeout", DEFAULT_LOGIN_TIMEOUT);
+ Qto= GetIntCatInfo("QueryTimeout", DEFAULT_QUERY_TIMEOUT);
+ UseCnc = GetBoolCatInfo("UseDSN", false);
+ return false;
+} // end of DefineAM
+
+/***********************************************************************/
+/* GetTable: makes a new Table Description Block. */
+/***********************************************************************/
+PTDB ODBCDEF::GetTable(PGLOBAL g, MODE m)
+{
+ PTDB tdbp = NULL;
+
+ /*********************************************************************/
+ /* Allocate a TDB of the proper type. */
+ /* Column blocks will be allocated only when needed. */
+ /*********************************************************************/
+ if (Xsrc)
+ tdbp = new(g) TDBXDBC(this);
+ else switch (Catfunc) {
+ case FNC_COL:
+ tdbp = new(g) TDBOCL(this);
+ break;
+ case FNC_TABLE:
+ tdbp = new(g) TDBOTB(this);
+ break;
+ case FNC_DSN:
+ tdbp = new(g) TDBSRC(this);
+ break;
+ case FNC_DRIVER:
+ tdbp = new(g) TDBDRV(this);
+ break;
+ default:
+ tdbp = new(g) TDBODBC(this);
+
+ if (Multiple == 1)
+ tdbp = new(g) TDBMUL(tdbp);
+ else if (Multiple == 2)
+ strcpy(g->Message, MSG(NO_ODBC_MUL));
+ } // endswitch Catfunc
+
+ return tdbp;
+} // end of GetTable
+
+/* -------------------------- Class TDBODBC -------------------------- */
+
+/***********************************************************************/
+/* Implementation of the TDBODBC class. */
+/***********************************************************************/
+TDBODBC::TDBODBC(PODEF tdp) : TDBEXT(tdp)
+{
+ Ocp = NULL;
+ Cnp = NULL;
+
+ if (tdp) {
+ Connect = tdp->Connect;
+ Ops.User = tdp->Username;
+ Ops.Pwd = tdp->Password;
+ Ops.Cto = tdp->Cto;
+ Ops.Qto = tdp->Qto;
+ Catver = tdp->Catver;
+ Ops.UseCnc = tdp->UseCnc;
+ } else {
+ Connect = NULL;
+ Ops.User = NULL;
+ Ops.Pwd = NULL;
+ Ops.Cto = DEFAULT_LOGIN_TIMEOUT;
+ Ops.Qto = DEFAULT_QUERY_TIMEOUT;
+ Catver = 0;
+ Ops.UseCnc = false;
+ } // endif tdp
+
+} // end of TDBODBC standard constructor
+
+TDBODBC::TDBODBC(PTDBODBC tdbp) : TDBEXT(tdbp)
+{
+ Ocp = tdbp->Ocp; // is that right ?
+ Cnp = tdbp->Cnp;
+ Connect = tdbp->Connect;
+ Ops = tdbp->Ops;
+} // end of TDBODBC copy constructor
+
+// Method
+PTDB TDBODBC::Clone(PTABS t)
+{
+ PTDB tp;
+ PODBCCOL cp1, cp2;
+ PGLOBAL g = t->G; // Is this really useful ???
+
+ tp = new(g) TDBODBC(this);
+
+ for (cp1 = (PODBCCOL)Columns; cp1; cp1 = (PODBCCOL)cp1->GetNext()) {
+ cp2 = new(g) ODBCCOL(cp1, tp); // Make a copy
+ NewPointer(t, cp1, cp2);
+ } // endfor cp1
+
+ return tp;
+} // end of CopyOne
+
+/***********************************************************************/
+/* Allocate ODBC column description block. */
+/***********************************************************************/
+PCOL TDBODBC::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
+{
+ return new(g) ODBCCOL(cdp, this, cprec, n);
+} // end of MakeCol
+
+/***********************************************************************/
+/* Extract the filename from connect string and return it. */
+/* This used for Multiple(1) tables. Also prepare a connect string */
+/* with a place holder to be used by SetFile. */
+/***********************************************************************/
+PCSZ TDBODBC::GetFile(PGLOBAL g)
+{
+ if (Connect) {
+ char *p1, *p2;
+ int i;
+ size_t n;
+
+ if (!(p1 = strstr(Connect, "DBQ="))) {
+ char *p, *lc = strlwr(PlugDup(g, Connect));
+
+ if ((p = strstr(lc, "database=")))
+ p1 = Connect + (p - lc);
+
+ i = 9;
+ } else
+ i = 4;
+
+ if (p1) {
+ p1 += i; // Beginning of file name
+ p2 = strchr(p1, ';'); // End of file path/name
+
+ // Make the File path/name from the connect string
+ n = (p2) ? p2 - p1 : strlen(p1);
+ DBQ = (PSZ)PlugSubAlloc(g, NULL, n + 1);
+ memcpy(DBQ, p1, n);
+ DBQ[n] = '\0';
+
+ // Make the Format used to re-generate Connect (3 = "%s" + 1)
+ MulConn = (char*)PlugSubAlloc(g, NULL, strlen(Connect) - n + 3);
+ memcpy(MulConn, Connect, p1 - Connect);
+ MulConn[p1 - Connect] = '\0';
+ strcat(strcat(MulConn, "%s"), (p2) ? p2 : ";");
+ } // endif p1
+
+ } // endif Connect
+
+ return (DBQ) ? DBQ : (PSZ)"???";
+} // end of GetFile
+
+/***********************************************************************/
+/* Set DBQ and get the new file name into the connect string. */
+/***********************************************************************/
+void TDBODBC::SetFile(PGLOBAL g, PCSZ fn)
+{
+ if (MulConn) {
+ int n = strlen(MulConn) + strlen(fn) - 1;
+
+ if (n > BufSize) {
+ // Allocate a buffer larger than needed so the chance
+ // of having to reallocate it is reduced.
+ BufSize = n + 6;
+ Connect = (char*)PlugSubAlloc(g, NULL, BufSize);
+ } // endif n
+
+ // Make the complete connect string
+ sprintf(Connect, MulConn, fn);
+ } // endif MultConn
+
+ DBQ = PlugDup(g, fn);
+} // end of SetFile
+
+/***********************************************************************/
+/* MakeInsert: make the Insert statement used with ODBC connection. */
+/***********************************************************************/
+bool TDBODBC::MakeInsert(PGLOBAL g)
+{
+ PCSZ schmp = NULL;
+ char *catp = NULL, buf[NAM_LEN * 3];
+ int len = 0;
+ bool oom, b = false;
+ PCOL colp;
+
+ for (colp = Columns; colp; colp = colp->GetNext())
+ if (colp->IsSpecial()) {
+ strcpy(g->Message, MSG(NO_ODBC_SPECOL));
+ return true;
+ } else {
+ // Column name can be encoded in UTF-8
+ Decode(colp->GetName(), buf, sizeof(buf));
+ len += (strlen(buf) + 6); // comma + quotes + valist
+ ((PEXTCOL)colp)->SetRank(++Nparm);
+ } // endif colp
+
+ // Below 32 is enough to contain the fixed part of the query
+ if (Catalog && *Catalog)
+ catp = Catalog;
+
+ if (catp)
+ len += strlen(catp) + 1;
+
+ if (Schema && *Schema)
+ schmp = Schema;
+
+ if (schmp)
+ len += strlen(schmp) + 1;
+
+ // Table name can be encoded in UTF-8
+ Decode(TableName, buf, sizeof(buf));
+ len += (strlen(buf) + 32);
+ Query = new(g) STRING(g, len, "INSERT INTO ");
+
+ if (catp) {
+ Query->Append(catp);
+
+ if (schmp) {
+ Query->Append('.');
+ Query->Append(schmp);
+ } // endif schmp
+
+ Query->Append('.');
+ } else if (schmp) {
+ Query->Append(schmp);
+ Query->Append('.');
+ } // endif schmp
+
+ if (Quote) {
+ // Put table name between identifier quotes in case in contains blanks
+ Query->Append(Quote);
+ Query->Append(buf);
+ Query->Append(Quote);
+ } else
+ Query->Append(buf);
+
+ Query->Append('(');
+
+ for (colp = Columns; colp; colp = colp->GetNext()) {
+ if (b)
+ Query->Append(", ");
+ else
+ b = true;
+
+ // Column name can be in UTF-8 encoding
+ Decode(colp->GetName(), buf, sizeof(buf));
+
+ if (Quote) {
+ // Put column name between identifier quotes in case in contains blanks
+ Query->Append(Quote);
+ Query->Append(buf);
+ Query->Append(Quote);
+ } else
+ Query->Append(buf);
+
+ } // endfor colp
+
+ Query->Append(") VALUES (");
+
+ for (int i = 0; i < Nparm; i++)
+ Query->Append("?,");
+
+ if ((oom = Query->IsTruncated()))
+ strcpy(g->Message, "MakeInsert: Out of memory");
+ else
+ Query->RepLast(')');
+
+ return oom;
+} // end of MakeInsert
+
+/***********************************************************************/
+/* ODBC Bind Parameter function. */
+/***********************************************************************/
+bool TDBODBC::BindParameters(PGLOBAL g)
+{
+ PODBCCOL colp;
+
+ for (colp = (PODBCCOL)Columns; colp; colp = (PODBCCOL)colp->Next) {
+ colp->AllocateBuffers(g, 0);
+
+ if (Ocp->BindParam(colp))
+ return true;
+
+ } // endfor colp
+
+ return false;
+} // end of BindParameters
+
+#if 0
+/***********************************************************************/
+/* MakeUpdate: make the SQL statement to send to ODBC connection. */
+/***********************************************************************/
+char *TDBODBC::MakeUpdate(PGLOBAL g)
+{
+ char *qc, *stmt = NULL, cmd[8], tab[96], end[1024];
+
+ stmt = (char*)PlugSubAlloc(g, NULL, strlen(Qrystr) + 64);
+ memset(end, 0, sizeof(end));
+
+ if (sscanf(Qrystr, "%s `%[^`]`%1023c", cmd, tab, end) > 2 ||
+ sscanf(Qrystr, "%s \"%[^\"]\"%1023c", cmd, tab, end) > 2)
+ qc = Ocp->GetQuoteChar();
+ else if (sscanf(Qrystr, "%s %s%1023c", cmd, tab, end) > 2)
+ qc = (Quoted) ? Quote : "";
+ else {
+ strcpy(g->Message, "Cannot use this UPDATE command");
+ return NULL;
+ } // endif sscanf
+
+ assert(!stricmp(cmd, "update"));
+ strcat(strcat(strcat(strcpy(stmt, "UPDATE "), qc), TableName), qc);
+
+ for (int i = 0; end[i]; i++)
+ if (end[i] == '`')
+ end[i] = *qc;
+
+ strcat(stmt, end);
+ return stmt;
+} // end of MakeUpdate
+
+/***********************************************************************/
+/* MakeDelete: make the SQL statement to send to ODBC connection. */
+/***********************************************************************/
+char *TDBODBC::MakeDelete(PGLOBAL g)
+{
+ char *qc, *stmt = NULL, cmd[8], from[8], tab[96], end[512];
+
+ stmt = (char*)PlugSubAlloc(g, NULL, strlen(Qrystr) + 64);
+ memset(end, 0, sizeof(end));
+
+ if (sscanf(Qrystr, "%s %s `%[^`]`%511c", cmd, from, tab, end) > 2 ||
+ sscanf(Qrystr, "%s %s \"%[^\"]\"%511c", cmd, from, tab, end) > 2)
+ qc = Ocp->GetQuoteChar();
+ else if (sscanf(Qrystr, "%s %s %s%511c", cmd, from, tab, end) > 2)
+ qc = (Quoted) ? Quote : "";
+ else {
+ strcpy(g->Message, "Cannot use this DELETE command");
+ return NULL;
+ } // endif sscanf
+
+ assert(!stricmp(cmd, "delete") && !stricmp(from, "from"));
+ strcat(strcat(strcat(strcpy(stmt, "DELETE FROM "), qc), TableName), qc);
+
+ if (*end) {
+ for (int i = 0; end[i]; i++)
+ if (end[i] == '`')
+ end[i] = *qc;
+
+ strcat(stmt, end);
+ } // endif end
+
+ return stmt;
+} // end of MakeDelete
+#endif // 0
+
+/***********************************************************************/
+/* ResetSize: call by TDBMUL when calculating size estimate. */
+/***********************************************************************/
+void TDBODBC::ResetSize(void)
+{
+ MaxSize = -1;
+
+ if (Ocp && Ocp->IsOpen())
+ Ocp->Close();
+
+} // end of ResetSize
+
+/***********************************************************************/
+/* ODBC Cardinality: returns table size in number of rows. */
+/***********************************************************************/
+int TDBODBC::Cardinality(PGLOBAL g)
+{
+ if (!g)
+ return (Mode == MODE_ANY && !Srcdef) ? 1 : 0;
+
+ if (Cardinal < 0 && Mode == MODE_ANY && !Srcdef && ExactInfo()) {
+ // Info command, we must return the exact table row number
+ char qry[96], tbn[64];
+ ODBConn *ocp = new(g) ODBConn(g, this);
+
+ if (ocp->Open(Connect, &Ops, Options) < 1)
+ return -1;
+
+ // Table name can be encoded in UTF-8
+ Decode(TableName, tbn, sizeof(tbn));
+ strcpy(qry, "SELECT COUNT(*) FROM ");
+
+ if (Quote)
+ strcat(strcat(strcat(qry, Quote), tbn), Quote);
+ else
+ strcat(qry, tbn);
+
+ // Allocate a Count(*) column (must not use the default constructor)
+ Cnp = new(g) ODBCCOL;
+ Cnp->InitValue(g);
+
+ if ((Cardinal = ocp->GetResultSize(qry, Cnp)) < 0)
+ return -3;
+
+ ocp->Close();
+ } else
+ Cardinal = 10; // To make MySQL happy
+
+ return Cardinal;
+} // end of Cardinality
+
+/***********************************************************************/
+/* ODBC Access Method opening routine. */
+/* New method now that this routine is called recursively (last table */
+/* first in reverse order): index blocks are immediately linked to */
+/* join block of next table if it exists or else are discarted. */
+/***********************************************************************/
+bool TDBODBC::OpenDB(PGLOBAL g)
+{
+ bool rc = true;
+
+ if (trace(1))
+ htrc("ODBC OpenDB: tdbp=%p tdb=R%d use=%dmode=%d\n",
+ this, Tdb_No, Use, Mode);
+
+ if (Use == USE_OPEN) {
+ /*******************************************************************/
+ /* Table already open, just replace it at its beginning. */
+ /*******************************************************************/
+ if (Memory == 1) {
+ if ((Qrp = Ocp->AllocateResult(g)))
+ Memory = 2; // Must be filled
+ else
+ Memory = 0; // Allocation failed, don't use it
+
+ } else if (Memory == 2)
+ Memory = 3; // Ok to use memory result
+
+ if (Memory < 3) {
+ // Method will depend on cursor type
+ if ((Rbuf = Ocp->Rewind(Query->GetStr(), (PODBCCOL)Columns)) < 0) {
+ if (Mode != MODE_READX) {
+ Ocp->Close();
+ return true;
+ } else {
+ Rbuf = 0;
+ }
+ }
+ } else {
+ Rbuf = Qrp->Nblin;
+ }
+
+ CurNum = 0;
+ Fpos = 0;
+ Curpos = 1;
+ return false;
+ } // endif use
+
+ /*********************************************************************/
+ /* Open an ODBC connection for this table. */
+ /* Note: this may not be the proper way to do. Perhaps it is better */
+ /* to test whether a connection is already open for this datasource */
+ /* and if so to allocate just a new result set. But this only for */
+ /* drivers allowing concurency in getting results ??? */
+ /*********************************************************************/
+ if (!Ocp)
+ Ocp = new(g) ODBConn(g, this);
+ else if (Ocp->IsOpen())
+ Ocp->Close();
+
+ if (Ocp->Open(Connect, &Ops, Options) < 1)
+ return true;
+ else if (Quoted)
+ Quote = Ocp->GetQuoteChar();
+
+ Use = USE_OPEN; // Do it now in case we are recursively called
+
+ /*********************************************************************/
+ /* Make the command and allocate whatever is used for getting results*/
+ /*********************************************************************/
+ if (Mode == MODE_READ || Mode == MODE_READX) {
+ if (Memory > 1 && !Srcdef) {
+ int n;
+
+ if (!MakeSQL(g, true)) {
+ // Allocate a Count(*) column
+ Cnp = new(g) ODBCCOL;
+ Cnp->InitValue(g);
+
+ if ((n = Ocp->GetResultSize(Query->GetStr(), Cnp)) < 0) {
+ char* msg = PlugDup(g, g->Message);
+
+ sprintf(g->Message, "Get result size: %s (rc=%d)", msg, n);
+ return true;
+ } else if (n) {
+ Ocp->m_Rows = n;
+
+ if ((Qrp = Ocp->AllocateResult(g)))
+ Memory = 2; // Must be filled
+ else {
+ strcpy(g->Message, "Result set memory allocation failed");
+ return true;
+ } // endif n
+
+ } else // Void result
+ Memory = 0;
+
+ Ocp->m_Rows = 0;
+ } else
+ return true;
+
+ } // endif Memory
+
+ if (!(rc = MakeSQL(g, false))) {
+ for (PODBCCOL colp = (PODBCCOL)Columns; colp;
+ colp = (PODBCCOL)colp->GetNext())
+ if (!colp->IsSpecial())
+ colp->AllocateBuffers(g, Rows);
+
+ rc = (Mode == MODE_READ)
+ ? ((Rows = Ocp->ExecDirectSQL(Query->GetStr(), (PODBCCOL)Columns)) < 0)
+ : false;
+ } // endif rc
+
+ } else if (Mode == MODE_INSERT) {
+ if (!(rc = MakeInsert(g))) {
+ if (Nparm != Ocp->PrepareSQL(Query->GetStr())) {
+ strcpy(g->Message, MSG(PARM_CNT_MISS));
+ rc = true;
+ } else
+ rc = BindParameters(g);
+
+ } // endif rc
+
+ } else if (Mode == MODE_UPDATE || Mode == MODE_DELETE) {
+ rc = false; // wait for CheckCond before calling MakeCommand(g);
+ } else
+ sprintf(g->Message, "Invalid mode %d", Mode);
+
+ if (rc) {
+ Ocp->Close();
+ return true;
+ } // endif rc
+
+ /*********************************************************************/
+ /* Reset statistics values. */
+ /*********************************************************************/
+ num_read = num_there = num_eq[0] = num_eq[1] = 0;
+ return false;
+} // end of OpenDB
+
+#if 0
+/***********************************************************************/
+/* GetRecpos: return the position of last read record. */
+/***********************************************************************/
+int TDBODBC::GetRecpos(void)
+{
+ return Fpos;
+} // end of GetRecpos
+#endif // 0
+
+/***********************************************************************/
+/* SetRecpos: set the position of next read record. */
+/***********************************************************************/
+bool TDBODBC::SetRecpos(PGLOBAL g, int recpos)
+{
+ if (Ocp->m_Full) {
+ Fpos = 0;
+ CurNum = recpos - 1;
+ } else if (Memory == 3) {
+ Fpos = recpos;
+ CurNum = -1;
+ } else if (Scrollable) {
+ // Is new position in the current row set?
+ if (recpos >= Curpos && recpos < Curpos + Rbuf) {
+ CurNum = recpos - Curpos;
+ Fpos = 0;
+ } else {
+ Fpos = recpos;
+ CurNum = 0;
+ } // endif recpos
+
+ } else {
+ strcpy(g->Message,
+ "This action requires Memory setting or a scrollable cursor");
+ return true;
+ } // endif's
+
+ // Indicate the table position was externally set
+ Placed = true;
+ return false;
+} // end of SetRecpos
+
+/***********************************************************************/
+/* Data Base indexed read routine for ODBC access method. */
+/***********************************************************************/
+bool TDBODBC::ReadKey(PGLOBAL g, OPVAL op, const key_range *kr)
+{
+ char c = Quote ? *Quote : 0;
+ int oldlen = Query->GetLength();
+ PHC hc = To_Def->GetHandler();
+
+ if (!(kr || hc->end_range) || op == OP_NEXT ||
+ Mode == MODE_UPDATE || Mode == MODE_DELETE) {
+ if (!kr && Mode == MODE_READX) {
+ // This is a false indexed read
+ Rows = Ocp->ExecDirectSQL((char*)Query->GetStr(), (PODBCCOL)Columns);
+ Mode = MODE_READ;
+ return (Rows < 0);
+ } // endif key
+
+ return false;
+ } else {
+ if (hc->MakeKeyWhere(g, Query, op, c, kr))
+ return true;
+
+ if (To_CondFil) {
+ if (To_CondFil->Idx != hc->active_index) {
+ To_CondFil->Idx = hc->active_index;
+ To_CondFil->Body= (char*)PlugSubAlloc(g, NULL, 0);
+ *To_CondFil->Body= 0;
+
+ if ((To_CondFil = hc->CheckCond(g, To_CondFil, Cond)))
+ PlugSubAlloc(g, NULL, strlen(To_CondFil->Body) + 1);
+
+ } // endif active_index
+
+ if (To_CondFil)
+ if (Query->Append(" AND ") || Query->Append(To_CondFil->Body)) {
+ strcpy(g->Message, "Readkey: Out of memory");
+ return true;
+ } // endif Append
+
+ } // endif To_Condfil
+
+ Mode = MODE_READ;
+ } // endif's op
+
+ if (trace(33))
+ htrc("ODBC ReadKey: Query=%s\n", Query->GetStr());
+
+ Rows = Ocp->ExecDirectSQL((char*)Query->GetStr(), (PODBCCOL)Columns);
+ Query->Truncate(oldlen);
+ return (Rows < 0);
+} // end of ReadKey
+
+/***********************************************************************/
+/* VRDNDOS: Data Base read routine for odbc access method. */
+/***********************************************************************/
+int TDBODBC::ReadDB(PGLOBAL g)
+{
+ int rc;
+
+ if (trace(2))
+ htrc("ODBC ReadDB: R%d Mode=%d\n", GetTdb_No(), Mode);
+
+ if (Mode == MODE_UPDATE || Mode == MODE_DELETE) {
+ if (!Query && MakeCommand(g))
+ return RC_FX;
+
+ // Send the UPDATE/DELETE command to the remote table
+ if (!Ocp->ExecSQLcommand(Query->GetStr())) {
+ sprintf(g->Message, "%s: %d affected rows", TableName, AftRows);
+
+ if (trace(1))
+ htrc("%s\n", g->Message);
+
+ PushWarning(g, this, 0); // 0 means a Note
+ return RC_EF; // Nothing else to do
+ } else
+ return RC_FX; // Error
+
+ } // endif Mode
+
+ /*********************************************************************/
+ /* Now start the reading process. */
+ /* Here is the place to fetch the line(s). */
+ /*********************************************************************/
+ if (Placed) {
+ if (Fpos && CurNum >= 0)
+ Rbuf = Ocp->Fetch((Curpos = Fpos));
+
+ rc = (Rbuf > 0) ? RC_OK : (Rbuf == 0) ? RC_EF : RC_FX;
+ Placed = false;
+ } else {
+ if (Memory != 3) {
+ if (++CurNum >= Rbuf) {
+ Rbuf = Ocp->Fetch();
+ Curpos = Fpos + 1;
+ CurNum = 0;
+ } // endif CurNum
+
+ rc = (Rbuf > 0) ? RC_OK : (Rbuf == 0) ? RC_EF : RC_FX;
+ } else // Getting result from memory
+ rc = (Fpos < Qrp->Nblin) ? RC_OK : RC_EF;
+
+ if (rc == RC_OK) {
+ if (Memory == 2)
+ Qrp->Nblin++;
+
+ Fpos++; // Used for memory and pos
+ } // endif rc
+
+ } // endif Placed
+
+ if (trace(2))
+ htrc(" Read: Rbuf=%d rc=%d\n", Rbuf, rc);
+
+ return rc;
+} // end of ReadDB
+
+/***********************************************************************/
+/* Data Base Insert write routine for ODBC access method. */
+/***********************************************************************/
+int TDBODBC::WriteDB(PGLOBAL g)
+{
+ int n = Ocp->ExecuteSQL();
+
+ if (n < 0) {
+ AftRows = n;
+ return RC_FX;
+ } else
+ AftRows += n;
+
+ return RC_OK;
+} // end of WriteDB
+
+/***********************************************************************/
+/* Data Base delete line routine for ODBC access method. */
+/***********************************************************************/
+int TDBODBC::DeleteDB(PGLOBAL g, int irc)
+{
+ if (irc == RC_FX) {
+ if (!Query && MakeCommand(g))
+ return RC_FX;
+
+ // Send the DELETE (all) command to the remote table
+ if (!Ocp->ExecSQLcommand(Query->GetStr())) {
+ sprintf(g->Message, "%s: %d affected rows", TableName, AftRows);
+
+ if (trace(1))
+ htrc("%s\n", g->Message);
+
+ PushWarning(g, this, 0); // 0 means a Note
+ return RC_OK; // This is a delete all
+ } else
+ return RC_FX; // Error
+
+ } else
+ return RC_OK; // Ignore
+
+} // end of DeleteDB
+
+/***********************************************************************/
+/* Data Base close routine for ODBC access method. */
+/***********************************************************************/
+void TDBODBC::CloseDB(PGLOBAL g)
+{
+ if (Ocp)
+
+ Ocp->Close();
+
+ if (trace(1))
+ htrc("ODBC CloseDB: closing %s\n", Name);
+
+} // end of CloseDB
+
+/* --------------------------- ODBCCOL ------------------------------- */
+
+/***********************************************************************/
+/* ODBCCOL public constructor. */
+/***********************************************************************/
+ODBCCOL::ODBCCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PCSZ am)
+ : EXTCOL(cdp, tdbp, cprec, i, am)
+{
+ // Set additional ODBC access method information for column.
+ Slen = 0;
+ StrLen = &Slen;
+ Sqlbuf = NULL;
+} // end of ODBCCOL constructor
+
+/***********************************************************************/
+/* ODBCCOL private constructor. */
+/***********************************************************************/
+ODBCCOL::ODBCCOL(void) : EXTCOL()
+{
+ Slen = 0;
+ StrLen = &Slen;
+ Sqlbuf = NULL;
+} // end of ODBCCOL constructor
+
+/***********************************************************************/
+/* ODBCCOL constructor used for copying columns. */
+/* tdbp is the pointer to the new table descriptor. */
+/***********************************************************************/
+ODBCCOL::ODBCCOL(ODBCCOL *col1, PTDB tdbp) : EXTCOL(col1, tdbp)
+{
+ Slen = col1->Slen;
+ StrLen = col1->StrLen;
+ Sqlbuf = col1->Sqlbuf;
+} // end of ODBCCOL copy constructor
+
+/***********************************************************************/
+/* ReadColumn: when SQLFetch is used there is nothing to do as the */
+/* column buffer was bind to the record set. This is also the case */
+/* when calculating MaxSize (Bufp is NULL even when Rows is not). */
+/***********************************************************************/
+void ODBCCOL::ReadColumn(PGLOBAL g)
+{
+ PTDBODBC tdbp = (PTDBODBC)To_Tdb;
+ int i = tdbp->Fpos - 1, n = tdbp->CurNum;
+
+ if (tdbp->Memory == 3) {
+ // Get the value from the stored memory
+ if (Crp->Nulls && Crp->Nulls[i] == '*') {
+ Value->Reset();
+ Value->SetNull(true);
+ } else {
+ Value->SetValue_pvblk(Crp->Kdata, i);
+ Value->SetNull(false);
+ } // endif Nulls
+
+ return;
+ } // endif Memory
+
+ if (StrLen[n] == SQL_NULL_DATA) {
+ // Null value
+ if (Nullable)
+ Value->SetNull(true);
+
+ Value->Reset();
+ goto put;
+ } else
+ Value->SetNull(false);
+
+ if (Bufp && tdbp->Rows) {
+ if (Buf_Type == TYPE_DATE)
+ *Sqlbuf = ((TIMESTAMP_STRUCT*)Bufp)[n];
+ else
+ Value->SetValue_pvblk(Blkp, n);
+
+ } // endif Bufp
+
+ if (Buf_Type == TYPE_DATE) {
+ struct tm dbtime;
+
+ memset(&dbtime, 0, sizeof(tm));
+ dbtime.tm_sec = (int)Sqlbuf->second;
+ dbtime.tm_min = (int)Sqlbuf->minute;
+ dbtime.tm_hour = (int)Sqlbuf->hour;
+ dbtime.tm_mday = (int)Sqlbuf->day;
+ dbtime.tm_mon = (int)Sqlbuf->month - 1;
+ dbtime.tm_year = (int)Sqlbuf->year - 1900;
+ ((DTVAL*)Value)->MakeTime(&dbtime);
+ } else if (Buf_Type == TYPE_DECIM && tdbp->Sep) {
+ // Be sure to use decimal point
+ char *p = strchr(Value->GetCharValue(), tdbp->Sep);
+
+ if (p)
+ *p = '.';
+
+ } // endif Buf_Type
+
+ if (trace(2)) {
+ char buf[64];
+
+ htrc("ODBC Column %s: rows=%d buf=%p type=%d value=%s\n",
+ Name, tdbp->Rows, Bufp, Buf_Type, Value->GetCharString(buf));
+ } // endif trace
+
+ put:
+ if (tdbp->Memory != 2)
+ return;
+
+ /*********************************************************************/
+ /* Fill the allocated result structure. */
+ /*********************************************************************/
+ if (Value->IsNull()) {
+ if (Crp->Nulls)
+ Crp->Nulls[i] = '*'; // Null value
+
+ Crp->Kdata->Reset(i);
+ } else
+ Crp->Kdata->SetValue(Value, i);
+
+} // end of ReadColumn
+
+/***********************************************************************/
+/* AllocateBuffers: allocate the extended buffer for SQLExtendedFetch */
+/* or Fetch. Note: we use Long+1 here because ODBC must have space */
+/* for the ending null character. */
+/***********************************************************************/
+void ODBCCOL::AllocateBuffers(PGLOBAL g, int rows)
+{
+ if (Buf_Type == TYPE_DATE)
+ Sqlbuf = (TIMESTAMP_STRUCT*)PlugSubAlloc(g, NULL,
+ sizeof(TIMESTAMP_STRUCT));
+
+ if (!rows)
+ return;
+
+ if (Buf_Type == TYPE_DATE)
+ Bufp = PlugSubAlloc(g, NULL, rows * sizeof(TIMESTAMP_STRUCT));
+ else {
+ Blkp = AllocValBlock(g, NULL, Buf_Type, rows, GetBuflen(),
+ GetScale(), true, false, false);
+ Bufp = Blkp->GetValPointer();
+ } // endelse
+
+ if (rows > 1)
+ StrLen = (SQLLEN *)PlugSubAlloc(g, NULL, rows * sizeof(SQLLEN));
+
+} // end of AllocateBuffers
+
+/***********************************************************************/
+/* Returns the buffer to use for Fetch or Extended Fetch. */
+/***********************************************************************/
+void *ODBCCOL::GetBuffer(DWORD rows)
+{
+ if (rows && To_Tdb) {
+ assert(rows == (DWORD)((TDBODBC*)To_Tdb)->Rows);
+ return Bufp;
+ } else
+ return (Buf_Type == TYPE_DATE) ? Sqlbuf : Value->GetTo_Val();
+
+} // end of GetBuffer
+
+/***********************************************************************/
+/* Returns the buffer length to use for Fetch or Extended Fetch. */
+/***********************************************************************/
+SWORD ODBCCOL::GetBuflen(void)
+{
+ SWORD flen;
+
+ switch (Buf_Type) {
+ case TYPE_DATE:
+ flen = (SWORD)sizeof(TIMESTAMP_STRUCT);
+ break;
+ case TYPE_STRING:
+ case TYPE_DECIM:
+ flen = (SWORD)Value->GetClen() + 1;
+ break;
+ default:
+ flen = (SWORD)Value->GetClen();
+ } // endswitch Buf_Type
+
+ return flen;
+} // end of GetBuflen
+
+/***********************************************************************/
+/* WriteColumn: make sure the bind buffer is updated. */
+/***********************************************************************/
+void ODBCCOL::WriteColumn(PGLOBAL g)
+{
+ /*********************************************************************/
+ /* Do convert the column value if necessary. */
+ /*********************************************************************/
+ if (Value != To_Val)
+ Value->SetValue_pval(To_Val, FALSE); // Convert the inserted value
+
+ if (Buf_Type == TYPE_DATE) {
+ struct tm tm, *dbtime = ((DTVAL*)Value)->GetGmTime(&tm);
+
+ Sqlbuf->second = dbtime->tm_sec;
+ Sqlbuf->minute = dbtime->tm_min;
+ Sqlbuf->hour = dbtime->tm_hour;
+ Sqlbuf->day = dbtime->tm_mday;
+ Sqlbuf->month = dbtime->tm_mon + 1;
+ Sqlbuf->year = dbtime->tm_year + 1900;
+ Sqlbuf->fraction = 0;
+ } else if (Buf_Type == TYPE_DECIM) {
+ // Some data sources require local decimal separator
+ char *p, sep = ((PTDBODBC)To_Tdb)->Sep;
+
+ if (sep && (p = strchr(Value->GetCharValue(), '.')))
+ *p = sep;
+
+ } // endif Buf_Type
+
+ if (Nullable)
+ *StrLen = (Value->IsNull()) ? SQL_NULL_DATA :
+ (IsTypeChar(Buf_Type)) ? SQL_NTS : 0;
+
+} // end of WriteColumn
+
+/* -------------------------- Class TDBXDBC -------------------------- */
+
+/***********************************************************************/
+/* Implementation of the TDBXDBC class. */
+/***********************************************************************/
+TDBXDBC::TDBXDBC(PODEF tdp) : TDBODBC(tdp)
+{
+ Cmdlist = NULL;
+ Cmdcol = NULL;
+ Mxr = tdp->Maxerr;
+ Nerr = 0;
+} // end of TDBXDBC constructor
+
+TDBXDBC::TDBXDBC(PTDBXDBC tdbp) : TDBODBC(tdbp)
+{
+ Cmdlist = tdbp->Cmdlist;
+ Cmdcol = tdbp->Cmdcol;
+ Mxr = tdbp->Mxr;
+ Nerr = tdbp->Nerr;
+} // end of TDBXDBC copy constructor
+
+PTDB TDBXDBC::Clone(PTABS t)
+{
+ PTDB tp;
+ PXSRCCOL cp1, cp2;
+ PGLOBAL g = t->G; // Is this really useful ???
+
+ tp = new(g) TDBXDBC(this);
+
+ for (cp1 = (PXSRCCOL)Columns; cp1; cp1 = (PXSRCCOL)cp1->GetNext()) {
+ cp2 = new(g) XSRCCOL(cp1, tp); // Make a copy
+ NewPointer(t, cp1, cp2);
+ } // endfor cp1
+
+ return tp;
+} // end of CopyOne
+
+/***********************************************************************/
+/* Allocate XSRC column description block. */
+/***********************************************************************/
+PCOL TDBXDBC::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
+{
+ PXSRCCOL colp = new(g) XSRCCOL(cdp, this, cprec, n);
+
+ if (!colp->Flag)
+ Cmdcol = colp->GetName();
+
+ return colp;
+} // end of MakeCol
+
+/***********************************************************************/
+/* MakeCMD: make the SQL statement to send to ODBC connection. */
+/***********************************************************************/
+PCMD TDBXDBC::MakeCMD(PGLOBAL g)
+{
+ PCMD xcmd = NULL;
+
+ if (To_CondFil) {
+ if (Cmdcol) {
+ if (!stricmp(Cmdcol, To_CondFil->Body) &&
+ (To_CondFil->Op == OP_EQ || To_CondFil->Op == OP_IN)) {
+ xcmd = To_CondFil->Cmds;
+ } else
+ strcpy(g->Message, "Invalid command specification filter");
+
+ } else
+ strcpy(g->Message, "No command column in select list");
+
+ } else if (!Srcdef)
+ strcpy(g->Message, "No Srcdef default command");
+ else
+ xcmd = new(g) CMD(g, Srcdef);
+
+ return xcmd;
+} // end of MakeCMD
+
+#if 0
+/***********************************************************************/
+/* ODBC Bind Parameter function. */
+/***********************************************************************/
+bool TDBXDBC::BindParameters(PGLOBAL g)
+{
+ PODBCCOL colp;
+
+ for (colp = (PODBCCOL)Columns; colp; colp = (PODBCCOL)colp->Next) {
+ colp->AllocateBuffers(g, 0);
+
+ if (Ocp->BindParam(colp))
+ return true;
+
+ } // endfor colp
+
+ return false;
+} // end of BindParameters
+#endif // 0
+
+/***********************************************************************/
+/* XDBC GetMaxSize: returns table size (not always one row). */
+/***********************************************************************/
+int TDBXDBC::GetMaxSize(PGLOBAL g)
+{
+ if (MaxSize < 0)
+ MaxSize = 10; // Just a guess
+
+ return MaxSize;
+} // end of GetMaxSize
+
+/***********************************************************************/
+/* ODBC Access Method opening routine. */
+/* New method now that this routine is called recursively (last table */
+/* first in reverse order): index blocks are immediately linked to */
+/* join block of next table if it exists or else are discarted. */
+/***********************************************************************/
+bool TDBXDBC::OpenDB(PGLOBAL g)
+{
+ if (trace(1))
+ htrc("ODBC OpenDB: tdbp=%p tdb=R%d use=%dmode=%d\n",
+ this, Tdb_No, Use, Mode);
+
+ if (Use == USE_OPEN) {
+ strcpy(g->Message, "Multiple execution is not allowed");
+ return true;
+ } // endif use
+
+ /*********************************************************************/
+ /* Open an ODBC connection for this table. */
+ /* Note: this may not be the proper way to do. Perhaps it is better */
+ /* to test whether a connection is already open for this datasource */
+ /* and if so to allocate just a new result set. But this only for */
+ /* drivers allowing concurency in getting results ??? */
+ /*********************************************************************/
+ if (!Ocp) {
+ Ocp = new(g) ODBConn(g, this);
+ } else if (Ocp->IsOpen())
+ Ocp->Close();
+
+ if (Ocp->Open(Connect, &Ops, Options) < 1)
+ return true;
+
+ Use = USE_OPEN; // Do it now in case we are recursively called
+
+ if (Mode != MODE_READ && Mode != MODE_READX) {
+ strcpy(g->Message, "No INSERT/DELETE/UPDATE of XDBC tables");
+ return true;
+ } // endif Mode
+
+ /*********************************************************************/
+ /* Get the command to execute. */
+ /*********************************************************************/
+ if (!(Cmdlist = MakeCMD(g))) {
+ // Next lines commented out because of CHECK TABLE
+ //Ocp->Close();
+ //return true;
+ } // endif Cmdlist
+
+ Rows = 1;
+ return false;
+} // end of OpenDB
+
+/***********************************************************************/
+/* ReadDB: Data Base read routine for xdbc access method. */
+/***********************************************************************/
+int TDBXDBC::ReadDB(PGLOBAL g)
+{
+ if (Cmdlist) {
+ if (!Query)
+ Query = new(g)STRING(g, 0, Cmdlist->Cmd);
+ else
+ Query->Set(Cmdlist->Cmd);
+
+ if (Ocp->ExecSQLcommand(Query->GetStr()))
+ Nerr++;
+
+ Fpos++; // Used for progress info
+ Cmdlist = (Nerr > Mxr) ? NULL : Cmdlist->Next;
+ return RC_OK;
+ } else {
+ PushWarning(g, this, 1);
+ return RC_EF;
+ } // endif Cmdlist
+
+} // end of ReadDB
+
+/***********************************************************************/
+/* Data Base write line routine for XDBC access method. */
+/***********************************************************************/
+int TDBXDBC::WriteDB(PGLOBAL g)
+{
+ strcpy(g->Message, "Execsrc tables are read only");
+ return RC_FX;
+} // end of DeleteDB
+
+/***********************************************************************/
+/* Data Base delete line routine for XDBC access method. */
+/***********************************************************************/
+int TDBXDBC::DeleteDB(PGLOBAL g, int irc)
+{
+ strcpy(g->Message, MSG(NO_ODBC_DELETE));
+ return RC_FX;
+} // end of DeleteDB
+
+/* --------------------------- XSRCCOL ------------------------------- */
+
+/***********************************************************************/
+/* XSRCCOL public constructor. */
+/***********************************************************************/
+XSRCCOL::XSRCCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PCSZ am)
+ : ODBCCOL(cdp, tdbp, cprec, i, am)
+{
+ // Set additional ODBC access method information for column.
+ Flag = cdp->GetOffset();
+} // end of XSRCCOL constructor
+
+/***********************************************************************/
+/* XSRCCOL constructor used for copying columns. */
+/* tdbp is the pointer to the new table descriptor. */
+/***********************************************************************/
+XSRCCOL::XSRCCOL(XSRCCOL *col1, PTDB tdbp) : ODBCCOL(col1, tdbp)
+{
+ Flag = col1->Flag;
+} // end of XSRCCOL copy constructor
+
+/***********************************************************************/
+/* ReadColumn: set column value according to Flag. */
+/***********************************************************************/
+void XSRCCOL::ReadColumn(PGLOBAL g)
+{
+ PTDBXDBC tdbp = (PTDBXDBC)To_Tdb;
+
+ switch (Flag) {
+ case 0: Value->SetValue_psz(tdbp->Query->GetStr()); break;
+ case 1: Value->SetValue(tdbp->AftRows); break;
+ case 2: Value->SetValue_psz(g->Message); break;
+ default: Value->SetValue_psz("Invalid Flag"); break;
+ } // endswitch Flag
+
+} // end of ReadColumn
+
+/***********************************************************************/
+/* WriteColumn: Should never be called. */
+/***********************************************************************/
+void XSRCCOL::WriteColumn(PGLOBAL g)
+{
+ // Should never be called
+} // end of WriteColumn
+
+/* ---------------------------TDBDRV class --------------------------- */
+
+/***********************************************************************/
+/* GetResult: Get the list of ODBC drivers. */
+/***********************************************************************/
+PQRYRES TDBDRV::GetResult(PGLOBAL g)
+{
+ return ODBCDrivers(g, Maxres, false);
+} // end of GetResult
+
+/* ---------------------------TDBSRC class --------------------------- */
+
+/***********************************************************************/
+/* GetResult: Get the list of ODBC data sources. */
+/***********************************************************************/
+PQRYRES TDBSRC::GetResult(PGLOBAL g)
+{
+ return ODBCDataSources(g, Maxres, false);
+} // end of GetResult
+
+/* ---------------------------TDBOTB class --------------------------- */
+
+/***********************************************************************/
+/* TDBOTB class constructor. */
+/***********************************************************************/
+TDBOTB::TDBOTB(PODEF tdp) : TDBDRV(tdp)
+{
+ Dsn = tdp->GetConnect();
+ Schema = tdp->GetTabschema();
+ Tab = tdp->GetTabname();
+ Tabtyp = tdp->Tabtyp;
+ Ops.User = tdp->Username;
+ Ops.Pwd = tdp->Password;
+ Ops.Cto = tdp->Cto;
+ Ops.Qto = tdp->Qto;
+ Ops.UseCnc = tdp->UseCnc;
+} // end of TDBOTB constructor
+
+/***********************************************************************/
+/* GetResult: Get the list of ODBC tables. */
+/***********************************************************************/
+PQRYRES TDBOTB::GetResult(PGLOBAL g)
+{
+ return ODBCTables(g, Dsn, Schema, Tab, Tabtyp, Maxres, false, &Ops);
+} // end of GetResult
+
+/* ---------------------------TDBOCL class --------------------------- */
+
+/***********************************************************************/
+/* TDBOCL class constructor. */
+/***********************************************************************/
+TDBOCL::TDBOCL(PODEF tdp) : TDBOTB(tdp)
+{
+ Colpat = tdp->Colpat;
+} // end of TDBOTB constructor
+
+/***********************************************************************/
+/* GetResult: Get the list of ODBC table columns. */
+/***********************************************************************/
+PQRYRES TDBOCL::GetResult(PGLOBAL g)
+{
+ return ODBCColumns(g, Dsn, Schema, Tab, Colpat, Maxres, false, &Ops);
+} // end of GetResult
+
+/* ------------------------ End of Tabodbc --------------------------- */