diff options
Diffstat (limited to 'storage/connect/tabutil.cpp')
-rw-r--r-- | storage/connect/tabutil.cpp | 804 |
1 files changed, 804 insertions, 0 deletions
diff --git a/storage/connect/tabutil.cpp b/storage/connect/tabutil.cpp new file mode 100644 index 00000000..941073b3 --- /dev/null +++ b/storage/connect/tabutil.cpp @@ -0,0 +1,804 @@ +/************* Tabutil cpp Declares Source Code File (.CPP) ************/ +/* Name: TABUTIL.CPP Version 1.2 */ +/* */ +/* (C) Copyright to the author Olivier BERTRAND 2013 - 2017 */ +/* */ +/* Utility function used by the PROXY, XCOL, OCCUR, and TBL tables. */ +/***********************************************************************/ + +/***********************************************************************/ +/* Include relevant section of system dependant header files. */ +/***********************************************************************/ +#define MYSQL_SERVER 1 +#include <my_global.h> +#include "sql_class.h" +#include "table.h" +#include "field.h" +#if defined(_WIN32) +#include <stdlib.h> +#include <stdio.h> +#if defined(__BORLANDC__) +#define __MFC_COMPAT__ // To define min/max as macro +#endif +//#include <windows.h> +#else +#if defined(UNIX) +#include <fnmatch.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "osutil.h" +#else +//#include <io.h> +#endif +//#include <fcntl.h> +#endif + +/***********************************************************************/ +/* Include application header files: */ +/***********************************************************************/ +#include "table.h" // MySQL table definitions +#include "global.h" +#include "plgdbsem.h" +#include "plgcnx.h" // For DB types +#include "myutil.h" +#include "valblk.h" +#include "resource.h" +//#include "reldef.h" +#include "xtable.h" +#include "tabext.h" +#include "tabmysql.h" +#include "tabcol.h" +#include "tabutil.h" +#include "ha_connect.h" + +int GetConvSize(void); + +/************************************************************************/ +/* Used by MYSQL tables to get MySQL parameters from the calling proxy */ +/* table (PROXY, TBL, XCL, or OCCUR) when used by one of these. */ +/************************************************************************/ +TABLE_SHARE *Remove_tshp(PCATLG cat) +{ + TABLE_SHARE *s = ((MYCAT*)cat)->GetHandler()->tshp; + + ((MYCAT*)cat)->GetHandler()->tshp = NULL; + return s; +} // end of Remove_thsp + +/************************************************************************/ +/* Used by MYSQL tables to get MySQL parameters from the calling proxy */ +/* table (PROXY, TBL, XCL, or OCCUR) when used by one of these. */ +/************************************************************************/ +void Restore_tshp(PCATLG cat, TABLE_SHARE *s) +{ + ((MYCAT*)cat)->GetHandler()->tshp = s; +} // end of Restore_thsp + +/************************************************************************/ +/* GetTableShare: allocates and open a table share. */ +/************************************************************************/ +TABLE_SHARE *GetTableShare(PGLOBAL g, THD *thd, const char *db, + const char *name, bool& mysql) +{ + char key[256]; + uint k; + TABLE_SHARE *s; + + k = sprintf(key, "%s", db) + 1; + k += sprintf(key + k, "%s", name); + key[++k] = 0; + + if (!(s = alloc_table_share(db, name, key, ++k))) { + strcpy(g->Message, "Error allocating share\n"); + return NULL; + } // endif s + + if (!open_table_def(thd, s, GTS_TABLE | GTS_VIEW)) { + if (!s->is_view) { + if (stricmp(plugin_name(s->db_plugin)->str, "connect")) + mysql = true; + else + mysql = false; + + } else + mysql = true; + + } else { + if (thd->is_error()) + thd->clear_error(); // Avoid stopping info commands + + sprintf(g->Message, "Error %d opening share\n", s->error); + free_table_share(s); + return NULL; + } // endif open_table_def + + return s; +} // end of GetTableShare + +/************************************************************************/ +/* TabColumns: constructs the result blocks containing all the columns */ +/* description of the object table that will be retrieved by discovery.*/ +/************************************************************************/ +PQRYRES TabColumns(PGLOBAL g, THD *thd, const char *db, + const char *name, bool& info) + { + int buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_STRING, TYPE_INT, + TYPE_INT, TYPE_SHORT, TYPE_SHORT, TYPE_SHORT, + TYPE_STRING, TYPE_STRING, TYPE_STRING}; + XFLD fldtyp[] = {FLD_NAME, FLD_TYPE, FLD_TYPENAME, FLD_PREC, + FLD_LENGTH, FLD_SCALE, FLD_RADIX, FLD_NULL, + FLD_REM, FLD_NO, FLD_CHARSET}; + unsigned int length[] = {0, 4, 16, 4, 4, 4, 4, 4, 0, 32, 32}; + PCSZ fmt; + char *pn, *tn, *fld, *colname, v; // *chset + int i, n, ncol = sizeof(buftyp) / sizeof(int); + int prec, len, type, scale; + int zconv = GetConvSize(); + bool mysql; + TABLE_SHARE *s = NULL; + Field* *field; + Field *fp; + PQRYRES qrp; + PCOLRES crp; + + if (!info) { + // Analyze the table name, it may have the format: [dbname.]tabname + if (strchr((char*)name, '.')) { + tn = (char*)PlugDup(g, name); + pn = strchr(tn, '.'); + *pn++ = 0; + db = tn; + name = pn; + } // endif pn + + if (!(s = GetTableShare(g, thd, db, name, mysql))) { + return NULL; + } else if (s->is_view) { + strcpy(g->Message, "Use MYSQL type to see columns from a view"); + info = true; // To tell caller name is a view + free_table_share(s); + return NULL; + } else + n = s->fieldnames.count; + + } else { + n = 0; + length[0] = 128; + } // endif info + + /**********************************************************************/ + /* Allocate the structures used to refer to the result set. */ + /**********************************************************************/ + if (!(qrp = PlgAllocResult(g, ncol, n, IDS_COLUMNS + 3, + buftyp, fldtyp, length, false, true))) + return NULL; + + // Some columns must be renamed + for (i = 0, crp = qrp->Colresp; crp; crp = crp->Next) + switch (++i) { + case 2: crp->Nulls = (char*)PlugSubAlloc(g, NULL, n); break; + case 10: crp->Name = "Date_fmt"; break; + case 11: crp->Name = "Collation"; break; + } // endswitch i + + if (info) + return qrp; + + /**********************************************************************/ + /* Now get the results into blocks. */ + /**********************************************************************/ + for (i = 0, field= s->field; *field; field++) { + fp= *field; + + // Get column name + crp = qrp->Colresp; // Column_Name + colname = (char *)fp->field_name.str; + crp->Kdata->SetValue(colname, i); + +// chset = (char *)fp->charset()->name; +// v = (!strcmp(chset, "binary")) ? 'B' : 0; + v = 0; + + if ((type = MYSQLtoPLG(fp->type(), &v)) == TYPE_ERROR) { + if (v == 'K') { + // Skip this column + sprintf(g->Message, "Column %s skipped (unsupported type)", colname); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + continue; + } // endif v + + sprintf(g->Message, "Column %s unsupported type", colname); + qrp = NULL; + break; + } // endif type + + if (v == 'X') { + len = zconv; + sprintf(g->Message, "Column %s converted to varchar(%d)", + colname, len); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + } // endif v + + crp = crp->Next; // Data_Type + crp->Kdata->SetValue(type, i); + + if (fp->flags & ZEROFILL_FLAG) + crp->Nulls[i] = 'Z'; + else if (fp->flags & UNSIGNED_FLAG) + crp->Nulls[i] = 'U'; + else // X means TEXT field + crp->Nulls[i] = (v == 'X') ? 'V' : v; + + crp = crp->Next; // Type_Name + crp->Kdata->SetValue(GetTypeName(type), i); + fmt = NULL; + + if (type == TYPE_DATE) { + // When creating tables we do need info about date columns + if (mysql) { + fmt = MyDateFmt(fp->type()); + prec = len = strlen(fmt); + } else { + fmt = (PCSZ)fp->option_struct->dateformat; + prec = len = fp->field_length; + } // endif mysql + + } else if (v != 'X') { + if (type == TYPE_DECIM) + prec = ((Field_new_decimal*)fp)->precision; + else + prec = fp->field_length; +// prec = (prec(???) == NOT_FIXED_DEC) ? 0 : fp->field_length; + + len = fp->char_length(); + } else + prec = len = zconv; + + crp = crp->Next; // Precision + crp->Kdata->SetValue(prec, i); + + crp = crp->Next; // Length + crp->Kdata->SetValue(len, i); + + crp = crp->Next; // Scale + scale = (type == TYPE_DOUBLE || type == TYPE_DECIM) ? fp->decimals() + : 0; + crp->Kdata->SetValue(scale, i); + + crp = crp->Next; // Radix + crp->Kdata->SetValue(0, i); + + crp = crp->Next; // Nullable + crp->Kdata->SetValue((fp->null_ptr != 0) ? 1 : 0, i); + + crp = crp->Next; // Remark + + // For Valgrind + if (fp->comment.length > 0 && (fld = (char*) fp->comment.str)) + crp->Kdata->SetValue(fld, fp->comment.length, i); + else + crp->Kdata->Reset(i); + + crp = crp->Next; // New (date format) + crp->Kdata->SetValue((fmt) ? fmt : (char*) "", i); + + crp = crp->Next; // New (charset) + fld = (char *)fp->charset()->name; + crp->Kdata->SetValue(fld, i); + + // Add this item + qrp->Nblin++; + i++; // Can be skipped + } // endfor field + + /**********************************************************************/ + /* Return the result pointer for use by GetData routines. */ + /**********************************************************************/ + if (s) + free_table_share(s); + + return qrp; + } // end of TabColumns + +/* -------------- Implementation of the PROXY classes ---------------- */ + +/***********************************************************************/ +/* PRXDEF constructor. */ +/***********************************************************************/ +PRXDEF::PRXDEF(void) + { + Tablep = NULL; + Pseudo = 3; +} // end of PRXDEF constructor + +/***********************************************************************/ +/* DefineAM: define specific AM block values from XCOL file. */ +/***********************************************************************/ +bool PRXDEF::DefineAM(PGLOBAL g, LPCSTR, int) + { + char *pn, *db, *tab, *def = NULL; + + db = GetStringCatInfo(g, "Dbname", "*"); + def = GetStringCatInfo(g, "Srcdef", NULL); + + if (!(tab = GetStringCatInfo(g, "Tabname", NULL))) { + if (!def) { + strcpy(g->Message, "Missing object table definition"); + return true; + } else + tab = PlugDup(g, "Noname"); + + } else + // Analyze the table name, it may have the format: [dbname.]tabname + if ((pn = strchr(tab, '.'))) { + *pn++ = 0; + db = tab; + tab = pn; + } // endif pn + + Tablep = new(g) XTAB(tab, def); + Tablep->SetSchema(db); + return false; + } // end of DefineAM + +/***********************************************************************/ +/* GetTable: makes a new TDB of the proper type. */ +/***********************************************************************/ +PTDB PRXDEF::GetTable(PGLOBAL g, MODE) + { + if (Catfunc == FNC_COL) + return new(g) TDBTBC(this); + else + return new(g) TDBPRX(this); + + } // end of GetTable + +/* ------------------------------------------------------------------- */ + +/***********************************************************************/ +/* Implementation of the TDBPRX class. */ +/***********************************************************************/ +TDBPRX::TDBPRX(PPRXDEF tdp) : TDBASE(tdp) + { + Tdbp = NULL; // The object table + } // end of TDBPRX constructor + +TDBPRX::TDBPRX(PTDBPRX tdbp) : TDBASE(tdbp) + { + Tdbp = tdbp->Tdbp; + } // end of TDBPRX copy constructor + +// Method +PTDB TDBPRX::Clone(PTABS t) + { + PTDB tp; + PPRXCOL cp1, cp2; + PGLOBAL g = t->G; + + tp = new(g) TDBPRX(this); + + for (cp1 = (PPRXCOL)Columns; cp1; cp1 = (PPRXCOL)cp1->GetNext()) { + cp2 = new(g) PRXCOL(cp1, tp); // Make a copy + NewPointer(t, cp1, cp2); + } // endfor cp1 + + return tp; + } // end of Clone + +/***********************************************************************/ +/* Get the PTDB of the sub-table. */ +/***********************************************************************/ +PTDB TDBPRX::GetSubTable(PGLOBAL g, PTABLE tabp, bool b) + { + const char *sp = NULL; + char *db, *name; + bool mysql = true; + PTDB tdbp = NULL; + TABLE_SHARE *s = NULL; + Field* *fp = NULL; + PCATLG cat = To_Def->GetCat(); + PHC hc = ((MYCAT*)cat)->GetHandler(); + LPCSTR cdb, curdb = hc->GetDBName(NULL); + THD *thd = (hc->GetTable())->in_use; + + db = (char*)(tabp->GetSchema() ? tabp->GetSchema() : curdb); + name = (char*)tabp->GetName(); + + // Check for eventual loop + for (PTABLE tp = To_Table; tp; tp = tp->Next) { + cdb = (tp->Schema) ? tp->Schema : curdb; + + if (!stricmp(name, tp->Name) && !stricmp(db, cdb)) { + sprintf(g->Message, "Table %s.%s pointing on itself", db, name); + return NULL; + } // endif + + } // endfor tp + + if (!tabp->GetSrc()) { + if (!(s = GetTableShare(g, thd, db, name, mysql))) + return NULL; + + if (s->is_view && !b) + s->field = hc->get_table()->s->field; + + hc->tshp = s; + } else if (b) { + // Don't use caller's columns + fp = hc->get_table()->field; + hc->get_table()->field = NULL; + + // Make caller use the source definition + sp = hc->get_table()->s->option_struct->srcdef; + hc->get_table()->s->option_struct->srcdef = tabp->GetSrc(); + } // endif srcdef + + if (mysql) { + // Access sub-table via MySQL API + if (!(tdbp= cat->GetTable(g, tabp, Mode, "MYPRX"))) { + char buf[MAX_STR]; + + strcpy(buf, g->Message); + snprintf(g->Message, MAX_STR, "Error accessing %s.%s: %s", db, name, buf); + hc->tshp = NULL; + goto err; + } // endif Define + + if (db) + ((PTDBMY)tdbp)->SetDatabase(tabp->GetSchema()); + + if (Mode == MODE_UPDATE || Mode == MODE_DELETE) + tdbp->SetName(Name); // For Make_Command + + } else { + // Sub-table is a CONNECT table + tabp->Next = To_Table; // For loop checking + tdbp = cat->GetTable(g, tabp, Mode); + } // endif mysql + + if (s) { + if (s->is_view && !b) + s->field = NULL; + + hc->tshp = NULL; + } else if (b) { + // Restore s structure that can be in cache + hc->get_table()->field = fp; + hc->get_table()->s->option_struct->srcdef = sp; + } // endif s + + if (trace(1) && tdbp) + htrc("Subtable %s in %s\n", + name, SVP(tdbp->GetDef()->GetDB())); + + err: + if (s) + free_table_share(s); + + return tdbp; + } // end of GetSubTable + +/***********************************************************************/ +/* Initializes the table. */ +/***********************************************************************/ +bool TDBPRX::InitTable(PGLOBAL g) + { + if (!Tdbp) { + // Get the table description block of this table + if (!(Tdbp = GetSubTable(g, ((PPRXDEF)To_Def)->Tablep))) + return true; + +// Tdbp->SetMode(Mode); + } // endif Tdbp + + return false; + } // end of InitTable + +/***********************************************************************/ +/* Allocate PRX column description block. */ +/***********************************************************************/ +PCOL TDBPRX::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n) + { + return new(g) PRXCOL(cdp, this, cprec, n); + } // end of MakeCol + +/***********************************************************************/ +/* PRX Cardinality: returns the number of rows in the table. */ +/***********************************************************************/ +int TDBPRX::Cardinality(PGLOBAL g) + { + if (Cardinal < 0) { + if (InitTable(g)) + return 0; + + Cardinal = Tdbp->Cardinality(g); + } // endif MaxSize + + return Cardinal; + } // end of GetMaxSize + +/***********************************************************************/ +/* PRX GetMaxSize: returns the maximum number of rows in the table. */ +/***********************************************************************/ +int TDBPRX::GetMaxSize(PGLOBAL g) + { + if (MaxSize < 0) { + if (InitTable(g)) + return 0; + + MaxSize = Tdbp->GetMaxSize(g); + } // endif MaxSize + + return MaxSize; + } // end of GetMaxSize + +/***********************************************************************/ +/* In this sample, ROWID will be the (virtual) row number, */ +/* while ROWNUM will be the occurrence rank in the multiple column. */ +/***********************************************************************/ +int TDBPRX::RowNumber(PGLOBAL g, bool b) + { + return Tdbp->RowNumber(g, b); + } // end of RowNumber + +/***********************************************************************/ +/* PROXY Access Method opening routine. */ +/***********************************************************************/ +bool TDBPRX::OpenDB(PGLOBAL g) + { + if (Use == USE_OPEN) { + /*******************************************************************/ + /* Table already open, just replace it at its beginning. */ + /*******************************************************************/ + return Tdbp->OpenDB(g); + } // endif use + + if (InitTable(g)) + return true; + else if (Mode != MODE_READ && (Read_Only || Tdbp->IsReadOnly())) { + strcpy(g->Message, "Cannot modify a read only table"); + return true; + } // endif tp + + /*********************************************************************/ + /* Check and initialize the subtable columns. */ + /*********************************************************************/ + for (PCOL cp = Columns; cp; cp = cp->GetNext()) + if (((PPRXCOL)cp)->Init(g, Tdbp)) + return true; + + /*********************************************************************/ + /* In Update mode, the updated column blocks must be distinct from */ + /* the read column blocks. So make a copy of the TDB and allocate */ + /* its column blocks in mode write (required by XML tables). */ + /*********************************************************************/ + if (Mode == MODE_UPDATE) { + PTDB utp; + + if (!(utp= Tdbp->Duplicate(g))) { + sprintf(g->Message, MSG(INV_UPDT_TABLE), Tdbp->GetName()); + return true; + } // endif tp + + for (PCOL cp = To_SetCols; cp; cp = cp->GetNext()) + if (((PPRXCOL)cp)->Init(g, utp)) + return true; + + } else if (Mode == MODE_DELETE) + Tdbp->SetNext(Next); + + /*********************************************************************/ + /* Physically open the object table. */ + /*********************************************************************/ + if (Tdbp->OpenDB(g)) + return true; + + Tdbp->SetNext(NULL); + Use = USE_OPEN; + return false; + } // end of OpenDB + +/***********************************************************************/ +/* Data Base read routine for PROY access method. */ +/***********************************************************************/ +int TDBPRX::ReadDB(PGLOBAL g) + { + /*********************************************************************/ + /* Now start the reading process. */ + /*********************************************************************/ + return Tdbp->ReadDB(g); + } // end of ReadDB + +/***********************************************************************/ +/* WriteDB: Data Base write routine for PROXY access methods. */ +/***********************************************************************/ +int TDBPRX::WriteDB(PGLOBAL g) + { + return Tdbp->WriteDB(g); + } // end of WriteDB + +/***********************************************************************/ +/* Data Base delete line routine for PROXY access methods. */ +/***********************************************************************/ +int TDBPRX::DeleteDB(PGLOBAL g, int irc) + { + return Tdbp->DeleteDB(g, irc); + } // end of DeleteDB + +/***********************************************************************/ +/* Used by the TBL tables. */ +/***********************************************************************/ +void TDBPRX::RemoveNext(PTABLE tp) + { + tp->Next = NULL; + } // end of RemoveNext + +/* ---------------------------- PRXCOL ------------------------------- */ + +/***********************************************************************/ +/* PRXCOL public constructor. */ +/***********************************************************************/ +PRXCOL::PRXCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PCSZ am) + : COLBLK(cdp, tdbp, i) + { + if (cprec) { + Next = cprec->GetNext(); + cprec->SetNext(this); + } else { + Next = tdbp->GetColumns(); + tdbp->SetColumns(this); + } // endif cprec + + // Set additional Dos access method information for column. + Long = cdp->GetLong(); // Useful ??? +//strcpy(F_Date, cdp->F_Date); + Colp = NULL; + To_Val = NULL; + Pseudo = false; + Colnum = cdp->GetOffset(); // If columns are retrieved by number + + if (trace(1)) + htrc(" making new %sCOL C%d %s at %p\n", am, Index, Name, this); + + } // end of PRXCOL constructor + +/***********************************************************************/ +/* PRXCOL constructor used for copying columns. */ +/* tdbp is the pointer to the new table descriptor. */ +/***********************************************************************/ +PRXCOL::PRXCOL(PRXCOL *col1, PTDB tdbp) : COLBLK(col1, tdbp) + { + Colp = col1->Colp; + To_Val = col1->To_Val; + Pseudo = col1->Pseudo; + Colnum = col1->Colnum; + } // end of PRXCOL copy constructor + +/***********************************************************************/ +/* Convert an UTF-8 name to latin characters. */ +/***********************************************************************/ +char *PRXCOL::Decode(PGLOBAL g, const char *cnm) + { + char *buf= (char*)PlugSubAlloc(g, NULL, strlen(cnm) + 1); + uint dummy_errors; + uint32 len= copy_and_convert(buf, strlen(cnm) + 1, + &my_charset_latin1, + cnm, strlen(cnm), + &my_charset_utf8mb3_general_ci, + &dummy_errors); + buf[len]= '\0'; + return buf; + } // end of Decode + +/***********************************************************************/ +/* PRXCOL initialization routine. */ +/* Look for the matching column in the object table. */ +/***********************************************************************/ +bool PRXCOL::Init(PGLOBAL g, PTDB tp) + { + if (!tp) + tp = ((PTDBPRX)To_Tdb)->Tdbp; + + if (!(Colp = tp->ColDB(g, Name, 0)) && Colnum) + Colp = tp->ColDB(g, NULL, Colnum); + + if (Colp) { + MODE mode = To_Tdb->GetMode(); + + // Needed for MYSQL subtables + ((COLBLK*)Colp)->SetName(Decode(g, Colp->GetName())); + + // May not have been done elsewhere + Colp->InitValue(g); + To_Val = Colp->GetValue(); + + if (mode == MODE_INSERT || mode == MODE_UPDATE) + if (Colp->SetBuffer(g, Colp->GetValue(), true, false)) + return true; + + // this may be needed by some tables (which?) + Colp->SetColUse(ColUse); + } else { + sprintf(g->Message, MSG(NO_MATCHING_COL), Name, tp->GetName()); + return true; + } // endif Colp + + return false; + } // end of Init + +/***********************************************************************/ +/* Reset the column descriptor to non evaluated yet. */ +/***********************************************************************/ +void PRXCOL::Reset(void) + { + if (Colp) + Colp->Reset(); + + Status &= ~BUF_READ; + } // end of Reset + +/***********************************************************************/ +/* ReadColumn: */ +/***********************************************************************/ +void PRXCOL::ReadColumn(PGLOBAL g) + { + if (trace(2)) + htrc("PRX ReadColumn: name=%s\n", Name); + + if (Colp) { + Colp->Eval(g); + Value->SetValue_pval(To_Val); + + // Set null when applicable + if (Nullable) + Value->SetNull(Value->IsNull()); + + } else { + Value->Reset(); + + // Set null when applicable + if (Nullable) + Value->SetNull(true); + + } // endif Colp + + } // end of ReadColumn + +/***********************************************************************/ +/* WriteColumn: */ +/***********************************************************************/ +void PRXCOL::WriteColumn(PGLOBAL g) + { + if (trace(2)) + htrc("PRX WriteColumn: name=%s\n", Name); + + if (Colp) { + To_Val->SetValue_pval(Value); + Colp->WriteColumn(g); + } // endif Colp + + } // end of WriteColumn + +/* ---------------------------TDBTBC class --------------------------- */ + +/***********************************************************************/ +/* TDBTBC class constructor. */ +/***********************************************************************/ +TDBTBC::TDBTBC(PPRXDEF tdp) : TDBCAT(tdp) + { + Db = (PSZ)tdp->Tablep->GetSchema(); + Tab = (PSZ)tdp->Tablep->GetName(); + } // end of TDBTBC constructor + +/***********************************************************************/ +/* GetResult: Get the list the MYSQL table columns. */ +/***********************************************************************/ +PQRYRES TDBTBC::GetResult(PGLOBAL g) + { + bool b = false; + + return TabColumns(g, current_thd, Db, Tab, b); + } // end of GetResult + |