1 /************* Tabodbc C++ Program Source Code File (.CPP) *************/
2 /* PROGRAM NAME: TABODBC                                               */
3 /* -------------                                                       */
4 /*  Version 3.2                                                        */
5 /*                                                                     */
6 /* COPYRIGHT:                                                          */
7 /* ----------                                                          */
8 /*  (C) Copyright to the author Olivier BERTRAND          2000-2018    */
9 /*                                                                     */
10 /* WHAT THIS PROGRAM DOES:                                             */
11 /* -----------------------                                             */
12 /*  This program are the TABODBC class DB execution routines.          */
13 /*                                                                     */
14 /* WHAT YOU NEED TO COMPILE THIS PROGRAM:                              */
15 /* --------------------------------------                              */
16 /*                                                                     */
17 /*  REQUIRED FILES:                                                    */
18 /*  ---------------                                                    */
19 /*    TABODBC.CPP    - Source code                                     */
20 /*    PLGDBSEM.H     - DB application declaration file                 */
21 /*    TABODBC.H      - TABODBC classes declaration file                */
22 /*    GLOBAL.H       - Global declaration file                         */
23 /*                                                                     */
24 /*  REQUIRED LIBRARIES:                                                */
25 /*  -------------------                                                */
26 /*    Large model C library                                            */
27 /*                                                                     */
28 /*  REQUIRED PROGRAMS:                                                 */
29 /*  ------------------                                                 */
30 /*    IBM, Borland, GNU or Microsoft C++ Compiler and Linker           */
31 /*                                                                     */
32 /***********************************************************************/
33 
34 /***********************************************************************/
35 /*  Include relevant MariaDB header file.                              */
36 /***********************************************************************/
37 #include "my_global.h"
38 #include "sql_class.h"
39 #if defined(_WIN32)
40 #include <io.h>
41 #include <fcntl.h>
42 #if defined(__BORLANDC__)
43 #define __MFC_COMPAT__                   // To define min/max as macro
44 #endif
45 //#include <windows.h>
46 #include <sqltypes.h>
47 #else
48 #if defined(UNIX)
49 #include <errno.h>
50 #define NODW
51 #include "osutil.h"
52 #else
53 #include <io.h>
54 #endif
55 #include <fcntl.h>
56 #endif
57 
58 /***********************************************************************/
59 /*  Include application header files:                                  */
60 /*  global.h    is header containing all global declarations.          */
61 /*  plgdbsem.h  is header containing the DB application declarations.  */
62 /*  kindex.h    is kindex header that also includes tabdos.h.          */
63 /*  tabodbc.h   is header containing the TABODBC class declarations.   */
64 /*  odbconn.h   is header containing ODBC connection declarations.     */
65 /***********************************************************************/
66 #include "global.h"
67 #include "plgdbsem.h"
68 #include "mycat.h"
69 #include "xtable.h"
70 #include "tabext.h"
71 #include "odbccat.h"
72 #include "tabodbc.h"
73 #include "tabmul.h"
74 //#include "reldef.h"
75 #include "tabcol.h"
76 #include "valblk.h"
77 #include "ha_connect.h"
78 
79 #include "sql_string.h"
80 
81 /***********************************************************************/
82 /*  DB static variables.                                               */
83 /***********************************************************************/
84 //     int num_read, num_there, num_eq[2], num_nf;        // Statistics
85 extern int num_read, num_there, num_eq[2];                // Statistics
86 
87 /***********************************************************************/
88 /*  External function.                                                 */
89 /***********************************************************************/
90 bool ExactInfo(void);
91 
92 /* -------------------------- Class ODBCDEF -------------------------- */
93 
94 /***********************************************************************/
95 /*  Constructor.                                                       */
96 /***********************************************************************/
97 ODBCDEF::ODBCDEF(void)
98 {
99   Connect = NULL;
100   Catver = 0;
101   UseCnc = false;
102 }  // end of ODBCDEF constructor
103 
104 /***********************************************************************/
105 /*  DefineAM: define specific AM block values from XDB file.           */
106 /***********************************************************************/
107 bool ODBCDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
108 {
109   Desc = Connect = GetStringCatInfo(g, "Connect", NULL);
110 
111   if (!Connect && !Catfunc) {
112     sprintf(g->Message, "Missing connection for ODBC table %s", Name);
113     return true;
114   } // endif Connect
115 
116 	if (EXTDEF::DefineAM(g, am, poff))
117 		return true;
118 
119   Catver = GetIntCatInfo("Catver", 2);
120   Options = ODBConn::noOdbcDialog;
121 //Options = ODBConn::noOdbcDialog | ODBConn::useCursorLib;
122   Cto= GetIntCatInfo("ConnectTimeout", DEFAULT_LOGIN_TIMEOUT);
123   Qto= GetIntCatInfo("QueryTimeout", DEFAULT_QUERY_TIMEOUT);
124 	UseCnc = GetBoolCatInfo("UseDSN", false);
125   return false;
126 } // end of DefineAM
127 
128 /***********************************************************************/
129 /*  GetTable: makes a new Table Description Block.                     */
130 /***********************************************************************/
131 PTDB ODBCDEF::GetTable(PGLOBAL g, MODE m)
132 {
133   PTDB tdbp = NULL;
134 
135   /*********************************************************************/
136   /*  Allocate a TDB of the proper type.                               */
137   /*  Column blocks will be allocated only when needed.                */
138   /*********************************************************************/
139   if (Xsrc)
140     tdbp = new(g) TDBXDBC(this);
141   else switch (Catfunc) {
142     case FNC_COL:
143       tdbp = new(g) TDBOCL(this);
144       break;
145     case FNC_TABLE:
146       tdbp = new(g) TDBOTB(this);
147       break;
148     case FNC_DSN:
149       tdbp = new(g) TDBSRC(this);
150       break;
151     case FNC_DRIVER:
152       tdbp = new(g) TDBDRV(this);
153       break;
154     default:
155       tdbp = new(g) TDBODBC(this);
156 
157       if (Multiple == 1)
158         tdbp = new(g) TDBMUL(tdbp);
159       else if (Multiple == 2)
160         strcpy(g->Message, MSG(NO_ODBC_MUL));
161   } // endswitch Catfunc
162 
163   return tdbp;
164 } // end of GetTable
165 
166 /* -------------------------- Class TDBODBC -------------------------- */
167 
168 /***********************************************************************/
169 /*  Implementation of the TDBODBC class.                               */
170 /***********************************************************************/
171 TDBODBC::TDBODBC(PODEF tdp) : TDBEXT(tdp)
172 {
173   Ocp = NULL;
174   Cnp = NULL;
175 
176   if (tdp) {
177     Connect = tdp->Connect;
178     Ops.User = tdp->Username;
179     Ops.Pwd = tdp->Password;
180     Ops.Cto = tdp->Cto;
181     Ops.Qto = tdp->Qto;
182     Catver = tdp->Catver;
183     Ops.UseCnc = tdp->UseCnc;
184   } else {
185     Connect = NULL;
186     Ops.User = NULL;
187     Ops.Pwd = NULL;
188     Ops.Cto = DEFAULT_LOGIN_TIMEOUT;
189     Ops.Qto = DEFAULT_QUERY_TIMEOUT;
190     Catver = 0;
191     Ops.UseCnc = false;
192   } // endif tdp
193 
194 } // end of TDBODBC standard constructor
195 
196 TDBODBC::TDBODBC(PTDBODBC tdbp) : TDBEXT(tdbp)
197 {
198   Ocp = tdbp->Ocp;            // is that right ?
199   Cnp = tdbp->Cnp;
200   Connect = tdbp->Connect;
201   Ops = tdbp->Ops;
202 } // end of TDBODBC copy constructor
203 
204 // Method
205 PTDB TDBODBC::Clone(PTABS t)
206 {
207   PTDB     tp;
208   PODBCCOL cp1, cp2;
209   PGLOBAL  g = t->G;        // Is this really useful ???
210 
211   tp = new(g) TDBODBC(this);
212 
213   for (cp1 = (PODBCCOL)Columns; cp1; cp1 = (PODBCCOL)cp1->GetNext()) {
214     cp2 = new(g) ODBCCOL(cp1, tp);  // Make a copy
215     NewPointer(t, cp1, cp2);
216   } // endfor cp1
217 
218   return tp;
219 } // end of CopyOne
220 
221 /***********************************************************************/
222 /*  Allocate ODBC column description block.                            */
223 /***********************************************************************/
224 PCOL TDBODBC::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
225 {
226   return new(g) ODBCCOL(cdp, this, cprec, n);
227 } // end of MakeCol
228 
229 /***********************************************************************/
230 /*  Extract the filename from connect string and return it.            */
231 /*  This used for Multiple(1) tables. Also prepare a connect string    */
232 /*  with a place holder to be used by SetFile.                         */
233 /***********************************************************************/
234 PCSZ TDBODBC::GetFile(PGLOBAL g)
235 {
236   if (Connect) {
237     char  *p1, *p2;
238 		int    i;
239     size_t n;
240 
241 		if (!(p1 = strstr(Connect, "DBQ="))) {
242 			char *p, *lc = strlwr(PlugDup(g, Connect));
243 
244 			if ((p = strstr(lc, "database=")))
245 				p1 = Connect + (p - lc);
246 
247 			i = 9;
248 		} else
249 			i = 4;
250 
251     if (p1) {
252       p1 += i;                        // Beginning of file name
253       p2 = strchr(p1, ';');           // End of file path/name
254 
255       // Make the File path/name from the connect string
256       n = (p2) ? p2 - p1 : strlen(p1);
257       DBQ = (PSZ)PlugSubAlloc(g, NULL, n + 1);
258       memcpy(DBQ, p1, n);
259       DBQ[n] = '\0';
260 
261       // Make the Format used to re-generate Connect (3 = "%s" + 1)
262       MulConn = (char*)PlugSubAlloc(g, NULL, strlen(Connect) - n + 3);
263       memcpy(MulConn, Connect, p1 - Connect);
264       MulConn[p1 - Connect] = '\0';
265       strcat(strcat(MulConn, "%s"), (p2) ? p2 : ";");
266     } // endif p1
267 
268   } // endif Connect
269 
270   return (DBQ) ? DBQ : (PSZ)"???";
271 } // end of GetFile
272 
273 /***********************************************************************/
274 /*  Set DBQ and get the new file name into the connect string.         */
275 /***********************************************************************/
276 void TDBODBC::SetFile(PGLOBAL g, PCSZ fn)
277 {
278   if (MulConn) {
279     int n = strlen(MulConn) + strlen(fn) - 1;
280 
281     if (n > BufSize) {
282       // Allocate a buffer larger than needed so the chance
283       // of having to reallocate it is reduced.
284       BufSize = n + 6;
285       Connect = (char*)PlugSubAlloc(g, NULL, BufSize);
286     } // endif n
287 
288     // Make the complete connect string
289     sprintf(Connect, MulConn, fn);
290   } // endif MultConn
291 
292   DBQ = PlugDup(g, fn);
293 } // end of SetFile
294 
295 /***********************************************************************/
296 /*  MakeInsert: make the Insert statement used with ODBC connection.   */
297 /***********************************************************************/
298 bool TDBODBC::MakeInsert(PGLOBAL g)
299 {
300 	PCSZ   schmp = NULL;
301 	char  *catp = NULL, buf[NAM_LEN * 3];
302 	int    len = 0;
303 	bool   oom, b = false;
304 	PTABLE tablep = To_Table;
305 	PCOL   colp;
306 
307   for (colp = Columns; colp; colp = colp->GetNext())
308     if (colp->IsSpecial()) {
309       strcpy(g->Message, MSG(NO_ODBC_SPECOL));
310       return true;
311     } else {
312 			// Column name can be encoded in UTF-8
313 			Decode(colp->GetName(), buf, sizeof(buf));
314 			len += (strlen(buf) + 6);	 // comma + quotes + valist
315 			((PEXTCOL)colp)->SetRank(++Nparm);
316     } // endif colp
317 
318 	// Below 32 is enough to contain the fixed part of the query
319 	if (Catalog && *Catalog)
320 		catp = Catalog;
321 
322 	if (catp)
323 		len += strlen(catp) + 1;
324 
325 	//if (tablep->GetSchema())
326 	//	schmp = (char*)tablep->GetSchema();
327 	//else
328 	if (Schema && *Schema)
329 		schmp = Schema;
330 
331 	if (schmp)
332 		len += strlen(schmp) + 1;
333 
334 	// Table name can be encoded in UTF-8
335 	Decode(TableName, buf, sizeof(buf));
336 	len += (strlen(buf) + 32);
337 	Query = new(g) STRING(g, len, "INSERT INTO ");
338 
339 	if (catp) {
340 		Query->Append(catp);
341 
342 		if (schmp) {
343 			Query->Append('.');
344 			Query->Append(schmp);
345 		} // endif schmp
346 
347 		Query->Append('.');
348 	}	else if (schmp) {
349 		Query->Append(schmp);
350 		Query->Append('.');
351 	} // endif schmp
352 
353 	if (Quote) {
354 		// Put table name between identifier quotes in case in contains blanks
355 		Query->Append(Quote);
356 		Query->Append(buf);
357 		Query->Append(Quote);
358 	}	else
359 		Query->Append(buf);
360 
361 	Query->Append('(');
362 
363 	for (colp = Columns; colp; colp = colp->GetNext()) {
364 		if (b)
365 			Query->Append(", ");
366 		else
367 			b = true;
368 
369 		// Column name can be in UTF-8 encoding
370 		Decode(colp->GetName(), buf, sizeof(buf));
371 
372 		if (Quote) {
373 			// Put column name between identifier quotes in case in contains blanks
374 			Query->Append(Quote);
375 			Query->Append(buf);
376 			Query->Append(Quote);
377 		}	else
378 			Query->Append(buf);
379 
380 	} // endfor colp
381 
382 	Query->Append(") VALUES (");
383 
384 	for (int i = 0; i < Nparm; i++)
385 		Query->Append("?,");
386 
387 	if ((oom = Query->IsTruncated()))
388 		strcpy(g->Message, "MakeInsert: Out of memory");
389 	else
390 		Query->RepLast(')');
391 
392   return oom;
393 } // end of MakeInsert
394 
395 /***********************************************************************/
396 /*  ODBC Bind Parameter function.                                      */
397 /***********************************************************************/
398 bool TDBODBC::BindParameters(PGLOBAL g)
399 {
400 	PODBCCOL colp;
401 
402 	for (colp = (PODBCCOL)Columns; colp; colp = (PODBCCOL)colp->Next) {
403 		colp->AllocateBuffers(g, 0);
404 
405 		if (Ocp->BindParam(colp))
406 			return true;
407 
408 	} // endfor colp
409 
410 	return false;
411 } // end of BindParameters
412 
413 #if 0
414 /***********************************************************************/
415 /*  MakeUpdate: make the SQL statement to send to ODBC connection.     */
416 /***********************************************************************/
417 char *TDBODBC::MakeUpdate(PGLOBAL g)
418 {
419   char *qc, *stmt = NULL, cmd[8], tab[96], end[1024];
420 
421   stmt = (char*)PlugSubAlloc(g, NULL, strlen(Qrystr) + 64);
422   memset(end, 0, sizeof(end));
423 
424   if (sscanf(Qrystr, "%s `%[^`]`%1023c", cmd, tab, end) > 2 ||
425       sscanf(Qrystr, "%s \"%[^\"]\"%1023c", cmd, tab, end) > 2)
426     qc = Ocp->GetQuoteChar();
427   else if (sscanf(Qrystr, "%s %s%1023c", cmd, tab, end) > 2)
428     qc = (Quoted) ? Quote : "";
429   else {
430     strcpy(g->Message, "Cannot use this UPDATE command");
431     return NULL;
432   } // endif sscanf
433 
434   assert(!stricmp(cmd, "update"));
435   strcat(strcat(strcat(strcpy(stmt, "UPDATE "), qc), TableName), qc);
436 
437   for (int i = 0; end[i]; i++)
438     if (end[i] == '`')
439       end[i] = *qc;
440 
441   strcat(stmt, end);
442   return stmt;
443 } // end of MakeUpdate
444 
445 /***********************************************************************/
446 /*  MakeDelete: make the SQL statement to send to ODBC connection.     */
447 /***********************************************************************/
448 char *TDBODBC::MakeDelete(PGLOBAL g)
449 {
450 	char *qc, *stmt = NULL, cmd[8], from[8], tab[96], end[512];
451 
452 	stmt = (char*)PlugSubAlloc(g, NULL, strlen(Qrystr) + 64);
453 	memset(end, 0, sizeof(end));
454 
455 	if (sscanf(Qrystr, "%s %s `%[^`]`%511c", cmd, from, tab, end) > 2 ||
456 		  sscanf(Qrystr, "%s %s \"%[^\"]\"%511c", cmd, from, tab, end) > 2)
457 		qc = Ocp->GetQuoteChar();
458 	else if (sscanf(Qrystr, "%s %s %s%511c", cmd, from, tab, end) > 2)
459 		qc = (Quoted) ? Quote : "";
460 	else {
461 		strcpy(g->Message, "Cannot use this DELETE command");
462 		return NULL;
463 	} // endif sscanf
464 
465 	assert(!stricmp(cmd, "delete") && !stricmp(from, "from"));
466 	strcat(strcat(strcat(strcpy(stmt, "DELETE FROM "), qc), TableName), qc);
467 
468 	if (*end) {
469 		for (int i = 0; end[i]; i++)
470 			if (end[i] == '`')
471 				end[i] = *qc;
472 
473 		strcat(stmt, end);
474 	} // endif end
475 
476 	return stmt;
477 } // end of MakeDelete
478 #endif // 0
479 
480 /***********************************************************************/
481 /*  ResetSize: call by TDBMUL when calculating size estimate.          */
482 /***********************************************************************/
483 void TDBODBC::ResetSize(void)
484 {
485   MaxSize = -1;
486 
487   if (Ocp && Ocp->IsOpen())
488     Ocp->Close();
489 
490 } // end of ResetSize
491 
492 /***********************************************************************/
493 /*  ODBC Cardinality: returns table size in number of rows.            */
494 /***********************************************************************/
495 int TDBODBC::Cardinality(PGLOBAL g)
496 {
497   if (!g)
498     return (Mode == MODE_ANY && !Srcdef) ? 1 : 0;
499 
500   if (Cardinal < 0 && Mode == MODE_ANY && !Srcdef && ExactInfo()) {
501     // Info command, we must return the exact table row number
502     char     qry[96], tbn[64];
503     ODBConn *ocp = new(g) ODBConn(g, this);
504 
505     if (ocp->Open(Connect, &Ops, Options) < 1)
506       return -1;
507 
508     // Table name can be encoded in UTF-8
509     Decode(TableName, tbn, sizeof(tbn));
510     strcpy(qry, "SELECT COUNT(*) FROM ");
511 
512     if (Quote)
513       strcat(strcat(strcat(qry, Quote), tbn), Quote);
514     else
515       strcat(qry, tbn);
516 
517     // Allocate a Count(*) column (must not use the default constructor)
518     Cnp = new(g) ODBCCOL;
519     Cnp->InitValue(g);
520 
521     if ((Cardinal = ocp->GetResultSize(qry, Cnp)) < 0)
522       return -3;
523 
524     ocp->Close();
525   } else
526     Cardinal = 10;    // To make MySQL happy
527 
528   return Cardinal;
529 } // end of Cardinality
530 
531 /***********************************************************************/
532 /*  ODBC Access Method opening routine.                                */
533 /*  New method now that this routine is called recursively (last table */
534 /*  first in reverse order): index blocks are immediately linked to    */
535 /*  join block of next table if it exists or else are discarted.       */
536 /***********************************************************************/
537 bool TDBODBC::OpenDB(PGLOBAL g)
538 {
539   bool rc = true;
540 
541   if (trace(1))
542     htrc("ODBC OpenDB: tdbp=%p tdb=R%d use=%dmode=%d\n",
543             this, Tdb_No, Use, Mode);
544 
545   if (Use == USE_OPEN) {
546     /*******************************************************************/
547     /*  Table already open, just replace it at its beginning.          */
548     /*******************************************************************/
549     if (Memory == 1) {
550       if ((Qrp = Ocp->AllocateResult(g)))
551         Memory = 2;            // Must be filled
552       else
553         Memory = 0;            // Allocation failed, don't use it
554 
555     } else if (Memory == 2)
556       Memory = 3;              // Ok to use memory result
557 
558     if (Memory < 3) {
559       // Method will depend on cursor type
560       if ((Rbuf = Ocp->Rewind(Query->GetStr(), (PODBCCOL)Columns)) < 0)
561 				if (Mode != MODE_READX) {
562 	        Ocp->Close();
563 		      return true;
564 				}	else
565 					Rbuf = 0;
566 
567     } else
568       Rbuf = Qrp->Nblin;
569 
570     CurNum = 0;
571     Fpos = 0;
572     Curpos = 1;
573     return false;
574   } // endif use
575 
576   /*********************************************************************/
577   /*  Open an ODBC connection for this table.                          */
578   /*  Note: this may not be the proper way to do. Perhaps it is better */
579   /*  to test whether a connection is already open for this datasource */
580   /*  and if so to allocate just a new result set. But this only for   */
581   /*  drivers allowing concurency in getting results ???               */
582   /*********************************************************************/
583   if (!Ocp)
584     Ocp = new(g) ODBConn(g, this);
585   else if (Ocp->IsOpen())
586     Ocp->Close();
587 
588   if (Ocp->Open(Connect, &Ops, Options) < 1)
589     return true;
590   else if (Quoted)
591     Quote = Ocp->GetQuoteChar();
592 
593   Use = USE_OPEN;       // Do it now in case we are recursively called
594 
595   /*********************************************************************/
596   /* Make the command and allocate whatever is used for getting results*/
597   /*********************************************************************/
598   if (Mode == MODE_READ || Mode == MODE_READX) {
599     if (Memory > 1 && !Srcdef) {
600       int n;
601 
602       if (!MakeSQL(g, true)) {
603         // Allocate a Count(*) column
604         Cnp = new(g) ODBCCOL;
605         Cnp->InitValue(g);
606 
607         if ((n = Ocp->GetResultSize(Query->GetStr(), Cnp)) < 0) {
608 					char* msg = PlugDup(g, g->Message);
609 
610 					sprintf(g->Message, "Get result size: %s (rc=%d)", msg, n);
611 					return true;
612 				} else if (n) {
613 					Ocp->m_Rows = n;
614 
615 					if ((Qrp = Ocp->AllocateResult(g)))
616 						Memory = 2;            // Must be filled
617 					else {
618 						strcpy(g->Message, "Result set memory allocation failed");
619 						return true;
620 					} // endif n
621 
622 				} else				 // Void result
623 					Memory = 0;
624 
625 				Ocp->m_Rows = 0;
626 			} else
627         return true;
628 
629     } // endif Memory
630 
631     if (!(rc = MakeSQL(g, false))) {
632       for (PODBCCOL colp = (PODBCCOL)Columns; colp;
633                     colp = (PODBCCOL)colp->GetNext())
634         if (!colp->IsSpecial())
635           colp->AllocateBuffers(g, Rows);
636 
637 			rc = (Mode == MODE_READ)
638     		 ? ((Rows = Ocp->ExecDirectSQL(Query->GetStr(), (PODBCCOL)Columns)) < 0)
639 				 : false;
640     } // endif rc
641 
642   } else if (Mode == MODE_INSERT) {
643     if (!(rc = MakeInsert(g))) {
644       if (Nparm != Ocp->PrepareSQL(Query->GetStr())) {
645         strcpy(g->Message, MSG(PARM_CNT_MISS));
646         rc = true;
647       } else
648         rc = BindParameters(g);
649 
650     } // endif rc
651 
652   } else if (Mode == MODE_UPDATE || Mode == MODE_DELETE) {
653     rc = false;  // wait for CheckCond before calling MakeCommand(g);
654   } else
655     sprintf(g->Message, "Invalid mode %d", Mode);
656 
657   if (rc) {
658     Ocp->Close();
659     return true;
660   } // endif rc
661 
662   /*********************************************************************/
663   /*  Reset statistics values.                                         */
664   /*********************************************************************/
665   num_read = num_there = num_eq[0] = num_eq[1] = 0;
666   return false;
667 } // end of OpenDB
668 
669 #if 0
670 /***********************************************************************/
671 /*  GetRecpos: return the position of last read record.                */
672 /***********************************************************************/
673 int TDBODBC::GetRecpos(void)
674 {
675   return Fpos;
676 } // end of GetRecpos
677 #endif // 0
678 
679 /***********************************************************************/
680 /*  SetRecpos: set the position of next read record.                   */
681 /***********************************************************************/
682 bool TDBODBC::SetRecpos(PGLOBAL g, int recpos)
683 {
684   if (Ocp->m_Full) {
685     Fpos = 0;
686     CurNum = recpos - 1;
687   } else if (Memory == 3) {
688     Fpos = recpos;
689     CurNum = -1;
690   } else if (Scrollable) {
691     // Is new position in the current row set?
692     if (recpos >= Curpos && recpos < Curpos + Rbuf) {
693       CurNum = recpos - Curpos;
694       Fpos = 0;
695     } else {
696       Fpos = recpos;
697       CurNum = 0;
698     } // endif recpos
699 
700   } else {
701     strcpy(g->Message,
702 			"This action requires Memory setting or a scrollable cursor");
703     return true;
704   } // endif's
705 
706   // Indicate the table position was externally set
707   Placed = true;
708   return false;
709 } // end of SetRecpos
710 
711 /***********************************************************************/
712 /*  Data Base indexed read routine for ODBC access method.             */
713 /***********************************************************************/
714 bool TDBODBC::ReadKey(PGLOBAL g, OPVAL op, const key_range *kr)
715 {
716 	char c = Quote ? *Quote : 0;
717 	int  oldlen = Query->GetLength();
718 	PHC  hc = To_Def->GetHandler();
719 
720 	if (!(kr || hc->end_range) || op == OP_NEXT ||
721   	     Mode == MODE_UPDATE || Mode == MODE_DELETE) {
722 		if (!kr && Mode == MODE_READX) {
723 			// This is a false indexed read
724 			Rows = Ocp->ExecDirectSQL((char*)Query->GetStr(), (PODBCCOL)Columns);
725 			Mode = MODE_READ;
726 			return (Rows < 0);
727 		} // endif key
728 
729 		return false;
730 	}	else {
731 		if (hc->MakeKeyWhere(g, Query, op, c, kr))
732 			return true;
733 
734 		if (To_CondFil) {
735 			if (To_CondFil->Idx != hc->active_index) {
736 				To_CondFil->Idx = hc->active_index;
737 				To_CondFil->Body= (char*)PlugSubAlloc(g, NULL, 0);
738 				*To_CondFil->Body= 0;
739 
740 				if ((To_CondFil = hc->CheckCond(g, To_CondFil, Cond)))
741 					PlugSubAlloc(g, NULL, strlen(To_CondFil->Body) + 1);
742 
743 			} // endif active_index
744 
745 			if (To_CondFil)
746 				if (Query->Append(" AND ") || Query->Append(To_CondFil->Body)) {
747 					strcpy(g->Message, "Readkey: Out of memory");
748 					return true;
749 				} // endif Append
750 
751 		} // endif To_Condfil
752 
753 		Mode = MODE_READ;
754 	} // endif's op
755 
756 	if (trace(33))
757 		htrc("ODBC ReadKey: Query=%s\n", Query->GetStr());
758 
759 	Rows = Ocp->ExecDirectSQL((char*)Query->GetStr(), (PODBCCOL)Columns);
760 	Query->Truncate(oldlen);
761 	return (Rows < 0);
762 } // end of ReadKey
763 
764 /***********************************************************************/
765 /*  VRDNDOS: Data Base read routine for odbc access method.            */
766 /***********************************************************************/
767 int TDBODBC::ReadDB(PGLOBAL g)
768 {
769   int   rc;
770 
771   if (trace(2))
772 		htrc("ODBC ReadDB: R%d Mode=%d\n", GetTdb_No(), Mode);
773 
774   if (Mode == MODE_UPDATE || Mode == MODE_DELETE) {
775     if (!Query && MakeCommand(g))
776       return RC_FX;
777 
778     // Send the UPDATE/DELETE command to the remote table
779     if (!Ocp->ExecSQLcommand(Query->GetStr())) {
780       sprintf(g->Message, "%s: %d affected rows", TableName, AftRows);
781 
782       if (trace(1))
783         htrc("%s\n", g->Message);
784 
785       PushWarning(g, this, 0);    // 0 means a Note
786       return RC_EF;               // Nothing else to do
787     } else
788       return RC_FX;               // Error
789 
790   } // endif Mode
791 
792   /*********************************************************************/
793   /*  Now start the reading process.                                   */
794   /*  Here is the place to fetch the line(s).                          */
795   /*********************************************************************/
796   if (Placed) {
797     if (Fpos && CurNum >= 0)
798       Rbuf = Ocp->Fetch((Curpos = Fpos));
799 
800     rc = (Rbuf > 0) ? RC_OK : (Rbuf == 0) ? RC_EF : RC_FX;
801     Placed = false;
802   } else {
803     if (Memory != 3) {
804       if (++CurNum >= Rbuf) {
805         Rbuf = Ocp->Fetch();
806         Curpos = Fpos + 1;
807         CurNum = 0;
808         } // endif CurNum
809 
810       rc = (Rbuf > 0) ? RC_OK : (Rbuf == 0) ? RC_EF : RC_FX;
811     } else                 // Getting result from memory
812       rc = (Fpos < Qrp->Nblin) ? RC_OK : RC_EF;
813 
814     if (rc == RC_OK) {
815       if (Memory == 2)
816         Qrp->Nblin++;
817 
818       Fpos++;                // Used for memory and pos
819     } // endif rc
820 
821   } // endif Placed
822 
823   if (trace(2))
824     htrc(" Read: Rbuf=%d rc=%d\n", Rbuf, rc);
825 
826   return rc;
827 } // end of ReadDB
828 
829 /***********************************************************************/
830 /*  Data Base Insert write routine for ODBC access method.             */
831 /***********************************************************************/
832 int TDBODBC::WriteDB(PGLOBAL g)
833 {
834   int n = Ocp->ExecuteSQL();
835 
836   if (n < 0) {
837     AftRows = n;
838     return RC_FX;
839   } else
840     AftRows += n;
841 
842   return RC_OK;
843 } // end of WriteDB
844 
845 /***********************************************************************/
846 /*  Data Base delete line routine for ODBC access method.              */
847 /***********************************************************************/
848 int TDBODBC::DeleteDB(PGLOBAL g, int irc)
849 {
850   if (irc == RC_FX) {
851     if (!Query && MakeCommand(g))
852       return RC_FX;
853 
854     // Send the DELETE (all) command to the remote table
855     if (!Ocp->ExecSQLcommand(Query->GetStr())) {
856       sprintf(g->Message, "%s: %d affected rows", TableName, AftRows);
857 
858       if (trace(1))
859         htrc("%s\n", g->Message);
860 
861       PushWarning(g, this, 0);    // 0 means a Note
862       return RC_OK;               // This is a delete all
863     } else
864       return RC_FX;               // Error
865 
866   } else
867     return RC_OK;                 // Ignore
868 
869 } // end of DeleteDB
870 
871 /***********************************************************************/
872 /*  Data Base close routine for ODBC access method.                    */
873 /***********************************************************************/
874 void TDBODBC::CloseDB(PGLOBAL g)
875 {
876   if (Ocp)
877 
878     Ocp->Close();
879 
880   if (trace(1))
881     htrc("ODBC CloseDB: closing %s\n", Name);
882 
883 } // end of CloseDB
884 
885 /* --------------------------- ODBCCOL ------------------------------- */
886 
887 /***********************************************************************/
888 /*  ODBCCOL public constructor.                                        */
889 /***********************************************************************/
890 ODBCCOL::ODBCCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PCSZ am)
891        : EXTCOL(cdp, tdbp, cprec, i, am)
892 {
893   // Set additional ODBC access method information for column.
894   Slen = 0;
895   StrLen = &Slen;
896   Sqlbuf = NULL;
897 } // end of ODBCCOL constructor
898 
899 /***********************************************************************/
900 /*  ODBCCOL private constructor.                                       */
901 /***********************************************************************/
902 ODBCCOL::ODBCCOL(void) : EXTCOL()
903 {
904   Slen = 0;
905   StrLen = &Slen;
906   Sqlbuf = NULL;
907 } // end of ODBCCOL constructor
908 
909 /***********************************************************************/
910 /*  ODBCCOL constructor used for copying columns.                      */
911 /*  tdbp is the pointer to the new table descriptor.                   */
912 /***********************************************************************/
913 ODBCCOL::ODBCCOL(ODBCCOL *col1, PTDB tdbp) : EXTCOL(col1, tdbp)
914 {
915   Slen = col1->Slen;
916   StrLen = col1->StrLen;
917   Sqlbuf = col1->Sqlbuf;
918 } // end of ODBCCOL copy constructor
919 
920 /***********************************************************************/
921 /*  ReadColumn: when SQLFetch is used there is nothing to do as the    */
922 /*  column buffer was bind to the record set. This is also the case    */
923 /*  when calculating MaxSize (Bufp is NULL even when Rows is not).     */
924 /***********************************************************************/
925 void ODBCCOL::ReadColumn(PGLOBAL g)
926 {
927   PTDBODBC tdbp = (PTDBODBC)To_Tdb;
928   int i = tdbp->Fpos - 1, n = tdbp->CurNum;
929 
930   if (tdbp->Memory == 3) {
931     // Get the value from the stored memory
932     if (Crp->Nulls && Crp->Nulls[i] == '*') {
933       Value->Reset();
934       Value->SetNull(true);
935     } else {
936       Value->SetValue_pvblk(Crp->Kdata, i);
937       Value->SetNull(false);
938     } // endif Nulls
939 
940     return;
941   } // endif Memory
942 
943   if (StrLen[n] == SQL_NULL_DATA) {
944     // Null value
945     if (Nullable)
946       Value->SetNull(true);
947 
948     Value->Reset();
949     goto put;
950   } else
951     Value->SetNull(false);
952 
953   if (Bufp && tdbp->Rows) {
954     if (Buf_Type == TYPE_DATE)
955       *Sqlbuf = ((TIMESTAMP_STRUCT*)Bufp)[n];
956     else
957       Value->SetValue_pvblk(Blkp, n);
958 
959   } // endif Bufp
960 
961   if (Buf_Type == TYPE_DATE) {
962     struct tm dbtime;
963 
964     memset(&dbtime, 0, sizeof(tm));
965     dbtime.tm_sec = (int)Sqlbuf->second;
966     dbtime.tm_min = (int)Sqlbuf->minute;
967     dbtime.tm_hour = (int)Sqlbuf->hour;
968     dbtime.tm_mday = (int)Sqlbuf->day;
969     dbtime.tm_mon = (int)Sqlbuf->month - 1;
970     dbtime.tm_year = (int)Sqlbuf->year - 1900;
971     ((DTVAL*)Value)->MakeTime(&dbtime);
972   } else if (Buf_Type == TYPE_DECIM && tdbp->Sep) {
973     // Be sure to use decimal point
974     char *p = strchr(Value->GetCharValue(), tdbp->Sep);
975 
976     if (p)
977       *p = '.';
978 
979   } // endif Buf_Type
980 
981   if (trace(2)) {
982     char buf[64];
983 
984     htrc("ODBC Column %s: rows=%d buf=%p type=%d value=%s\n",
985       Name, tdbp->Rows, Bufp, Buf_Type, Value->GetCharString(buf));
986   } // endif trace
987 
988  put:
989   if (tdbp->Memory != 2)
990     return;
991 
992   /*********************************************************************/
993   /*  Fill the allocated result structure.                             */
994   /*********************************************************************/
995   if (Value->IsNull()) {
996     if (Crp->Nulls)
997       Crp->Nulls[i] = '*';           // Null value
998 
999     Crp->Kdata->Reset(i);
1000   } else
1001     Crp->Kdata->SetValue(Value, i);
1002 
1003 } // end of ReadColumn
1004 
1005 /***********************************************************************/
1006 /*  AllocateBuffers: allocate the extended buffer for SQLExtendedFetch */
1007 /*  or Fetch.  Note: we use Long+1 here because ODBC must have space   */
1008 /*  for the ending null character.                                     */
1009 /***********************************************************************/
1010 void ODBCCOL::AllocateBuffers(PGLOBAL g, int rows)
1011 {
1012   if (Buf_Type == TYPE_DATE)
1013     Sqlbuf = (TIMESTAMP_STRUCT*)PlugSubAlloc(g, NULL,
1014                                              sizeof(TIMESTAMP_STRUCT));
1015 
1016   if (!rows)
1017     return;
1018 
1019   if (Buf_Type == TYPE_DATE)
1020     Bufp = PlugSubAlloc(g, NULL, rows * sizeof(TIMESTAMP_STRUCT));
1021   else {
1022     Blkp = AllocValBlock(g, NULL, Buf_Type, rows, GetBuflen(),
1023                                   GetScale(), true, false, false);
1024     Bufp = Blkp->GetValPointer();
1025   } // endelse
1026 
1027   if (rows > 1)
1028     StrLen = (SQLLEN *)PlugSubAlloc(g, NULL, rows * sizeof(SQLLEN));
1029 
1030 } // end of AllocateBuffers
1031 
1032 /***********************************************************************/
1033 /*  Returns the buffer to use for Fetch or Extended Fetch.             */
1034 /***********************************************************************/
1035 void *ODBCCOL::GetBuffer(DWORD rows)
1036 {
1037   if (rows && To_Tdb) {
1038     assert(rows == (DWORD)((TDBODBC*)To_Tdb)->Rows);
1039     return Bufp;
1040   } else
1041     return (Buf_Type == TYPE_DATE) ? Sqlbuf : Value->GetTo_Val();
1042 
1043 } // end of GetBuffer
1044 
1045 /***********************************************************************/
1046 /*  Returns the buffer length to use for Fetch or Extended Fetch.      */
1047 /***********************************************************************/
1048 SWORD ODBCCOL::GetBuflen(void)
1049 {
1050   SWORD flen;
1051 
1052   switch (Buf_Type) {
1053     case TYPE_DATE:
1054       flen = (SWORD)sizeof(TIMESTAMP_STRUCT);
1055       break;
1056     case TYPE_STRING:
1057     case TYPE_DECIM:
1058       flen = (SWORD)Value->GetClen() + 1;
1059       break;
1060     default:
1061       flen = (SWORD)Value->GetClen();
1062     } // endswitch Buf_Type
1063 
1064   return flen;
1065 } // end of GetBuflen
1066 
1067 /***********************************************************************/
1068 /*  WriteColumn: make sure the bind buffer is updated.                 */
1069 /***********************************************************************/
1070 void ODBCCOL::WriteColumn(PGLOBAL g)
1071 {
1072   /*********************************************************************/
1073   /*  Do convert the column value if necessary.                        */
1074   /*********************************************************************/
1075   if (Value != To_Val)
1076     Value->SetValue_pval(To_Val, FALSE);   // Convert the inserted value
1077 
1078   if (Buf_Type == TYPE_DATE) {
1079     struct tm tm, *dbtime = ((DTVAL*)Value)->GetGmTime(&tm);
1080 
1081     Sqlbuf->second = dbtime->tm_sec;
1082     Sqlbuf->minute = dbtime->tm_min;
1083     Sqlbuf->hour   = dbtime->tm_hour;
1084     Sqlbuf->day    = dbtime->tm_mday;
1085     Sqlbuf->month  = dbtime->tm_mon + 1;
1086     Sqlbuf->year   = dbtime->tm_year + 1900;
1087     Sqlbuf->fraction = 0;
1088   } else if (Buf_Type == TYPE_DECIM) {
1089     // Some data sources require local decimal separator
1090     char *p, sep = ((PTDBODBC)To_Tdb)->Sep;
1091 
1092     if (sep && (p = strchr(Value->GetCharValue(), '.')))
1093       *p = sep;
1094 
1095   } // endif Buf_Type
1096 
1097   if (Nullable)
1098     *StrLen = (Value->IsNull()) ? SQL_NULL_DATA :
1099          (IsTypeChar(Buf_Type)) ? SQL_NTS : 0;
1100 
1101 } // end of WriteColumn
1102 
1103 /* -------------------------- Class TDBXDBC -------------------------- */
1104 
1105 /***********************************************************************/
1106 /*  Implementation of the TDBXDBC class.                               */
1107 /***********************************************************************/
1108 TDBXDBC::TDBXDBC(PODEF tdp) : TDBODBC(tdp)
1109 {
1110   Cmdlist = NULL;
1111   Cmdcol = NULL;
1112   Mxr = tdp->Maxerr;
1113   Nerr = 0;
1114 } // end of TDBXDBC constructor
1115 
1116 TDBXDBC::TDBXDBC(PTDBXDBC tdbp) : TDBODBC(tdbp)
1117 {
1118   Cmdlist = tdbp->Cmdlist;
1119   Cmdcol = tdbp->Cmdcol;
1120   Mxr = tdbp->Mxr;
1121   Nerr = tdbp->Nerr;
1122 } // end of TDBXDBC copy constructor
1123 
1124 PTDB TDBXDBC::Clone(PTABS t)
1125 {
1126   PTDB     tp;
1127   PXSRCCOL cp1, cp2;
1128   PGLOBAL  g = t->G;        // Is this really useful ???
1129 
1130   tp = new(g) TDBXDBC(this);
1131 
1132   for (cp1 = (PXSRCCOL)Columns; cp1; cp1 = (PXSRCCOL)cp1->GetNext()) {
1133     cp2 = new(g) XSRCCOL(cp1, tp);  // Make a copy
1134     NewPointer(t, cp1, cp2);
1135   } // endfor cp1
1136 
1137   return tp;
1138 } // end of CopyOne
1139 
1140 /***********************************************************************/
1141 /*  Allocate XSRC column description block.                            */
1142 /***********************************************************************/
1143 PCOL TDBXDBC::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
1144 {
1145   PXSRCCOL colp = new(g) XSRCCOL(cdp, this, cprec, n);
1146 
1147   if (!colp->Flag)
1148     Cmdcol = colp->GetName();
1149 
1150   return colp;
1151 } // end of MakeCol
1152 
1153 /***********************************************************************/
1154 /*  MakeCMD: make the SQL statement to send to ODBC connection.        */
1155 /***********************************************************************/
1156 PCMD TDBXDBC::MakeCMD(PGLOBAL g)
1157 {
1158   PCMD xcmd = NULL;
1159 
1160   if (To_CondFil) {
1161     if (Cmdcol) {
1162       if (!stricmp(Cmdcol, To_CondFil->Body) &&
1163           (To_CondFil->Op == OP_EQ || To_CondFil->Op == OP_IN)) {
1164         xcmd = To_CondFil->Cmds;
1165       } else
1166         strcpy(g->Message, "Invalid command specification filter");
1167 
1168     } else
1169       strcpy(g->Message, "No command column in select list");
1170 
1171   } else if (!Srcdef)
1172     strcpy(g->Message, "No Srcdef default command");
1173   else
1174     xcmd = new(g) CMD(g, Srcdef);
1175 
1176   return xcmd;
1177 } // end of MakeCMD
1178 
1179 #if 0
1180 /***********************************************************************/
1181 /*  ODBC Bind Parameter function.                                      */
1182 /***********************************************************************/
1183 bool TDBXDBC::BindParameters(PGLOBAL g)
1184 {
1185   PODBCCOL colp;
1186 
1187   for (colp = (PODBCCOL)Columns; colp; colp = (PODBCCOL)colp->Next) {
1188     colp->AllocateBuffers(g, 0);
1189 
1190     if (Ocp->BindParam(colp))
1191       return true;
1192 
1193     } // endfor colp
1194 
1195   return false;
1196 } // end of BindParameters
1197 #endif // 0
1198 
1199 /***********************************************************************/
1200 /*  XDBC GetMaxSize: returns table size (not always one row).          */
1201 /***********************************************************************/
1202 int TDBXDBC::GetMaxSize(PGLOBAL g)
1203 {
1204   if (MaxSize < 0)
1205     MaxSize = 10;             // Just a guess
1206 
1207   return MaxSize;
1208 } // end of GetMaxSize
1209 
1210 /***********************************************************************/
1211 /*  ODBC Access Method opening routine.                                */
1212 /*  New method now that this routine is called recursively (last table */
1213 /*  first in reverse order): index blocks are immediately linked to    */
1214 /*  join block of next table if it exists or else are discarted.       */
1215 /***********************************************************************/
1216 bool TDBXDBC::OpenDB(PGLOBAL g)
1217 {
1218   bool rc = false;
1219 
1220   if (trace(1))
1221     htrc("ODBC OpenDB: tdbp=%p tdb=R%d use=%dmode=%d\n",
1222             this, Tdb_No, Use, Mode);
1223 
1224   if (Use == USE_OPEN) {
1225     strcpy(g->Message, "Multiple execution is not allowed");
1226     return true;
1227   } // endif use
1228 
1229   /*********************************************************************/
1230   /*  Open an ODBC connection for this table.                          */
1231   /*  Note: this may not be the proper way to do. Perhaps it is better */
1232   /*  to test whether a connection is already open for this datasource */
1233   /*  and if so to allocate just a new result set. But this only for   */
1234   /*  drivers allowing concurency in getting results ???               */
1235   /*********************************************************************/
1236   if (!Ocp) {
1237     Ocp = new(g) ODBConn(g, this);
1238   } else if (Ocp->IsOpen())
1239     Ocp->Close();
1240 
1241   if (Ocp->Open(Connect, &Ops, Options) < 1)
1242     return true;
1243 
1244   Use = USE_OPEN;       // Do it now in case we are recursively called
1245 
1246   if (Mode != MODE_READ && Mode != MODE_READX) {
1247     strcpy(g->Message, "No INSERT/DELETE/UPDATE of XDBC tables");
1248     return true;
1249   } // endif Mode
1250 
1251   /*********************************************************************/
1252   /*  Get the command to execute.                                      */
1253   /*********************************************************************/
1254   if (!(Cmdlist = MakeCMD(g))) {
1255 		// Next lines commented out because of CHECK TABLE
1256 		//Ocp->Close();
1257     //return true;
1258   } // endif Cmdlist
1259 
1260   Rows = 1;
1261   return false;
1262 } // end of OpenDB
1263 
1264 /***********************************************************************/
1265 /*  ReadDB: Data Base read routine for xdbc access method.             */
1266 /***********************************************************************/
1267 int TDBXDBC::ReadDB(PGLOBAL g)
1268 {
1269   if (Cmdlist) {
1270 		if (!Query)
1271 			Query = new(g)STRING(g, 0, Cmdlist->Cmd);
1272 		else
1273 			Query->Set(Cmdlist->Cmd);
1274 
1275     if (Ocp->ExecSQLcommand(Query->GetStr()))
1276       Nerr++;
1277 
1278     Fpos++;                // Used for progress info
1279     Cmdlist = (Nerr > Mxr) ? NULL : Cmdlist->Next;
1280     return RC_OK;
1281 	} else {
1282 		PushWarning(g, this, 1);
1283 		return RC_EF;
1284 	}	// endif Cmdlist
1285 
1286 } // end of ReadDB
1287 
1288 /***********************************************************************/
1289 /*  Data Base write line routine for XDBC access method.               */
1290 /***********************************************************************/
1291 int TDBXDBC::WriteDB(PGLOBAL g)
1292 {
1293   strcpy(g->Message, "Execsrc tables are read only");
1294   return RC_FX;
1295 } // end of DeleteDB
1296 
1297 /***********************************************************************/
1298 /*  Data Base delete line routine for XDBC access method.              */
1299 /***********************************************************************/
1300 int TDBXDBC::DeleteDB(PGLOBAL g, int irc)
1301 {
1302   strcpy(g->Message, MSG(NO_ODBC_DELETE));
1303   return RC_FX;
1304 } // end of DeleteDB
1305 
1306 /* --------------------------- XSRCCOL ------------------------------- */
1307 
1308 /***********************************************************************/
1309 /*  XSRCCOL public constructor.                                        */
1310 /***********************************************************************/
1311 XSRCCOL::XSRCCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PCSZ am)
1312        : ODBCCOL(cdp, tdbp, cprec, i, am)
1313 {
1314   // Set additional ODBC access method information for column.
1315   Flag = cdp->GetOffset();
1316 } // end of XSRCCOL constructor
1317 
1318 /***********************************************************************/
1319 /*  XSRCCOL constructor used for copying columns.                      */
1320 /*  tdbp is the pointer to the new table descriptor.                   */
1321 /***********************************************************************/
1322 XSRCCOL::XSRCCOL(XSRCCOL *col1, PTDB tdbp) : ODBCCOL(col1, tdbp)
1323 {
1324   Flag = col1->Flag;
1325 } // end of XSRCCOL copy constructor
1326 
1327 /***********************************************************************/
1328 /*  ReadColumn: set column value according to Flag.                    */
1329 /***********************************************************************/
1330 void XSRCCOL::ReadColumn(PGLOBAL g)
1331 {
1332   PTDBXDBC tdbp = (PTDBXDBC)To_Tdb;
1333 
1334   switch (Flag) {
1335     case  0: Value->SetValue_psz(tdbp->Query->GetStr()); break;
1336 		case  1: Value->SetValue(tdbp->AftRows);             break;
1337 		case  2: Value->SetValue_psz(g->Message);            break;
1338 		default: Value->SetValue_psz("Invalid Flag");        break;
1339     } // endswitch Flag
1340 
1341 } // end of ReadColumn
1342 
1343 /***********************************************************************/
1344 /*  WriteColumn: Should never be called.                               */
1345 /***********************************************************************/
1346 void XSRCCOL::WriteColumn(PGLOBAL g)
1347 {
1348   // Should never be called
1349 } // end of WriteColumn
1350 
1351 /* ---------------------------TDBDRV class --------------------------- */
1352 
1353 /***********************************************************************/
1354 /*  GetResult: Get the list of ODBC drivers.                           */
1355 /***********************************************************************/
1356 PQRYRES TDBDRV::GetResult(PGLOBAL g)
1357 {
1358   return ODBCDrivers(g, Maxres, false);
1359 } // end of GetResult
1360 
1361 /* ---------------------------TDBSRC class --------------------------- */
1362 
1363 /***********************************************************************/
1364 /*  GetResult: Get the list of ODBC data sources.                      */
1365 /***********************************************************************/
1366 PQRYRES TDBSRC::GetResult(PGLOBAL g)
1367 {
1368   return ODBCDataSources(g, Maxres, false);
1369 } // end of GetResult
1370 
1371 /* ---------------------------TDBOTB class --------------------------- */
1372 
1373 /***********************************************************************/
1374 /*  TDBOTB class constructor.                                          */
1375 /***********************************************************************/
1376 TDBOTB::TDBOTB(PODEF tdp) : TDBDRV(tdp)
1377 {
1378   Dsn = tdp->GetConnect();
1379   Schema = tdp->GetTabschema();
1380   Tab = tdp->GetTabname();
1381 	Tabtyp = tdp->Tabtyp;
1382   Ops.User = tdp->Username;
1383   Ops.Pwd = tdp->Password;
1384   Ops.Cto = tdp->Cto;
1385   Ops.Qto = tdp->Qto;
1386   Ops.UseCnc = tdp->UseCnc;
1387 } // end of TDBOTB constructor
1388 
1389 /***********************************************************************/
1390 /*  GetResult: Get the list of ODBC tables.                            */
1391 /***********************************************************************/
1392 PQRYRES TDBOTB::GetResult(PGLOBAL g)
1393 {
1394   return ODBCTables(g, Dsn, Schema, Tab, Tabtyp, Maxres, false, &Ops);
1395 } // end of GetResult
1396 
1397 /* ---------------------------TDBOCL class --------------------------- */
1398 
1399 /***********************************************************************/
1400 /*  TDBOCL class constructor.                                          */
1401 /***********************************************************************/
1402 TDBOCL::TDBOCL(PODEF tdp) : TDBOTB(tdp)
1403 {
1404 	Colpat = tdp->Colpat;
1405 } // end of TDBOTB constructor
1406 
1407 /***********************************************************************/
1408 /*  GetResult: Get the list of ODBC table columns.                     */
1409 /***********************************************************************/
1410 PQRYRES TDBOCL::GetResult(PGLOBAL g)
1411 {
1412   return ODBCColumns(g, Dsn, Schema, Tab, Colpat, Maxres, false, &Ops);
1413 } // end of GetResult
1414 
1415 /* ------------------------ End of Tabodbc --------------------------- */
1416