mirror of
https://github.com/MariaDB/server.git
synced 2025-01-29 10:14:19 +01:00
2ff01e763e
Old style C functions `strcpy()`, `strcat()` and `sprintf()` are vulnerable to security issues due to lacking memory boundary checks. Replace these in the Connect storage engine with safe new and/or custom functions such as `snprintf()` `safe_strcpy()` and `safe_strcat()`. With this change FlawFinder and other static security analyzers report 287 fewer findings. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc.
1755 lines
57 KiB
C++
1755 lines
57 KiB
C++
/************* TabMySQL C++ Program Source Code File (.CPP) *************/
|
|
/* PROGRAM NAME: TABMYSQL */
|
|
/* ------------- */
|
|
/* Version 2.0 */
|
|
/* */
|
|
/* AUTHOR: */
|
|
/* ------- */
|
|
/* Olivier BERTRAND 2007-2017 */
|
|
/* */
|
|
/* WHAT THIS PROGRAM DOES: */
|
|
/* ----------------------- */
|
|
/* Implements a table type that are MySQL tables. */
|
|
/* It can optionally use the embedded MySQL library. */
|
|
/* */
|
|
/* WHAT YOU NEED TO COMPILE THIS PROGRAM: */
|
|
/* -------------------------------------- */
|
|
/* */
|
|
/* REQUIRED FILES: */
|
|
/* --------------- */
|
|
/* TABMYSQL.CPP - Source code */
|
|
/* PLGDBSEM.H - DB application declaration file */
|
|
/* TABMYSQL.H - TABMYSQL 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 */
|
|
/* */
|
|
/************************************************************************/
|
|
#define MYSQL_SERVER 1
|
|
#include "my_global.h"
|
|
#include "sql_class.h"
|
|
#include "sql_servers.h"
|
|
#if defined(_WIN32)
|
|
//#include <windows.h>
|
|
#else // !_WIN32
|
|
//#include <fnmatch.h>
|
|
//#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "osutil.h"
|
|
//#include <io.h>
|
|
//#include <fcntl.h>
|
|
#endif // !_WIN32
|
|
|
|
/***********************************************************************/
|
|
/* Include application header files: */
|
|
/***********************************************************************/
|
|
#include "global.h"
|
|
#include "plgdbsem.h"
|
|
#include "xtable.h"
|
|
#include "tabext.h"
|
|
#include "tabcol.h"
|
|
#include "colblk.h"
|
|
//#include "reldef.h"
|
|
#include "tabmysql.h"
|
|
#include "valblk.h"
|
|
#include "tabutil.h"
|
|
#include "ha_connect.h"
|
|
|
|
#if defined(_CONSOLE)
|
|
void PrintResult(PGLOBAL, PSEM, PQRYRES);
|
|
#endif // _CONSOLE
|
|
|
|
// Used to check whether a MYSQL table is created on itself
|
|
bool CheckSelf(PGLOBAL g, TABLE_SHARE *s, PCSZ host, PCSZ db,
|
|
PCSZ tab, PCSZ src, int port);
|
|
|
|
/***********************************************************************/
|
|
/* External function. */
|
|
/***********************************************************************/
|
|
bool ExactInfo(void);
|
|
|
|
/* -------------- Implementation of the MYSQLDEF class --------------- */
|
|
|
|
/***********************************************************************/
|
|
/* Constructor. */
|
|
/***********************************************************************/
|
|
MYSQLDEF::MYSQLDEF(void)
|
|
{
|
|
Pseudo = 2; // SERVID is Ok but not ROWID
|
|
Hostname = NULL;
|
|
//Tabschema = NULL;
|
|
//Tabname = NULL;
|
|
//Srcdef = NULL;
|
|
//Username = NULL;
|
|
//Password = NULL;
|
|
Portnumber = 0;
|
|
Isview = false;
|
|
Bind = false;
|
|
Delayed = false;
|
|
Ignored = false;
|
|
//Xsrc = false;
|
|
Huge = false;
|
|
} // end of MYSQLDEF constructor
|
|
|
|
/***********************************************************************/
|
|
/* Get connection info from the declared server. */
|
|
/***********************************************************************/
|
|
bool MYSQLDEF::GetServerInfo(PGLOBAL g, const char *server_name)
|
|
{
|
|
THD *thd= current_thd;
|
|
MEM_ROOT *mem= thd->mem_root;
|
|
FOREIGN_SERVER *server, server_buffer;
|
|
DBUG_ENTER("GetServerInfo");
|
|
DBUG_PRINT("info", ("server_name %s", server_name));
|
|
|
|
if (!server_name || !strlen(server_name)) {
|
|
DBUG_PRINT("info", ("server_name not defined!"));
|
|
snprintf(g->Message, sizeof(g->Message), "server_name not defined!");
|
|
DBUG_RETURN(true);
|
|
} // endif server_name
|
|
|
|
// get_server_by_name() clones the server if exists and allocates
|
|
// copies of strings in the supplied mem_root
|
|
if (!(server= get_server_by_name(mem, server_name, &server_buffer))) {
|
|
DBUG_PRINT("info", ("get_server_by_name returned > 0 error condition!"));
|
|
/* need to come up with error handling */
|
|
snprintf(g->Message, sizeof(g->Message), "get_server_by_name returned > 0 error condition!");
|
|
DBUG_RETURN(true);
|
|
} // endif server
|
|
|
|
DBUG_PRINT("info", ("get_server_by_name returned server at %p",
|
|
server));
|
|
|
|
// TODO: We need to examine which of these can really be NULL
|
|
Hostname = PlugDup(g, server->host);
|
|
Tabschema = PlugDup(g, server->db);
|
|
Username = PlugDup(g, server->username);
|
|
Password = PlugDup(g, server->password);
|
|
Portnumber = (server->port) ? server->port : GetDefaultPort();
|
|
|
|
DBUG_RETURN(false);
|
|
} // end of GetServerInfo
|
|
|
|
/***********************************************************************/
|
|
/* Parse connection string */
|
|
/* */
|
|
/* SYNOPSIS */
|
|
/* ParseURL() */
|
|
/* url The connection string to parse */
|
|
/* */
|
|
/* DESCRIPTION */
|
|
/* Populates the table with information about the connection */
|
|
/* to the foreign database that will serve as the data source. */
|
|
/* This string must be specified (currently) in the "CONNECTION" */
|
|
/* field, listed in the CREATE TABLE statement. */
|
|
/* */
|
|
/* This string MUST be in the format of any of these: */
|
|
/* */
|
|
/* CONNECTION="scheme://user:pwd@host:port/database/table" */
|
|
/* CONNECTION="scheme://user@host/database/table" */
|
|
/* CONNECTION="scheme://user@host:port/database/table" */
|
|
/* CONNECTION="scheme://user:pwd@host/database/table" */
|
|
/* */
|
|
/* _OR_ */
|
|
/* */
|
|
/* CONNECTION="connection name" (NIY) */
|
|
/* */
|
|
/* An Example: */
|
|
/* */
|
|
/* CREATE TABLE t1 (id int(32)) */
|
|
/* ENGINE="CONNECT" TABLE_TYPE="MYSQL" */
|
|
/* CONNECTION="mysql://joe:pwd@192.168.1.111:9308/dbname/tabname"; */
|
|
/* */
|
|
/* CREATE TABLE t2 ( */
|
|
/* id int(4) NOT NULL auto_increment, */
|
|
/* name varchar(32) NOT NULL, */
|
|
/* PRIMARY KEY(id) */
|
|
/* ) ENGINE="CONNECT" TABLE_TYPE="MYSQL" */
|
|
/* CONNECTION="my_conn"; (NIY) */
|
|
/* */
|
|
/* 'password' and 'port' are both optional. */
|
|
/* */
|
|
/* RETURN VALUE */
|
|
/* false success */
|
|
/* true error */
|
|
/* */
|
|
/***********************************************************************/
|
|
bool MYSQLDEF::ParseURL(PGLOBAL g, char *url, bool b)
|
|
{
|
|
char *tabn, *pwd, *schema;
|
|
|
|
if ((!strstr(url, "://") && (!strchr(url, '@')))) {
|
|
// No :// or @ in connection string. Must be a straight
|
|
// connection name of either "server" or "server/table"
|
|
// ok, so we do a little parsing, but not completely!
|
|
if ((tabn= strchr(url, '/'))) {
|
|
// If there is a single '/' in the connection string,
|
|
// this means the user is specifying a table name
|
|
*tabn++= '\0';
|
|
|
|
// there better not be any more '/'s !
|
|
if (strchr(tabn, '/'))
|
|
return true;
|
|
|
|
Tabname = tabn;
|
|
} else
|
|
// Otherwise, straight server name,
|
|
Tabname = (b) ? GetStringCatInfo(g, "Tabname", Name) : NULL;
|
|
|
|
if (trace(1))
|
|
htrc("server: %s TableName: %s", url, Tabname);
|
|
|
|
Server = url;
|
|
return GetServerInfo(g, url);
|
|
} else {
|
|
// URL, parse it
|
|
char *sport, *scheme = url;
|
|
|
|
if (!(Username = strstr(url, "://"))) {
|
|
snprintf(g->Message, sizeof(g->Message), "Connection is not an URL");
|
|
return true;
|
|
} // endif User
|
|
|
|
scheme[Username - scheme] = 0;
|
|
|
|
if (stricmp(scheme, "mysql")) {
|
|
snprintf(g->Message, sizeof(g->Message), "scheme must be mysql");
|
|
return true;
|
|
} // endif scheme
|
|
|
|
Username += 3;
|
|
|
|
if (!(Hostname = (char*)strchr(Username, '@'))) {
|
|
snprintf(g->Message, sizeof(g->Message), "No host specified in URL");
|
|
return true;
|
|
} else {
|
|
*Hostname++ = 0; // End Username
|
|
Server = Hostname;
|
|
} // endif Hostname
|
|
|
|
if ((pwd = (char*)strchr(Username, ':'))) {
|
|
*pwd++ = 0; // End username
|
|
|
|
// Make sure there isn't an extra /
|
|
if (strchr(pwd, '/')) {
|
|
snprintf(g->Message, sizeof(g->Message), "Syntax error in URL");
|
|
return true;
|
|
} // endif
|
|
|
|
// Found that if the string is:
|
|
// user:@hostname:port/db/table
|
|
// Then password is a null string, so set to NULL
|
|
if ((pwd[0] == 0))
|
|
Password = NULL;
|
|
else
|
|
Password = pwd;
|
|
|
|
} // endif password
|
|
|
|
// Make sure there isn't an extra / or @ */
|
|
if ((strchr(Username, '/')) || (strchr(Hostname, '@'))) {
|
|
snprintf(g->Message, sizeof(g->Message), "Syntax error in URL");
|
|
return true;
|
|
} // endif
|
|
|
|
if ((schema = strchr(Hostname, '/'))) {
|
|
*schema++ = 0;
|
|
|
|
if ((tabn = strchr(schema, '/'))) {
|
|
*tabn++ = 0;
|
|
|
|
// Make sure there's not an extra /
|
|
if ((strchr(tabn, '/'))) {
|
|
snprintf(g->Message, sizeof(g->Message), "Syntax error in URL");
|
|
return true;
|
|
} // endif /
|
|
|
|
Tabname = tabn;
|
|
} // endif TableName
|
|
|
|
Tabschema = schema;
|
|
} // endif database
|
|
|
|
if ((sport = strchr(Hostname, ':')))
|
|
*sport++ = 0;
|
|
|
|
// For unspecified values, get the values of old style options
|
|
// but only if called from MYSQLDEF, else set them to NULL
|
|
Portnumber = (sport && sport[0]) ? atoi(sport)
|
|
: (b) ? GetIntCatInfo("Port", GetDefaultPort()) : 0;
|
|
|
|
if (Username[0] == 0)
|
|
Username = (b) ? GetStringCatInfo(g, "User", "*") : NULL;
|
|
|
|
if (Hostname[0] == 0)
|
|
Hostname = (b) ? GetStringCatInfo(g, "Host", "localhost") : NULL;
|
|
|
|
if (!Tabschema || !*Tabschema)
|
|
Tabschema = (b) ? GetStringCatInfo(g, "Database", "*") : NULL;
|
|
|
|
if (!Tabname || !*Tabname)
|
|
Tabname = (b) ? GetStringCatInfo(g, "Tabname", Name) : NULL;
|
|
|
|
if (!Password)
|
|
Password = (b) ? GetStringCatInfo(g, "Password", NULL) : NULL;
|
|
} // endif URL
|
|
|
|
#if 0
|
|
if (!share->port)
|
|
if (!share->hostname || strcmp(share->hostname, my_localhost) == 0)
|
|
share->socket= (char *) MYSQL_UNIX_ADDR;
|
|
else
|
|
share->port= MYSQL_PORT;
|
|
#endif // 0
|
|
|
|
return false;
|
|
} // end of ParseURL
|
|
|
|
/***********************************************************************/
|
|
/* DefineAM: define specific AM block values from XCV file. */
|
|
/***********************************************************************/
|
|
bool MYSQLDEF::DefineAM(PGLOBAL g, LPCSTR am, int)
|
|
{
|
|
char *url;
|
|
|
|
Desc = "MySQL Table";
|
|
|
|
Delayed = !!GetIntCatInfo("Delayed", 0);
|
|
Ignored = !!GetIntCatInfo("Ignored", 0);
|
|
|
|
if (stricmp(am, "MYPRX")) {
|
|
// Normal case of specific MYSQL table
|
|
url = GetStringCatInfo(g, "Connect", NULL);
|
|
|
|
if (!url || !*url) {
|
|
// Not using the connection URL
|
|
Hostname = GetStringCatInfo(g, "Host", "localhost");
|
|
Tabschema = GetStringCatInfo(g, "Database", "*");
|
|
Tabname = GetStringCatInfo(g, "Name", Name); // Deprecated
|
|
Tabname = GetStringCatInfo(g, "Tabname", Tabname);
|
|
Username = GetStringCatInfo(g, "User", "*");
|
|
Password = GetStringCatInfo(g, "Password", NULL);
|
|
Portnumber = GetIntCatInfo("Port", GetDefaultPort());
|
|
Server = Hostname;
|
|
} else if (ParseURL(g, url))
|
|
return true;
|
|
|
|
Bind = !!GetIntCatInfo("Bind", 0);
|
|
} else {
|
|
// MYSQL access from a PROXY table
|
|
TABLE_SHARE* s;
|
|
|
|
Tabschema = GetStringCatInfo(g, "Database", Tabschema ? Tabschema : PlugDup(g, "*"));
|
|
Isview = GetBoolCatInfo("View", false);
|
|
|
|
// We must get other connection parms from the calling table
|
|
s = Remove_tshp(Cat);
|
|
url = GetStringCatInfo(g, "Connect", NULL);
|
|
|
|
if (!url || !*url) {
|
|
Hostname = GetStringCatInfo(g, "Host", "localhost");
|
|
Username = GetStringCatInfo(g, "User", "*");
|
|
Password = GetStringCatInfo(g, "Password", NULL);
|
|
Portnumber = GetIntCatInfo("Port", GetDefaultPort());
|
|
Server = Hostname;
|
|
} else {
|
|
PCSZ locdb = Tabschema;
|
|
|
|
if (ParseURL(g, url))
|
|
return true;
|
|
|
|
Tabschema = locdb;
|
|
} // endif url
|
|
|
|
Tabname = Name;
|
|
|
|
// Needed for column description
|
|
Restore_tshp(Cat, s);
|
|
} // endif am
|
|
|
|
if ((Srcdef = GetStringCatInfo(g, "Srcdef", NULL))) {
|
|
Read_Only = true;
|
|
Isview = true;
|
|
} else if (CheckSelf(g, Hc->GetTable()->s, Hostname, Tabschema,
|
|
Tabname, Srcdef, Portnumber))
|
|
return true;
|
|
|
|
// Used for Update and Delete
|
|
Qrystr = GetStringCatInfo(g, "Query_String", "?");
|
|
Quoted = GetIntCatInfo("Quoted", 0);
|
|
|
|
// Specific for command executing tables
|
|
Xsrc = GetBoolCatInfo("Execsrc", false);
|
|
Maxerr = GetIntCatInfo("Maxerr", 0);
|
|
Huge = GetBoolCatInfo("Huge", false);
|
|
return false;
|
|
} // end of DefineAM
|
|
|
|
/***********************************************************************/
|
|
/* GetTable: makes a new TDB of the proper type. */
|
|
/***********************************************************************/
|
|
PTDB MYSQLDEF::GetTable(PGLOBAL g, MODE)
|
|
{
|
|
if (Xsrc)
|
|
return new(g) TDBMYEXC(this);
|
|
else if (Catfunc == FNC_COL)
|
|
return new(g) TDBMCL(this);
|
|
else
|
|
return new(g) TDBMYSQL(this);
|
|
|
|
} // end of GetTable
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/***********************************************************************/
|
|
/* Implementation of the TDBMYSQL class. */
|
|
/***********************************************************************/
|
|
TDBMYSQL::TDBMYSQL(PMYDEF tdp) : TDBEXT(tdp)
|
|
{
|
|
if (tdp) {
|
|
Host = tdp->Hostname;
|
|
// Schema = tdp->Tabschema;
|
|
// TableName = tdp->Tabname;
|
|
// Srcdef = tdp->Srcdef;
|
|
// User = tdp->Username;
|
|
// Pwd = tdp->Password;
|
|
Server = tdp->Server;
|
|
// Qrystr = tdp->Qrystr;
|
|
Quoted = MY_MAX(0, tdp->Quoted);
|
|
Port = tdp->Portnumber;
|
|
Isview = tdp->Isview;
|
|
Prep = tdp->Bind;
|
|
Delayed = tdp->Delayed;
|
|
Ignored = tdp->Ignored;
|
|
Myc.m_Use = tdp->Huge;
|
|
} else {
|
|
Host = NULL;
|
|
// Schema = NULL;
|
|
// TableName = NULL;
|
|
// Srcdef = NULL;
|
|
// User = NULL;
|
|
// Pwd = NULL;
|
|
Server = NULL;
|
|
// Qrystr = NULL;
|
|
// Quoted = 0;
|
|
Port = 0;
|
|
Isview = false;
|
|
Prep = false;
|
|
Delayed = false;
|
|
Ignored = false;
|
|
} // endif tdp
|
|
|
|
Bind = NULL;
|
|
//Query = NULL;
|
|
Fetched = false;
|
|
m_Rc = RC_FX;
|
|
//AftRows = 0;
|
|
N = -1;
|
|
//Nparm = 0;
|
|
} // end of TDBMYSQL constructor
|
|
|
|
TDBMYSQL::TDBMYSQL(PTDBMY tdbp) : TDBEXT(tdbp)
|
|
{
|
|
Host = tdbp->Host;
|
|
//Schema = tdbp->Schema;
|
|
//TableName = tdbp->TableName;
|
|
//Srcdef = tdbp->Srcdef;
|
|
//User = tdbp->User;
|
|
//Pwd = tdbp->Pwd;
|
|
//Qrystr = tdbp->Qrystr;
|
|
//Quoted = tdbp->Quoted;
|
|
Server = tdbp->Server;
|
|
Port = tdbp->Port;
|
|
Isview = tdbp->Isview;
|
|
Prep = tdbp->Prep;
|
|
Delayed = tdbp->Delayed;
|
|
Ignored = tdbp->Ignored;
|
|
Bind = NULL;
|
|
//Query = tdbp->Query;
|
|
Fetched = tdbp->Fetched;
|
|
m_Rc = tdbp->m_Rc;
|
|
//AftRows = tdbp->AftRows;
|
|
N = tdbp->N;
|
|
//Nparm = tdbp->Nparm;
|
|
} // end of TDBMYSQL copy constructor
|
|
|
|
// Is this really useful ??? --> Yes for UPDATE
|
|
PTDB TDBMYSQL::Clone(PTABS t)
|
|
{
|
|
PTDB tp;
|
|
PCOL cp1, cp2;
|
|
PGLOBAL g = t->G;
|
|
|
|
tp = new(g) TDBMYSQL(this);
|
|
|
|
for (cp1 = Columns; cp1; cp1 = cp1->GetNext()) {
|
|
cp2 = new(g) MYSQLCOL((PMYCOL)cp1, tp);
|
|
|
|
NewPointer(t, cp1, cp2);
|
|
} // endfor cp1
|
|
|
|
return tp;
|
|
} // end of Clone
|
|
|
|
/***********************************************************************/
|
|
/* Allocate MYSQL column description block. */
|
|
/***********************************************************************/
|
|
PCOL TDBMYSQL::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
|
|
{
|
|
return new(g) MYSQLCOL(cdp, this, cprec, n);
|
|
} // end of MakeCol
|
|
|
|
/***********************************************************************/
|
|
/* MakeSelect: make the Select statement use with MySQL connection. */
|
|
/* Note: when implementing EOM filtering, column only used in local */
|
|
/* filter should be removed from column list. */
|
|
/***********************************************************************/
|
|
bool TDBMYSQL::MakeSelect(PGLOBAL g, bool mx)
|
|
{
|
|
//char *tk = "`";
|
|
char tk = '`';
|
|
int len = 0, rank = 0;
|
|
bool b = false;
|
|
PCOL colp;
|
|
//PDBUSER dup = PlgGetUser(g);
|
|
|
|
if (Query)
|
|
return false; // already done
|
|
|
|
if (Srcdef)
|
|
return MakeSrcdef(g);
|
|
|
|
// Allocate the string used to contain Query
|
|
Query = new(g) STRING(g, 1023, "SELECT ");
|
|
|
|
if (Columns) {
|
|
for (colp = Columns; colp; colp = colp->GetNext())
|
|
if (!colp->IsSpecial()) {
|
|
if (b)
|
|
Query->Append(", ");
|
|
else
|
|
b = true;
|
|
|
|
Query->Append(tk);
|
|
Query->Append(colp->GetName());
|
|
Query->Append(tk);
|
|
((PMYCOL)colp)->Rank = rank++;
|
|
} // endif colp
|
|
|
|
} else {
|
|
// ncol == 0 can occur for views or queries such as
|
|
// Query count(*) from... for which we will count the rows from
|
|
// Query '*' from...
|
|
// (the use of a char constant minimize the result storage)
|
|
if (Isview)
|
|
Query->Append('*');
|
|
else
|
|
Query->Append("'*'");
|
|
|
|
} // endif ncol
|
|
|
|
Query->Append(" FROM ");
|
|
Query->Append(tk);
|
|
Query->Append(TableName);
|
|
Query->Append(tk);
|
|
len = Query->GetLength();
|
|
|
|
if (To_CondFil) {
|
|
if (!mx) {
|
|
Query->Append(" WHERE ");
|
|
Query->Append(To_CondFil->Body);
|
|
len = Query->GetLength() + 1;
|
|
} else
|
|
len += (strlen(To_CondFil->Body) + 256);
|
|
|
|
} else
|
|
len += (mx ? 256 : 1);
|
|
|
|
if (Query->IsTruncated() || Query->Resize(len)) {
|
|
snprintf(g->Message, sizeof(g->Message), "MakeSelect: Out of memory");
|
|
return true;
|
|
} // endif Query
|
|
|
|
if (trace(33))
|
|
htrc("Query=%s\n", Query->GetStr());
|
|
|
|
return false;
|
|
} // end of MakeSelect
|
|
|
|
/***********************************************************************/
|
|
/* MakeInsert: make the Insert statement used with MySQL connection. */
|
|
/***********************************************************************/
|
|
bool TDBMYSQL::MakeInsert(PGLOBAL g)
|
|
{
|
|
const char *tk = "`";
|
|
uint len = 0;
|
|
bool oom, b = false;
|
|
PCOL colp;
|
|
|
|
if (Query)
|
|
return false; // already done
|
|
|
|
if (Prep) {
|
|
#if !defined(MYSQL_PREPARED_STATEMENTS)
|
|
snprintf(g->Message, sizeof(g->Message), "Prepared statements not used (not supported)");
|
|
PushWarning(g, this);
|
|
Prep = false;
|
|
#endif // !MYSQL_PREPARED_STATEMENTS
|
|
} // endif Prep
|
|
|
|
for (colp = Columns; colp; colp = colp->GetNext())
|
|
if (colp->IsSpecial()) {
|
|
snprintf(g->Message, sizeof(g->Message), MSG(NO_SPEC_COL));
|
|
return true;
|
|
} else {
|
|
len += (strlen(colp->GetName()) + 4);
|
|
|
|
// Parameter marker
|
|
if (!Prep) {
|
|
if (colp->GetResultType() == TYPE_DATE)
|
|
len += 20;
|
|
else
|
|
len += colp->GetLength();
|
|
|
|
} else
|
|
len += 2;
|
|
|
|
((PMYCOL)colp)->Rank = Nparm++;
|
|
} // endif colp
|
|
|
|
// Below 40 is enough to contain the fixed part of the query
|
|
len += (strlen(TableName) + 40);
|
|
Query = new(g) STRING(g, len);
|
|
|
|
Query->Set("INSERT ");
|
|
if (Delayed)
|
|
Query->Append("DELAYED ");
|
|
if (Ignored)
|
|
Query->Append("IGNORE ");
|
|
|
|
Query->Append("INTO ");
|
|
Query->Append(tk);
|
|
Query->Append(TableName);
|
|
Query->Append("` (");
|
|
|
|
for (colp = Columns; colp; colp = colp->GetNext()) {
|
|
if (b)
|
|
Query->Append(", ");
|
|
else
|
|
b = true;
|
|
|
|
Query->Append(tk);
|
|
Query->Append(colp->GetName());
|
|
Query->Append(tk);
|
|
} // endfor colp
|
|
|
|
Query->Append(") VALUES (");
|
|
|
|
#if defined(MYSQL_PREPARED_STATEMENTS)
|
|
if (Prep) {
|
|
for (int i = 0; i < Nparm; i++)
|
|
Query->Append("?,");
|
|
|
|
Query->RepLast(')');
|
|
Query->Trim();
|
|
} // endif Prep
|
|
#endif // MYSQL_PREPARED_STATEMENTS
|
|
|
|
if ((oom = Query->IsTruncated()))
|
|
snprintf(g->Message, sizeof(g->Message), "MakeInsert: Out of memory");
|
|
|
|
return oom;
|
|
} // end of MakeInsert
|
|
|
|
/***********************************************************************/
|
|
/* MakeCommand: make the Update or Delete statement to send to the */
|
|
/* MySQL server. Limited to remote values and filtering. */
|
|
/***********************************************************************/
|
|
bool TDBMYSQL::MakeCommand(PGLOBAL g)
|
|
{
|
|
Query = new(g) STRING(g, strlen(Qrystr) + 64);
|
|
|
|
if (Quoted > 0 || stricmp(Name, TableName)) {
|
|
char *p, *qrystr, name[68];
|
|
bool qtd = Quoted > 0;
|
|
|
|
|
|
// Make a lower case copy of the originale query
|
|
qrystr = (char*)PlugSubAlloc(g, NULL, strlen(Qrystr) + 5);
|
|
strlwr(strcpy(qrystr, Qrystr));
|
|
|
|
// Check whether the table name is equal to a keyword
|
|
// If so, it must be quoted in the original query
|
|
strlwr(strcat(strcat(strcpy(name, "`"), Name), "`"));
|
|
|
|
if (!strstr("`update`delete`low_priority`ignore`quick`from`", name))
|
|
strlwr(strcpy(name, Name)); // Not a keyword
|
|
|
|
if ((p = strstr(qrystr, name))) {
|
|
Query->Set(Qrystr, (uint)(p - qrystr));
|
|
|
|
if (qtd && *(p-1) == ' ') {
|
|
Query->Append('`');
|
|
Query->Append(TableName);
|
|
Query->Append('`');
|
|
} else
|
|
Query->Append(TableName);
|
|
|
|
Query->Append(Qrystr + (p - qrystr) + strlen(name));
|
|
|
|
if (Query->IsTruncated()) {
|
|
snprintf(g->Message, sizeof(g->Message), "MakeCommand: Out of memory");
|
|
return true;
|
|
} else
|
|
strlwr(strcpy(qrystr, Query->GetStr()));
|
|
|
|
} else {
|
|
snprintf(g->Message, sizeof(g->Message), "Cannot use this %s command",
|
|
(Mode == MODE_UPDATE) ? "UPDATE" : "DELETE");
|
|
return true;
|
|
} // endif p
|
|
|
|
} else
|
|
(void)Query->Set(Qrystr);
|
|
|
|
return false;
|
|
} // end of MakeCommand
|
|
|
|
#if 0
|
|
/***********************************************************************/
|
|
/* MakeUpdate: make the Update statement use with MySQL connection. */
|
|
/* Limited to remote values and filtering. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::MakeUpdate(PGLOBAL g)
|
|
{
|
|
char *qc, cmd[8], tab[96], end[1024];
|
|
|
|
Query = (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 = "`";
|
|
else if (sscanf(Qrystr, "%s %s%1023c", cmd, tab, end) > 2
|
|
&& !stricmp(tab, Name))
|
|
qc = (Quoted) ? "`" : "";
|
|
else {
|
|
snprintf(g->Message, sizeof(g->Message), "Cannot use this UPDATE command");
|
|
return RC_FX;
|
|
} // endif sscanf
|
|
|
|
assert(!stricmp(cmd, "update"));
|
|
strcat(strcat(strcat(strcpy(Query, "UPDATE "), qc), TableName), qc);
|
|
strcat(Query, end);
|
|
return RC_OK;
|
|
} // end of MakeUpdate
|
|
|
|
/***********************************************************************/
|
|
/* MakeDelete: make the Delete statement used with MySQL connection. */
|
|
/* Limited to remote filtering. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::MakeDelete(PGLOBAL g)
|
|
{
|
|
char *qc, cmd[8], from[8], tab[96], end[512];
|
|
|
|
Query = (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 = "`";
|
|
else if (sscanf(Qrystr, "%s %s %s%511c", cmd, from, tab, end) > 2)
|
|
qc = (Quoted) ? "`" : "";
|
|
else {
|
|
snprintf(g->Message, sizeof(g->Message), "Cannot use this DELETE command");
|
|
return RC_FX;
|
|
} // endif sscanf
|
|
|
|
assert(!stricmp(cmd, "delete") && !stricmp(from, "from"));
|
|
strcat(strcat(strcat(strcpy(Query, "DELETE FROM "), qc), TableName), qc);
|
|
|
|
if (*end)
|
|
strcat(Query, end);
|
|
|
|
return RC_OK;
|
|
} // end of MakeDelete
|
|
#endif // 0
|
|
|
|
/***********************************************************************/
|
|
/* MYSQL Cardinality: returns the number of rows in the table. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::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 query[96];
|
|
MYSQLC myc;
|
|
|
|
if (myc.Open(g, Host, Schema, User, Pwd, Port, csname))
|
|
return -1;
|
|
|
|
strcpy(query, "SELECT COUNT(*) FROM ");
|
|
|
|
if (Quoted > 0)
|
|
strcat(strcat(strcat(query, "`"), TableName), "`");
|
|
else
|
|
strcat(query, TableName);
|
|
|
|
Cardinal = myc.GetTableSize(g, query);
|
|
myc.Close();
|
|
} else
|
|
Cardinal = 10; // To make MySQL happy
|
|
|
|
return Cardinal;
|
|
} // end of Cardinality
|
|
|
|
#if 0
|
|
/***********************************************************************/
|
|
/* MYSQL GetMaxSize: returns the maximum number of rows in the table. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::GetMaxSize(PGLOBAL g)
|
|
{
|
|
if (MaxSize < 0) {
|
|
if (Mode == MODE_DELETE)
|
|
// Return 0 in mode DELETE in case of delete all.
|
|
MaxSize = 0;
|
|
else if (!Cardinality(NULL))
|
|
MaxSize = 10; // To make MySQL happy
|
|
else if ((MaxSize = Cardinality(g)) < 0)
|
|
MaxSize = 12; // So we can see an error occurred
|
|
|
|
} // endif MaxSize
|
|
|
|
return MaxSize;
|
|
} // end of GetMaxSize
|
|
#endif // 0
|
|
|
|
/***********************************************************************/
|
|
/* This a fake routine as ROWID does not exist in MySQL. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::RowNumber(PGLOBAL, bool)
|
|
{
|
|
return N + 1;
|
|
} // end of RowNumber
|
|
|
|
/***********************************************************************/
|
|
/* Return 0 in mode UPDATE to tell that the update is done. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::GetProgMax(PGLOBAL g)
|
|
{
|
|
return (Mode == MODE_UPDATE) ? 0 : GetMaxSize(g);
|
|
} // end of GetProgMax
|
|
|
|
/***********************************************************************/
|
|
/* MySQL Bind Parameter function. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::BindColumns(PGLOBAL g __attribute__((unused)))
|
|
{
|
|
#if defined(MYSQL_PREPARED_STATEMENTS)
|
|
if (Prep) {
|
|
Bind = (MYSQL_BIND*)PlugSubAlloc(g, NULL, Nparm * sizeof(MYSQL_BIND));
|
|
|
|
for (PMYCOL colp = (PMYCOL)Columns; colp; colp = (PMYCOL)colp->Next)
|
|
colp->InitBind(g);
|
|
|
|
return Myc.BindParams(g, Bind);
|
|
} // endif prep
|
|
#endif // MYSQL_PREPARED_STATEMENTS
|
|
|
|
return RC_OK;
|
|
} // end of BindColumns
|
|
|
|
/***********************************************************************/
|
|
/* MySQL Access Method opening routine. */
|
|
/***********************************************************************/
|
|
bool TDBMYSQL::OpenDB(PGLOBAL g)
|
|
{
|
|
if (Use == USE_OPEN) {
|
|
/*******************************************************************/
|
|
/* Table already open, just replace it at its beginning. */
|
|
/*******************************************************************/
|
|
if (Myc.Rewind(g, (Mode == MODE_READX) ? Query->GetStr() : NULL) != RC_OK)
|
|
return true;
|
|
|
|
N = -1;
|
|
return false;
|
|
} // endif use
|
|
|
|
/*********************************************************************/
|
|
/* Open a MySQL 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 server */
|
|
/* and if so to allocate just a new result set. But this only for */
|
|
/* servers allowing concurency in getting results ??? */
|
|
/*********************************************************************/
|
|
if (!Myc.Connected()) {
|
|
if (Myc.Open(g, Host, Schema, User, Pwd, Port, csname))
|
|
return true;
|
|
|
|
} // endif Connected
|
|
|
|
/*********************************************************************/
|
|
/* Take care of DATE columns. */
|
|
/*********************************************************************/
|
|
for (PMYCOL colp = (PMYCOL)Columns; colp; colp = (PMYCOL)colp->Next)
|
|
if (colp->Buf_Type == TYPE_DATE)
|
|
// Format must match DATETIME MySQL type
|
|
((DTVAL*)colp->GetValue())->SetFormat(g, "YYYY-MM-DD hh:mm:ss", 19);
|
|
|
|
/*********************************************************************/
|
|
/* Allocate whatever is used for getting results. */
|
|
/*********************************************************************/
|
|
if (Mode == MODE_READ || Mode == MODE_READX) {
|
|
MakeSelect(g, Mode == MODE_READX);
|
|
if (Mode == MODE_READ && !Query)
|
|
{
|
|
Myc.Close();
|
|
return true;
|
|
}
|
|
m_Rc = (Mode == MODE_READ)
|
|
? Myc.ExecSQL(g, Query->GetStr()) : RC_OK;
|
|
|
|
#if 0
|
|
if (!Myc.m_Res || !Myc.m_Fields) {
|
|
snprintf(g->Message, sizeof(g->Message), "%s result", (Myc.m_Res) ? "Void" : "No");
|
|
Myc.Close();
|
|
return true;
|
|
} // endif m_Res
|
|
#endif // 0
|
|
|
|
if (!m_Rc && Srcdef)
|
|
if (SetColumnRanks(g))
|
|
return true;
|
|
|
|
} else if (Mode == MODE_INSERT) {
|
|
if (Srcdef) {
|
|
snprintf(g->Message, sizeof(g->Message), "No insert into anonym views");
|
|
Myc.Close();
|
|
return true;
|
|
} // endif Srcdef
|
|
|
|
if (!MakeInsert(g)) {
|
|
#if defined(MYSQL_PREPARED_STATEMENTS)
|
|
int n = (Prep)
|
|
? Myc.PrepareSQL(g, Query->GetCharValue()) : Nparm;
|
|
|
|
if (Nparm != n) {
|
|
if (n >= 0) // Other errors return negative values
|
|
snprintf(g->Message, sizeof(g->Message), MSG(BAD_PARM_COUNT));
|
|
|
|
} else
|
|
#endif // MYSQL_PREPARED_STATEMENTS
|
|
m_Rc = BindColumns(g);
|
|
|
|
} // endif MakeInsert
|
|
|
|
} else
|
|
// m_Rc = (Mode == MODE_DELETE) ? MakeDelete(g) : MakeUpdate(g);
|
|
m_Rc = (MakeCommand(g)) ? RC_FX : RC_OK;
|
|
|
|
if (m_Rc == RC_FX) {
|
|
Myc.Close();
|
|
return true;
|
|
} // endif rc
|
|
|
|
Use = USE_OPEN;
|
|
return false;
|
|
} // end of OpenDB
|
|
|
|
/***********************************************************************/
|
|
/* Set the rank of columns in the result set. */
|
|
/***********************************************************************/
|
|
bool TDBMYSQL::SetColumnRanks(PGLOBAL g)
|
|
{
|
|
for (PCOL colp = Columns; colp; colp = colp->GetNext())
|
|
if (((PMYCOL)colp)->FindRank(g))
|
|
return true;
|
|
|
|
return false;
|
|
} // end of SetColumnRanks
|
|
|
|
/***********************************************************************/
|
|
/* Called by Parent table to make the columns of a View. */
|
|
/***********************************************************************/
|
|
PCOL TDBMYSQL::MakeFieldColumn(PGLOBAL g, char *name)
|
|
{
|
|
int n;
|
|
MYSQL_FIELD *fld;
|
|
PCOL cp, colp = NULL;
|
|
|
|
for (n = 0; n < Myc.m_Fields; n++) {
|
|
fld = &Myc.m_Res->fields[n];
|
|
|
|
if (!stricmp(name, fld->name)) {
|
|
colp = new(g) MYSQLCOL(fld, this, n);
|
|
|
|
if (colp->InitValue(g))
|
|
return NULL;
|
|
|
|
if (!Columns)
|
|
Columns = colp;
|
|
else for (cp = Columns; cp; cp = cp->GetNext())
|
|
if (!cp->GetNext()) {
|
|
cp->SetNext(colp);
|
|
break;
|
|
} // endif Next
|
|
|
|
break;
|
|
} // endif name
|
|
|
|
} // endfor n
|
|
|
|
if (!colp)
|
|
snprintf(g->Message, sizeof(g->Message), "Column %s is not in view", name);
|
|
|
|
return colp;
|
|
} // end of MakeFieldColumn
|
|
|
|
/***********************************************************************/
|
|
/* Called by Pivot tables to find default column names in a View */
|
|
/* as the name of last field not equal to the passed name. */
|
|
/***********************************************************************/
|
|
char *TDBMYSQL::FindFieldColumn(char *name)
|
|
{
|
|
int n;
|
|
MYSQL_FIELD *fld;
|
|
char *cp = NULL;
|
|
|
|
for (n = Myc.m_Fields - 1; n >= 0; n--) {
|
|
fld = &Myc.m_Res->fields[n];
|
|
|
|
if (!name || stricmp(name, fld->name)) {
|
|
cp = fld->name;
|
|
break;
|
|
} // endif name
|
|
|
|
} // endfor n
|
|
|
|
return cp;
|
|
} // end of FindFieldColumn
|
|
|
|
/***********************************************************************/
|
|
/* Send an UPDATE or DELETE command to the remote server. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::SendCommand(PGLOBAL g)
|
|
{
|
|
int w;
|
|
|
|
if (Myc.ExecSQLcmd(g, Query->GetStr(), &w) == RC_NF) {
|
|
AftRows = Myc.m_Afrw;
|
|
snprintf(g->Message, sizeof(g->Message), "%s: %d affected rows", TableName, AftRows);
|
|
PushWarning(g, this, 0); // 0 means a Note
|
|
|
|
if (trace(1))
|
|
htrc("%s\n", g->Message);
|
|
|
|
if (w && Myc.ExecSQL(g, "SHOW WARNINGS") == RC_OK) {
|
|
// We got warnings from the remote server
|
|
while (Myc.Fetch(g, -1) == RC_OK) {
|
|
snprintf(g->Message, sizeof(g->Message), "%s: (%s) %s", TableName,
|
|
Myc.GetCharField(1), Myc.GetCharField(2));
|
|
PushWarning(g, this);
|
|
} // endwhile Fetch
|
|
|
|
Myc.FreeResult();
|
|
} // endif w
|
|
|
|
return RC_EF; // Nothing else to do
|
|
} else
|
|
return RC_FX; // Error
|
|
|
|
} // end of SendCommand
|
|
|
|
/***********************************************************************/
|
|
/* Data Base indexed read routine for MYSQL access method. */
|
|
/***********************************************************************/
|
|
bool TDBMYSQL::ReadKey(PGLOBAL g, OPVAL op, const key_range *kr)
|
|
{
|
|
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
|
|
m_Rc = Myc.ExecSQL(g, Query->GetStr());
|
|
Mode = MODE_READ;
|
|
return (m_Rc == RC_FX) ? true : false;
|
|
} // endif key
|
|
|
|
return false;
|
|
} else {
|
|
if (Myc.m_Res)
|
|
Myc.FreeResult();
|
|
|
|
if (hc->MakeKeyWhere(g, Query, op, '`', 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)) {
|
|
snprintf(g->Message, sizeof(g->Message), "Readkey: Out of memory");
|
|
return true;
|
|
} // endif Append
|
|
|
|
} // endif To_Condfil
|
|
|
|
Mode = MODE_READ;
|
|
} // endif's op
|
|
|
|
if (trace(33))
|
|
htrc("MYSQL ReadKey: Query=%s\n", Query->GetStr());
|
|
|
|
m_Rc = Myc.ExecSQL(g, Query->GetStr());
|
|
Query->Truncate(oldlen);
|
|
return (m_Rc == RC_FX) ? true : false;
|
|
} // end of ReadKey
|
|
|
|
/***********************************************************************/
|
|
/* Data Base read routine for MYSQL access method. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::ReadDB(PGLOBAL g)
|
|
{
|
|
int rc;
|
|
|
|
if (trace(2))
|
|
htrc("MySQL ReadDB: R%d Mode=%d\n", GetTdb_No(), Mode);
|
|
|
|
if (Mode == MODE_UPDATE || Mode == MODE_DELETE)
|
|
return SendCommand(g);
|
|
|
|
/*********************************************************************/
|
|
/* Now start the reading process. */
|
|
/* Here is the place to fetch the line. */
|
|
/*********************************************************************/
|
|
N++;
|
|
Fetched = ((rc = Myc.Fetch(g, -1)) == RC_OK);
|
|
|
|
if (trace(2))
|
|
htrc(" Read: rc=%d\n", rc);
|
|
|
|
return rc;
|
|
} // end of ReadDB
|
|
|
|
/***********************************************************************/
|
|
/* WriteDB: Data Base write routine for MYSQL access methods. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::WriteDB(PGLOBAL g)
|
|
{
|
|
#if defined(MYSQL_PREPARED_STATEMENTS)
|
|
if (Prep)
|
|
return Myc.ExecStmt(g);
|
|
#endif // MYSQL_PREPARED_STATEMENTS
|
|
|
|
// Statement was not prepared, we must construct and execute
|
|
// an insert query for each line to insert
|
|
int rc;
|
|
uint len = Query->GetLength();
|
|
char buf[64];
|
|
|
|
// Make the Insert command value list
|
|
for (PCOL colp = Columns; colp; colp = colp->GetNext()) {
|
|
if (!colp->GetValue()->IsNull()) {
|
|
if (colp->GetResultType() == TYPE_STRING ||
|
|
colp->GetResultType() == TYPE_DATE)
|
|
Query->Append_quoted(colp->GetValue()->GetCharString(buf));
|
|
else
|
|
Query->Append(colp->GetValue()->GetCharString(buf));
|
|
|
|
} else
|
|
Query->Append("NULL");
|
|
|
|
Query->Append(',');
|
|
} // endfor colp
|
|
|
|
if (unlikely(Query->IsTruncated())) {
|
|
snprintf(g->Message, sizeof(g->Message), "WriteDB: Out of memory");
|
|
rc = RC_FX;
|
|
} else {
|
|
Query->RepLast(')');
|
|
Myc.m_Rows = -1; // To execute the query
|
|
rc = Myc.ExecSQL(g, Query->GetStr());
|
|
Query->Truncate(len); // Restore query
|
|
} // endif Query
|
|
|
|
return (rc == RC_NF) ? RC_OK : rc; // RC_NF is Ok
|
|
} // end of WriteDB
|
|
|
|
/***********************************************************************/
|
|
/* Data Base delete all routine for MYSQL access methods. */
|
|
/***********************************************************************/
|
|
int TDBMYSQL::DeleteDB(PGLOBAL g, int irc)
|
|
{
|
|
if (irc == RC_FX)
|
|
// Send the DELETE (all) command to the remote table
|
|
return (SendCommand(g) == RC_FX) ? RC_FX : RC_OK;
|
|
else
|
|
return RC_OK; // Ignore
|
|
|
|
} // end of DeleteDB
|
|
|
|
/***********************************************************************/
|
|
/* Data Base close routine for MySQL access method. */
|
|
/***********************************************************************/
|
|
void TDBMYSQL::CloseDB(PGLOBAL g)
|
|
{
|
|
if (Myc.Connected()) {
|
|
|
|
Myc.Close();
|
|
} // endif Myc
|
|
|
|
if (trace(1))
|
|
htrc("MySQL CloseDB: closing %s rc=%d\n", Name, m_Rc);
|
|
|
|
} // end of CloseDB
|
|
|
|
// ------------------------ MYSQLCOL functions --------------------------
|
|
|
|
/***********************************************************************/
|
|
/* MYSQLCOL public constructor. */
|
|
/***********************************************************************/
|
|
MYSQLCOL::MYSQLCOL(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 MySQL access method information for column.
|
|
Precision = Long = cdp->GetLong();
|
|
Bind = NULL;
|
|
To_Val = NULL;
|
|
Slen = 0;
|
|
Rank = -1; // Not known yet
|
|
|
|
if (trace(1))
|
|
htrc(" making new %sCOL C%d %s at %p\n", am, Index, Name, this);
|
|
|
|
} // end of MYSQLCOL constructor
|
|
|
|
/***********************************************************************/
|
|
/* MYSQLCOL public constructor. */
|
|
/***********************************************************************/
|
|
MYSQLCOL::MYSQLCOL(MYSQL_FIELD *fld, PTDB tdbp, int i, PCSZ am)
|
|
: COLBLK(NULL, tdbp, i)
|
|
{
|
|
//const char *chset = get_charset_name(fld->charsetnr);
|
|
//char v = (!strcmp(chset, "binary")) ? 'B' : 0;
|
|
char v = 0;
|
|
|
|
Name = fld->name;
|
|
Opt = 0;
|
|
Precision = Long = fld->length;
|
|
Buf_Type = MYSQLtoPLG(fld->type, &v);
|
|
strcpy(Format.Type, GetFormatType(Buf_Type));
|
|
Format.Length = Long;
|
|
Format.Prec = fld->decimals;
|
|
ColUse = U_P;
|
|
Nullable = !IS_NOT_NULL(fld->flags);
|
|
|
|
// Set additional MySQL access method information for column.
|
|
Bind = NULL;
|
|
To_Val = NULL;
|
|
Slen = 0;
|
|
Rank = i;
|
|
|
|
if (trace(1))
|
|
htrc(" making new %sCOL C%d %s at %p\n", am, Index, Name, this);
|
|
|
|
} // end of MYSQLCOL constructor
|
|
|
|
/***********************************************************************/
|
|
/* MYSQLCOL constructor used for copying columns. */
|
|
/* tdbp is the pointer to the new table descriptor. */
|
|
/***********************************************************************/
|
|
MYSQLCOL::MYSQLCOL(MYSQLCOL *col1, PTDB tdbp) : COLBLK(col1, tdbp)
|
|
{
|
|
Long = col1->Long;
|
|
Bind = NULL;
|
|
To_Val = NULL;
|
|
Slen = col1->Slen;
|
|
Rank = col1->Rank;
|
|
} // end of MYSQLCOL copy constructor
|
|
|
|
/***********************************************************************/
|
|
/* FindRank: Find the rank of this column in the result set. */
|
|
/***********************************************************************/
|
|
bool MYSQLCOL::FindRank(PGLOBAL g)
|
|
{
|
|
int n;
|
|
MYSQLC myc = ((PTDBMY)To_Tdb)->Myc;
|
|
|
|
for (n = 0; n < myc.m_Fields; n++)
|
|
if (!stricmp(Name, myc.m_Res->fields[n].name)) {
|
|
Rank = n;
|
|
return false;
|
|
} // endif Name
|
|
|
|
snprintf(g->Message, sizeof(g->Message), "Column %s not in result set", Name);
|
|
return true;
|
|
} // end of FindRank
|
|
|
|
/***********************************************************************/
|
|
/* SetBuffer: prepare a column block for write operation. */
|
|
/***********************************************************************/
|
|
bool MYSQLCOL::SetBuffer(PGLOBAL g, PVAL value, bool ok, bool check)
|
|
{
|
|
if (!(To_Val = value)) {
|
|
snprintf(g->Message, sizeof(g->Message), MSG(VALUE_ERROR), Name);
|
|
return true;
|
|
} else if (Buf_Type == value->GetType()) {
|
|
// Values are of the (good) column type
|
|
if (Buf_Type == TYPE_DATE) {
|
|
// If any of the date values is formatted
|
|
// output format must be set for the receiving table
|
|
if (GetDomain() || ((DTVAL *)value)->IsFormatted())
|
|
goto newval; // This will make a new value;
|
|
|
|
} else if (Buf_Type == TYPE_DOUBLE)
|
|
// Float values must be written with the correct (column) precision
|
|
// Note: maybe this should be forced by ShowValue instead of this ?
|
|
value->SetPrec(GetScale());
|
|
|
|
Value = value; // Directly access the external value
|
|
} else {
|
|
// Values are not of the (good) column type
|
|
if (check) {
|
|
snprintf(g->Message, sizeof(g->Message), MSG(TYPE_VALUE_ERR), Name,
|
|
GetTypeName(Buf_Type), GetTypeName(value->GetType()));
|
|
return true;
|
|
} // endif check
|
|
|
|
newval:
|
|
if (InitValue(g)) // Allocate the matching value block
|
|
return true;
|
|
|
|
} // endif's Value, Buf_Type
|
|
|
|
// Because Colblk's have been made from a copy of the original TDB in
|
|
// case of Update, we must reset them to point to the original one.
|
|
if (To_Tdb->GetOrig())
|
|
To_Tdb = (PTDB)To_Tdb->GetOrig();
|
|
|
|
// Set the Column
|
|
Status = (ok) ? BUF_EMPTY : BUF_NO;
|
|
return false;
|
|
} // end of SetBuffer
|
|
|
|
/***********************************************************************/
|
|
/* InitBind: Initialize the bind structure according to type. */
|
|
/***********************************************************************/
|
|
void MYSQLCOL::InitBind(PGLOBAL g)
|
|
{
|
|
PTDBMY tdbp = (PTDBMY)To_Tdb;
|
|
|
|
assert(tdbp->Bind && Rank < tdbp->Nparm);
|
|
|
|
Bind = &tdbp->Bind[Rank];
|
|
memset(Bind, 0, sizeof(MYSQL_BIND));
|
|
|
|
if (Buf_Type == TYPE_DATE) {
|
|
Bind->buffer_type = PLGtoMYSQL(TYPE_STRING, false);
|
|
Bind->buffer = (char *)PlugSubAlloc(g,NULL, 20);
|
|
Bind->buffer_length = 20;
|
|
Bind->length = &Slen;
|
|
} else {
|
|
Bind->buffer_type = PLGtoMYSQL(Buf_Type, false);
|
|
Bind->buffer = (char *)Value->GetTo_Val();
|
|
Bind->buffer_length = Value->GetClen();
|
|
Bind->length = (IsTypeChar(Buf_Type)) ? &Slen : NULL;
|
|
} // endif Buf_Type
|
|
|
|
} // end of InitBind
|
|
|
|
/***********************************************************************/
|
|
/* ReadColumn: */
|
|
/***********************************************************************/
|
|
void MYSQLCOL::ReadColumn(PGLOBAL g)
|
|
{
|
|
char *p, *buf, tim[20];
|
|
int rc;
|
|
PTDBMY tdbp = (PTDBMY)To_Tdb;
|
|
|
|
/*********************************************************************/
|
|
/* If physical fetching of the line was deferred, do it now. */
|
|
/*********************************************************************/
|
|
if (!tdbp->Fetched)
|
|
{
|
|
if ((rc = tdbp->Myc.Fetch(g, tdbp->N)) != RC_OK) {
|
|
if (rc == RC_EF)
|
|
snprintf(g->Message, sizeof(g->Message), MSG(INV_DEF_READ), rc);
|
|
|
|
throw 11;
|
|
} else
|
|
tdbp->Fetched = true;
|
|
}
|
|
if ((buf = ((PTDBMY)To_Tdb)->Myc.GetCharField(Rank))) {
|
|
if (trace(2))
|
|
htrc("MySQL ReadColumn: name=%s buf=%s\n", Name, buf);
|
|
|
|
// TODO: have a true way to differenciate temporal values
|
|
if (Buf_Type == TYPE_DATE && strlen(buf) == 8)
|
|
// This is a TIME value
|
|
p = strcat(strcpy(tim, "1970-01-01 "), buf);
|
|
else
|
|
p = buf;
|
|
|
|
if (Value->SetValue_char(p, strlen(p))) {
|
|
snprintf(g->Message, sizeof(g->Message), "Out of range value for column %s at row %d",
|
|
Name, tdbp->RowNumber(g));
|
|
PushWarning(g, tdbp);
|
|
} // endif SetValue_char
|
|
|
|
} else {
|
|
if (Nullable)
|
|
Value->SetNull(true);
|
|
|
|
Value->Reset(); // Null value
|
|
} // endif buf
|
|
|
|
} // end of ReadColumn
|
|
|
|
/***********************************************************************/
|
|
/* WriteColumn: make sure the bind buffer is updated. */
|
|
/***********************************************************************/
|
|
void MYSQLCOL::WriteColumn(PGLOBAL)
|
|
{
|
|
/*********************************************************************/
|
|
/* Do convert the column value if necessary. */
|
|
/*********************************************************************/
|
|
if (Value != To_Val)
|
|
Value->SetValue_pval(To_Val, false); // Convert the inserted value
|
|
|
|
#if defined(MYSQL_PREPARED_STATEMENTS)
|
|
if (((PTDBMY)To_Tdb)->Prep) {
|
|
if (Buf_Type == TYPE_DATE) {
|
|
Value->ShowValue((char *)Bind->buffer, (int)Bind->buffer_length);
|
|
Slen = strlen((char *)Bind->buffer);
|
|
} else if (IsTypeChar(Buf_Type))
|
|
Slen = strlen(Value->GetCharValue());
|
|
|
|
} // endif Prep
|
|
#endif // MYSQL_PREPARED_STATEMENTS
|
|
|
|
} // end of WriteColumn
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
/***********************************************************************/
|
|
/* Implementation of the TDBMYEXC class. */
|
|
/***********************************************************************/
|
|
TDBMYEXC::TDBMYEXC(PMYDEF tdp) : TDBMYSQL(tdp)
|
|
{
|
|
Cmdlist = NULL;
|
|
Cmdcol = NULL;
|
|
Shw = false;
|
|
Havew = false;
|
|
Isw = false;
|
|
Warnings = 0;
|
|
Mxr = tdp->Maxerr;
|
|
Nerr = 0;
|
|
} // end of TDBMYEXC constructor
|
|
|
|
TDBMYEXC::TDBMYEXC(PTDBMYX tdbp) : TDBMYSQL(tdbp)
|
|
{
|
|
Cmdlist = tdbp->Cmdlist;
|
|
Cmdcol = tdbp->Cmdcol;
|
|
Shw = tdbp->Shw;
|
|
Havew = tdbp->Havew;
|
|
Isw = tdbp->Isw;
|
|
Mxr = tdbp->Mxr;
|
|
Nerr = tdbp->Nerr;
|
|
} // end of TDBMYEXC copy constructor
|
|
|
|
// Is this really useful ???
|
|
PTDB TDBMYEXC::Clone(PTABS t)
|
|
{
|
|
PTDB tp;
|
|
PCOL cp1, cp2;
|
|
PGLOBAL g = t->G;
|
|
|
|
tp = new(g) TDBMYEXC(this);
|
|
|
|
for (cp1 = Columns; cp1; cp1 = cp1->GetNext()) {
|
|
cp2 = new(g) MYXCOL((PMYXCOL)cp1, tp);
|
|
|
|
NewPointer(t, cp1, cp2);
|
|
} // endfor cp1
|
|
|
|
return tp;
|
|
} // end of Clone
|
|
|
|
/***********************************************************************/
|
|
/* Allocate MYSQL column description block. */
|
|
/***********************************************************************/
|
|
PCOL TDBMYEXC::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
|
|
{
|
|
PMYXCOL colp = new(g) MYXCOL(cdp, this, cprec, n);
|
|
|
|
if (!colp->Flag)
|
|
Cmdcol = colp->GetName();
|
|
|
|
return colp;
|
|
} // end of MakeCol
|
|
|
|
/***********************************************************************/
|
|
/* MakeCMD: make the SQL statement to send to MYSQL connection. */
|
|
/***********************************************************************/
|
|
PCMD TDBMYEXC::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
|
|
snprintf(g->Message, sizeof(g->Message), "Invalid command specification filter");
|
|
|
|
} else
|
|
snprintf(g->Message, sizeof(g->Message), "No command column in select list");
|
|
|
|
} else if (!Srcdef)
|
|
snprintf(g->Message, sizeof(g->Message), "No Srcdef default command");
|
|
else
|
|
xcmd = new(g) CMD(g, Srcdef);
|
|
|
|
return xcmd;
|
|
} // end of MakeCMD
|
|
|
|
/***********************************************************************/
|
|
/* EXC GetMaxSize: returns the maximum number of rows in the table. */
|
|
/***********************************************************************/
|
|
int TDBMYEXC::GetMaxSize(PGLOBAL)
|
|
{
|
|
if (MaxSize < 0) {
|
|
MaxSize = 10; // a guess
|
|
} // endif MaxSize
|
|
|
|
return MaxSize;
|
|
} // end of GetMaxSize
|
|
|
|
/***********************************************************************/
|
|
/* MySQL Exec Access Method opening routine. */
|
|
/***********************************************************************/
|
|
bool TDBMYEXC::OpenDB(PGLOBAL g)
|
|
{
|
|
if (Use == USE_OPEN) {
|
|
snprintf(g->Message, sizeof(g->Message), "Multiple execution is not allowed");
|
|
return true;
|
|
} // endif use
|
|
|
|
/*********************************************************************/
|
|
/* Open a MySQL 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 server */
|
|
/* and if so to allocate just a new result set. But this only for */
|
|
/* servers allowing concurency in getting results ??? */
|
|
/*********************************************************************/
|
|
if (!Myc.Connected())
|
|
if (Myc.Open(g, Host, Schema, User, Pwd, Port))
|
|
return true;
|
|
|
|
Use = USE_OPEN; // Do it now in case we are recursively called
|
|
|
|
if (Mode != MODE_READ && Mode != MODE_READX) {
|
|
snprintf(g->Message, sizeof(g->Message), "No INSERT/DELETE/UPDATE of MYSQL EXEC tables");
|
|
return true;
|
|
} // endif Mode
|
|
|
|
/*********************************************************************/
|
|
/* Get the command to execute. */
|
|
/*********************************************************************/
|
|
if (!(Cmdlist = MakeCMD(g))) {
|
|
// Next lines commented out because of CHECK TABLE
|
|
//Myc.Close();
|
|
//return true;
|
|
} // endif Cmdlist
|
|
|
|
return false;
|
|
} // end of OpenDB
|
|
|
|
/***********************************************************************/
|
|
/* Data Base read routine for MYSQL access method. */
|
|
/***********************************************************************/
|
|
int TDBMYEXC::ReadDB(PGLOBAL g)
|
|
{
|
|
if (Havew) {
|
|
// Process result set from SHOW WARNINGS
|
|
if (Myc.Fetch(g, -1) != RC_OK) {
|
|
Myc.FreeResult();
|
|
Havew = Isw = false;
|
|
} else {
|
|
N++;
|
|
Isw = true;
|
|
return RC_OK;
|
|
} // endif Fetch
|
|
|
|
} // endif m_Res
|
|
|
|
if (Cmdlist) {
|
|
// Process query to send
|
|
int rc;
|
|
|
|
do {
|
|
if (Query)
|
|
Query->Set(Cmdlist->Cmd);
|
|
else
|
|
Query = new(g) STRING(g, 0, Cmdlist->Cmd);
|
|
|
|
switch (rc = Myc.ExecSQLcmd(g, Query->GetStr(), &Warnings)) {
|
|
case RC_NF:
|
|
AftRows = Myc.m_Afrw;
|
|
snprintf(g->Message, sizeof(g->Message), "Affected rows");
|
|
break;
|
|
case RC_OK:
|
|
AftRows = Myc.m_Fields;
|
|
snprintf(g->Message, sizeof(g->Message), "Result set columns");
|
|
break;
|
|
case RC_FX:
|
|
AftRows = Myc.m_Afrw;
|
|
Nerr++;
|
|
break;
|
|
case RC_INFO:
|
|
Shw = true;
|
|
} // endswitch rc
|
|
|
|
Cmdlist = (Nerr > Mxr) ? NULL : Cmdlist->Next;
|
|
} while (rc == RC_INFO);
|
|
|
|
if (Shw && Warnings)
|
|
Havew = (Myc.ExecSQL(g, "SHOW WARNINGS") == RC_OK);
|
|
|
|
++N;
|
|
return RC_OK;
|
|
} else {
|
|
PushWarning(g, this, 1);
|
|
return RC_EF;
|
|
} // endif Cmdlist
|
|
|
|
} // end of ReadDB
|
|
|
|
/***********************************************************************/
|
|
/* WriteDB: Data Base write routine for Exec MYSQL access methods. */
|
|
/***********************************************************************/
|
|
int TDBMYEXC::WriteDB(PGLOBAL g)
|
|
{
|
|
snprintf(g->Message, sizeof(g->Message), "EXEC MYSQL tables are read only");
|
|
return RC_FX;
|
|
} // end of WriteDB
|
|
|
|
// ------------------------- MYXCOL functions ---------------------------
|
|
|
|
/***********************************************************************/
|
|
/* MYXCOL public constructor. */
|
|
/***********************************************************************/
|
|
MYXCOL::MYXCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PCSZ am)
|
|
: MYSQLCOL(cdp, tdbp, cprec, i, am)
|
|
{
|
|
// Set additional EXEC MYSQL access method information for column.
|
|
Flag = cdp->GetOffset();
|
|
} // end of MYSQLCOL constructor
|
|
|
|
/***********************************************************************/
|
|
/* MYSQLCOL public constructor. */
|
|
/***********************************************************************/
|
|
MYXCOL::MYXCOL(MYSQL_FIELD *fld, PTDB tdbp, int i, PCSZ am)
|
|
: MYSQLCOL(fld, tdbp, i, am)
|
|
{
|
|
if (trace(1))
|
|
htrc(" making new %sCOL C%d %s at %p\n", am, Index, Name, this);
|
|
|
|
} // end of MYSQLCOL constructor
|
|
|
|
/***********************************************************************/
|
|
/* MYXCOL constructor used for copying columns. */
|
|
/* tdbp is the pointer to the new table descriptor. */
|
|
/***********************************************************************/
|
|
MYXCOL::MYXCOL(MYXCOL *col1, PTDB tdbp) : MYSQLCOL(col1, tdbp)
|
|
{
|
|
Flag = col1->Flag;
|
|
} // end of MYXCOL copy constructor
|
|
|
|
/***********************************************************************/
|
|
/* ReadColumn: */
|
|
/***********************************************************************/
|
|
void MYXCOL::ReadColumn(PGLOBAL g)
|
|
{
|
|
PTDBMYX tdbp = (PTDBMYX)To_Tdb;
|
|
|
|
if (tdbp->Isw) {
|
|
char *buf = NULL;
|
|
|
|
if (Flag < 3) {
|
|
buf = tdbp->Myc.GetCharField(Flag);
|
|
Value->SetValue_psz(buf);
|
|
} else
|
|
Value->Reset();
|
|
|
|
} else
|
|
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;
|
|
case 3: Value->SetValue(tdbp->Warnings); break;
|
|
default: Value->SetValue_psz("Invalid Flag"); break;
|
|
} // endswitch Flag
|
|
|
|
} // end of ReadColumn
|
|
|
|
/***********************************************************************/
|
|
/* WriteColumn: should never be called. */
|
|
/***********************************************************************/
|
|
void MYXCOL::WriteColumn(PGLOBAL)
|
|
{
|
|
assert(false);
|
|
} // end of WriteColumn
|
|
|
|
/* ---------------------------TDBMCL class --------------------------- */
|
|
|
|
/***********************************************************************/
|
|
/* TDBMCL class constructor. */
|
|
/***********************************************************************/
|
|
TDBMCL::TDBMCL(PMYDEF tdp) : TDBCAT(tdp)
|
|
{
|
|
Host = tdp->Hostname;
|
|
Db = tdp->Tabschema;
|
|
Tab = tdp->Tabname;
|
|
User = tdp->Username;
|
|
Pwd = tdp->Password;
|
|
Port = tdp->Portnumber;
|
|
} // end of TDBMCL constructor
|
|
|
|
/***********************************************************************/
|
|
/* GetResult: Get the list the MYSQL table columns. */
|
|
/***********************************************************************/
|
|
PQRYRES TDBMCL::GetResult(PGLOBAL g)
|
|
{
|
|
return MyColumns(g, NULL, Host, Db, User, Pwd, Tab, NULL, Port, false);
|
|
} // end of GetResult
|