diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
commit | a175314c3e5827eb193872241446f2f8f5c9d33c (patch) | |
tree | cd3d60ca99ae00829c52a6ca79150a5b6e62528b /storage/connect/ha_connect.cc | |
parent | Initial commit. (diff) | |
download | mariadb-10.5-upstream.tar.xz mariadb-10.5-upstream.zip |
Adding upstream version 1:10.5.12.upstream/1%10.5.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'storage/connect/ha_connect.cc')
-rw-r--r-- | storage/connect/ha_connect.cc | 7535 |
1 files changed, 7535 insertions, 0 deletions
diff --git a/storage/connect/ha_connect.cc b/storage/connect/ha_connect.cc new file mode 100644 index 00000000..e95e91e1 --- /dev/null +++ b/storage/connect/ha_connect.cc @@ -0,0 +1,7535 @@ +/* Copyright (C) MariaDB Corporation Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +/** + @file ha_connect.cc + + @brief + The ha_connect engine is a stubbed storage engine that enables to create tables + based on external data. Principally they are based on plain files of many + different types, but also on collections of such files, collection of tables, + local or remote MySQL/MariaDB tables retrieved via MySQL API, + ODBC/JDBC tables retrieving data from other DBMS having an ODBC/JDBC server, + and even virtual tables. + + @details + ha_connect will let you create/open/delete tables, the created table can be + done specifying an already existing file, the drop table command will just + suppress the table definition but not the eventual data file. + Indexes are not supported for all table types but data can be inserted, + updated or deleted. + + You can enable the CONNECT storage engine in your build by doing the + following during your build process:<br> ./configure + --with-connect-storage-engine + + You can install the CONNECT handler as all other storage handlers. + + Once this is done, MySQL will let you create tables with:<br> + CREATE TABLE <table name> (...) ENGINE=CONNECT; + + The example storage engine does not use table locks. It + implements an example "SHARE" that is inserted into a hash by table + name. This is not used yet. + + Please read the object definition in ha_connect.h before reading the rest + of this file. + + @note + This MariaDB CONNECT handler is currently an adaptation of the XDB handler + that was written for MySQL version 4.1.2-alpha. Its overall design should + be enhanced in the future to meet MariaDB requirements. + + @note + It was written also from the Brian's ha_example handler and contains parts + of it that are there, such as table and system variables. + + @note + When you create an CONNECT table, the MySQL Server creates a table .frm + (format) file in the database directory, using the table name as the file + name as is customary with MySQL. + For file based tables, if a file name is not specified, this is an inward + table. An empty file is made in the current data directory that you can + populate later like for other engine tables. This file modified on ALTER + and is deleted when dropping the table. + If a file name is specified, this in an outward table. The specified file + will be used as representing the table data and will not be modified or + deleted on command such as ALTER or DROP. + To get an idea of what occurs, here is an example select that would do + a scan of an entire table: + + @code + ha-connect::open + ha_connect::store_lock + ha_connect::external_lock + ha_connect::info + ha_connect::rnd_init + ha_connect::extra + ENUM HA_EXTRA_CACHE Cache record in HA_rrnd() + ha_connect::rnd_next + ha_connect::rnd_next + ha_connect::rnd_next + ha_connect::rnd_next + ha_connect::rnd_next + ha_connect::rnd_next + ha_connect::rnd_next + ha_connect::rnd_next + ha_connect::rnd_next + ha_connect::extra + ENUM HA_EXTRA_NO_CACHE End caching of records (def) + ha_connect::external_lock + ha_connect::extra + ENUM HA_EXTRA_RESET Reset database to after open + @endcode + + Here you see that the connect storage engine has 9 rows called before + rnd_next signals that it has reached the end of its data. Calls to + ha_connect::extra() are hints as to what will be occuring to the request. + + Author Olivier Bertrand + */ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#define MYSQL_SERVER 1 +#define DONT_DEFINE_VOID +#include <my_global.h> +#include "sql_parse.h" +#include "sql_base.h" +#include "sql_partition.h" +#undef OFFSET + +#define NOPARSE +#define NJDBC +#if defined(UNIX) +#include "osutil.h" +#endif // UNIX +#include "global.h" +#include "plgdbsem.h" +#include "xtable.h" +#include "tabext.h" +#if defined(ODBC_SUPPORT) +#include "odbccat.h" +#endif // ODBC_SUPPORT +#if defined(JAVA_SUPPORT) +#include "tabjdbc.h" +#include "jdbconn.h" +#endif // JAVA_SUPPORT +#if defined(CMGO_SUPPORT) +#include "cmgoconn.h" +#endif // CMGO_SUPPORT +#include "tabmysql.h" +#include "filamdbf.h" +#include "tabxcl.h" +#include "tabfmt.h" +//#include "reldef.h" +#include "tabcol.h" +#include "xindex.h" +#if defined(_WIN32) +#include <io.h> +#include "tabwmi.h" +#endif // _WIN32 +#include "connect.h" +#include "user_connect.h" +#include "ha_connect.h" +#include "myutil.h" +#include "preparse.h" +#include "inihandl.h" +#if defined(LIBXML2_SUPPORT) +#include "libdoc.h" +#endif // LIBXML2_SUPPORT +#include "taboccur.h" +#include "tabpivot.h" +#include "tabfix.h" + +#define my_strupr(p) my_caseup_str(default_charset_info, (p)); +#define my_strlwr(p) my_casedn_str(default_charset_info, (p)); +#define my_stricmp(a,b) my_strcasecmp(default_charset_info, (a), (b)) + + +/***********************************************************************/ +/* Initialize the ha_connect static members. */ +/***********************************************************************/ +#define SZCONV 1024 // Default converted text size +#define SZWORK 67108864 // Default work area size 64M +#define SZWMIN 4194304 // Minimum work area size 4M +#define JSONMAX 50 // JSON Default max grp size + +extern "C" { + char version[]= "Version 1.07.0002 March 22, 2021"; +#if defined(_WIN32) + char compver[]= "Version 1.07.0002 " __DATE__ " " __TIME__; + static char slash= '\\'; +#else // !_WIN32 + static char slash= '/'; +#endif // !_WIN32 +} // extern "C" + +#if MYSQL_VERSION_ID > 100200 +#define stored_in_db stored_in_db() +#endif // MYSQL_VERSION_ID + +#if defined(XMAP) + my_bool xmap= false; +#endif // XMAP + +ulong ha_connect::num= 0; + +#if defined(XMSG) +extern "C" { + char *msg_path; +} // extern "C" +#endif // XMSG + +#if defined(JAVA_SUPPORT) + char *JvmPath; + char *ClassPath; +#endif // JAVA_SUPPORT + +pthread_mutex_t parmut; +pthread_mutex_t usrmut; +pthread_mutex_t tblmut; + +#if defined(DEVELOPMENT) +char *GetUserVariable(PGLOBAL g, const uchar *varname); + +char *GetUserVariable(PGLOBAL g, const uchar *varname) +{ + char buf[1024]; + bool b; + THD *thd= current_thd; + CHARSET_INFO *cs= system_charset_info; + String *str= NULL, tmp(buf, sizeof(buf), cs); + HASH uvars= thd->user_vars; + user_var_entry *uvar= (user_var_entry*)my_hash_search(&uvars, varname, 0); + + if (uvar) + str= uvar->val_str(&b, &tmp, NOT_FIXED_DEC); + + return str ? PlugDup(g, str->ptr()) : NULL; +}; // end of GetUserVariable +#endif // DEVELOPMENT + +/***********************************************************************/ +/* Utility functions. */ +/***********************************************************************/ +PQRYRES OEMColumns(PGLOBAL g, PTOS topt, char *tab, char *db, bool info); +PQRYRES VirColumns(PGLOBAL g, bool info); +PQRYRES JSONColumns(PGLOBAL g, PCSZ db, PCSZ dsn, PTOS topt, bool info); +#ifdef BSON_SUPPORT +PQRYRES BSONColumns(PGLOBAL g, PCSZ db, PCSZ dsn, PTOS topt, bool info); +#endif // BSON_SUPPORT +PQRYRES XMLColumns(PGLOBAL g, char *db, char *tab, PTOS topt, bool info); +#if defined(REST_SUPPORT) +PQRYRES RESTColumns(PGLOBAL g, PTOS topt, char *tab, char *db, bool info); +#endif // REST_SUPPORT +#if defined(JAVA_SUPPORT) +PQRYRES MGOColumns(PGLOBAL g, PCSZ db, PCSZ url, PTOS topt, bool info); +#endif // JAVA_SUPPORT +int TranslateJDBCType(int stp, char *tn, int prec, int& len, char& v); +void PushWarning(PGLOBAL g, THD *thd, int level); +bool CheckSelf(PGLOBAL g, TABLE_SHARE *s, PCSZ host, PCSZ db, + PCSZ tab, PCSZ src, int port); +#if defined(ZIP_SUPPORT) +bool ZipLoadFile(PGLOBAL, PCSZ, PCSZ, PCSZ, bool, bool); +#endif // ZIP_SUPPORT +bool ExactInfo(void); +#if defined(CMGO_SUPPORT) +//void mongo_init(bool); +#endif // CMGO_SUPPORT +USETEMP UseTemp(void); +int GetConvSize(void); +TYPCONV GetTypeConv(void); +int GetDefaultDepth(void); +int GetDefaultPrec(void); +bool JsonAllPath(void); +char *GetJsonNull(void); +uint GetJsonGrpSize(void); +char *GetJavaWrapper(void); +#if defined(BSON_SUPPORT) +bool Force_Bson(void); +#endif // BSON_SUPPORT +size_t GetWorkSize(void); +void SetWorkSize(size_t); +extern "C" const char *msglang(void); + +static void PopUser(PCONNECT xp); +static PCONNECT GetUser(THD *thd, PCONNECT xp); +static PGLOBAL GetPlug(THD *thd, PCONNECT& lxp); + +static handler *connect_create_handler(handlerton *hton, + TABLE_SHARE *table, + MEM_ROOT *mem_root); + +static bool checkPrivileges(THD* thd, TABTYPE type, PTOS options, + const char* db, TABLE* table = NULL, + bool quick = false); + +static int connect_assisted_discovery(handlerton *hton, THD* thd, + TABLE_SHARE *table_s, + HA_CREATE_INFO *info); + +/****************************************************************************/ +/* Return str as a zero terminated string. */ +/****************************************************************************/ +static char *strz(PGLOBAL g, LEX_CSTRING &ls) +{ + char* str= NULL; + + if (ls.str) { + str= (char*)PlugSubAlloc(g, NULL, ls.length + 1); + memcpy(str, ls.str, ls.length); + str[ls.length] = 0; + } // endif str + + return str; +} // end of strz + +/***********************************************************************/ +/* CONNECT session variables definitions. */ +/***********************************************************************/ +// Tracing: 0 no, 1 yes, 2 more, 4 index... 511 all +const char *xtrace_names[] = +{ + "YES", "MORE", "INDEX", "MEMORY", "SUBALLOC", + "QUERY", "STMT", "HANDLER", "BLOCK", "MONGO", NullS +}; + +TYPELIB xtrace_typelib = +{ + array_elements(xtrace_names) - 1, "xtrace_typelib", + xtrace_names, NULL +}; + +static MYSQL_THDVAR_SET( + xtrace, // name + PLUGIN_VAR_RQCMDARG, // opt + "Trace values.", // comment + NULL, // check + NULL, // update function + 0, // def (NO) + &xtrace_typelib); // typelib + +// Getting exact info values +static MYSQL_THDVAR_BOOL(exact_info, PLUGIN_VAR_RQCMDARG, + "Getting exact info values", + NULL, NULL, 0); + +// Enabling cond_push +static MYSQL_THDVAR_BOOL(cond_push, PLUGIN_VAR_RQCMDARG, + "Enabling cond_push", + NULL, NULL, 1); // YES by default + +/** + Temporary file usage: + no: Not using temporary file + auto: Using temporary file when needed + yes: Allways using temporary file + force: Force using temporary file (no MAP) + test: Reserved +*/ +const char *usetemp_names[]= +{ + "NO", "AUTO", "YES", "FORCE", "TEST", NullS +}; + +TYPELIB usetemp_typelib= +{ + array_elements(usetemp_names) - 1, "usetemp_typelib", + usetemp_names, NULL +}; + +static MYSQL_THDVAR_ENUM( + use_tempfile, // name + PLUGIN_VAR_RQCMDARG, // opt + "Temporary file use.", // comment + NULL, // check + NULL, // update function + 1, // def (AUTO) + &usetemp_typelib); // typelib + +#ifdef _WIN64 +// Size used for g->Sarea_Size +static MYSQL_THDVAR_ULONGLONG(work_size, + PLUGIN_VAR_RQCMDARG, + "Size of the CONNECT work area.", + NULL, NULL, SZWORK, SZWMIN, ULONGLONG_MAX, 1); +#else +// Size used for g->Sarea_Size +static MYSQL_THDVAR_ULONG(work_size, + PLUGIN_VAR_RQCMDARG, + "Size of the CONNECT work area.", + NULL, NULL, SZWORK, SZWMIN, ULONG_MAX, 1); +#endif + +// Size used when converting TEXT columns to VARCHAR +static MYSQL_THDVAR_INT(conv_size, + PLUGIN_VAR_RQCMDARG, // opt + "Size used when converting TEXT columns.", + NULL, NULL, SZCONV, 0, 65500, 1); + +/** + Type conversion: + no: Unsupported types -> TYPE_ERROR + yes: TEXT -> VARCHAR + force: Do it also for ODBC BINARY and BLOBs + skip: skip unsupported type columns in Discovery +*/ +const char *xconv_names[]= +{ + "NO", "YES", "FORCE", "SKIP", NullS +}; + +TYPELIB xconv_typelib= +{ + array_elements(xconv_names) - 1, "xconv_typelib", + xconv_names, NULL +}; + +static MYSQL_THDVAR_ENUM( + type_conv, // name + PLUGIN_VAR_RQCMDARG, // opt + "Unsupported types conversion.", // comment + NULL, // check + NULL, // update function + 1, // def (yes) + &xconv_typelib); // typelib + +// Adding JPATH to all Json table columns +static MYSQL_THDVAR_BOOL(json_all_path, PLUGIN_VAR_RQCMDARG, + "Adding JPATH to all Json table columns", + NULL, NULL, 1); // YES by default + +// Null representation for JSON values +static MYSQL_THDVAR_STR(json_null, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC, + "Representation of Json null values", + // check_json_null, update_json_null, + NULL, NULL, "<null>"); + +// Default Json, XML or Mongo depth +static MYSQL_THDVAR_INT(default_depth, + PLUGIN_VAR_RQCMDARG, + "Default depth used by Json, XML and Mongo discovery", + NULL, NULL, 5, -1, 16, 1); // Defaults to 5 + +// Default precision for doubles +static MYSQL_THDVAR_INT(default_prec, + PLUGIN_VAR_RQCMDARG, + "Default precision used for doubles", + NULL, NULL, 6, 0, 16, 1); // Defaults to 6 + +// Estimate max number of rows for JSON aggregate functions +static MYSQL_THDVAR_UINT(json_grp_size, + PLUGIN_VAR_RQCMDARG, // opt + "max number of rows for JSON aggregate functions.", + NULL, NULL, JSONMAX, 1, INT_MAX, 1); + +#if defined(JAVA_SUPPORT) +// Default java wrapper to use with JDBC tables +static MYSQL_THDVAR_STR(java_wrapper, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC, + "Java wrapper class name", + // check_java_wrapper, update_java_wrapper, + NULL, NULL, "wrappers/JdbcInterface"); +#endif // JAVA_SUPPORT + +// This is apparently not acceptable for a plugin so it is undocumented +#if defined(JAVA_SUPPORT) || defined(CMGO_SUPPORT) +// Enabling MONGO table type +#if defined(MONGO_SUPPORT) || (MYSQL_VERSION_ID > 100200) +static MYSQL_THDVAR_BOOL(enable_mongo, PLUGIN_VAR_RQCMDARG, + "Enabling the MongoDB access", NULL, NULL, 1); +#else // !version 2,3 +static MYSQL_THDVAR_BOOL(enable_mongo, PLUGIN_VAR_RQCMDARG, + "Enabling the MongoDB access", NULL, NULL, 0); +#endif // !version 2,3 +#endif // JAVA_SUPPORT || CMGO_SUPPORT + +#if defined(BSON_SUPPORT) +// Force using BSON for JSON tables +static MYSQL_THDVAR_BOOL(force_bson, PLUGIN_VAR_RQCMDARG, + "Force using BSON for JSON tables", + NULL, NULL, 0); // NO by default +#endif // BSON_SUPPORT + +#if defined(XMSG) || defined(NEWMSG) +const char *language_names[]= +{ + "default", "english", "french", NullS +}; + +TYPELIB language_typelib= +{ + array_elements(language_names) - 1, "language_typelib", + language_names, NULL +}; + +static MYSQL_THDVAR_ENUM( + msg_lang, // name + PLUGIN_VAR_RQCMDARG, // opt + "Message language", // comment + NULL, // check + NULL, // update + 1, // def (ENGLISH) + &language_typelib); // typelib +#endif // XMSG || NEWMSG + +/***********************************************************************/ +/* The CONNECT handlerton object. */ +/***********************************************************************/ +handlerton *connect_hton= NULL; + +/***********************************************************************/ +/* Function to export session variable values to other source files. */ +/***********************************************************************/ +uint GetTraceValue(void) + {return (uint)(connect_hton ? THDVAR(current_thd, xtrace) : 0);} +bool ExactInfo(void) {return THDVAR(current_thd, exact_info);} +static bool CondPushEnabled(void) {return THDVAR(current_thd, cond_push);} +bool JsonAllPath(void) {return THDVAR(current_thd, json_all_path);} +USETEMP UseTemp(void) {return (USETEMP)THDVAR(current_thd, use_tempfile);} +int GetConvSize(void) {return THDVAR(current_thd, conv_size);} +TYPCONV GetTypeConv(void) {return (TYPCONV)THDVAR(current_thd, type_conv);} +char *GetJsonNull(void) + {return connect_hton ? THDVAR(current_thd, json_null) : NULL;} +int GetDefaultDepth(void) {return THDVAR(current_thd, default_depth);} +int GetDefaultPrec(void) {return THDVAR(current_thd, default_prec);} +uint GetJsonGrpSize(void) + {return connect_hton ? THDVAR(current_thd, json_grp_size) : 50;} +size_t GetWorkSize(void) {return (size_t)THDVAR(current_thd, work_size);} +void SetWorkSize(size_t) +{ + // Changing the session variable value seems to be impossible here + // and should be done in a check function + push_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, 0, + "Work size too big, try setting a smaller value"); +} // end of SetWorkSize + +#if defined(JAVA_SUPPORT) +char *GetJavaWrapper(void) +{return connect_hton ? THDVAR(current_thd, java_wrapper) + : (char*)"wrappers/JdbcInterface";} +#endif // JAVA_SUPPORT + +#if defined(JAVA_SUPPORT) || defined(CMGO_SUPPORT) +bool MongoEnabled(void) {return THDVAR(current_thd, enable_mongo);} +#endif // JAVA_SUPPORT || CMGO_SUPPORT + +#if defined(BSON_SUPPORT) +bool Force_Bson(void) {return THDVAR(current_thd, force_bson);} +#endif // BSON_SUPPORT) + +#if defined(XMSG) || defined(NEWMSG) +extern "C" const char *msglang(void) + {return language_names[THDVAR(current_thd, msg_lang)];} +#else // !XMSG && !NEWMSG +extern "C" const char *msglang(void) +{ +#if defined(FRENCH) + return "french"; +#else // DEFAULT + return "english"; +#endif // DEFAULT +} // end of msglang +#endif // !XMSG && !NEWMSG + +#if 0 +/***********************************************************************/ +/* Global variables update functions. */ +/***********************************************************************/ +static void update_connect_zconv(MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save) +{ + zconv= *(int *)var_ptr= *(int *)save; +} // end of update_connect_zconv + +static void update_connect_xconv(MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save) +{ + xconv= (int)(*(ulong *)var_ptr= *(ulong *)save); +} // end of update_connect_xconv + +#if defined(XMAP) +static void update_connect_xmap(MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save) +{ + xmap= (my_bool)(*(my_bool *)var_ptr= *(my_bool *)save); +} // end of update_connect_xmap +#endif // XMAP +#endif // 0 + +#if 0 // (was XMSG) Unuseful because not called for default value +static void update_msg_path(MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, const void *save) +{ + char *value= *(char**)save; + char *old= *(char**)var_ptr; + + if (value) + *(char**)var_ptr= my_strdup(value, MYF(0)); + else + *(char**)var_ptr= 0; + + my_free(old); +} // end of update_msg_path + +static int check_msg_path (MYSQL_THD thd, struct st_mysql_sys_var *var, + void *save, struct st_mysql_value *value) +{ + const char *path; + char buff[512]; + int len= sizeof(buff); + + path= value->val_str(value, buff, &len); + + if (path && *path != '*') { + /* Save a pointer to the name in the + 'file_format_name_map' constant array. */ + *(char**)save= my_strdup(path, MYF(0)); + return(0); + } else { + push_warning_printf(thd, + Sql_condition::WARN_LEVEL_WARN, + ER_WRONG_ARGUMENTS, + "CONNECT: invalid message path"); + } // endif path + + *(char**)save= NULL; + return(1); +} // end of check_msg_path +#endif // 0 + +/** + CREATE TABLE option list (table options) + + These can be specified in the CREATE TABLE: + CREATE TABLE ( ... ) {...here...} +*/ +ha_create_table_option connect_table_option_list[]= +{ + HA_TOPTION_STRING("TABLE_TYPE", type), + HA_TOPTION_STRING("FILE_NAME", filename), + HA_TOPTION_STRING("XFILE_NAME", optname), +//HA_TOPTION_STRING("CONNECT_STRING", connect), + HA_TOPTION_STRING("TABNAME", tabname), + HA_TOPTION_STRING("TABLE_LIST", tablist), + HA_TOPTION_STRING("DBNAME", dbname), + HA_TOPTION_STRING("SEP_CHAR", separator), + HA_TOPTION_STRING("QCHAR", qchar), + HA_TOPTION_STRING("MODULE", module), + HA_TOPTION_STRING("SUBTYPE", subtype), + HA_TOPTION_STRING("CATFUNC", catfunc), + HA_TOPTION_STRING("SRCDEF", srcdef), + HA_TOPTION_STRING("COLIST", colist), + HA_TOPTION_STRING("FILTER", filter), + HA_TOPTION_STRING("OPTION_LIST", oplist), + HA_TOPTION_STRING("DATA_CHARSET", data_charset), + HA_TOPTION_STRING("HTTP", http), + HA_TOPTION_STRING("URI", uri), + HA_TOPTION_NUMBER("LRECL", lrecl, 0, 0, INT_MAX32, 1), + HA_TOPTION_NUMBER("BLOCK_SIZE", elements, 0, 0, INT_MAX32, 1), +//HA_TOPTION_NUMBER("ESTIMATE", estimate, 0, 0, INT_MAX32, 1), + HA_TOPTION_NUMBER("MULTIPLE", multiple, 0, 0, 3, 1), + HA_TOPTION_NUMBER("HEADER", header, 0, 0, 3, 1), + HA_TOPTION_NUMBER("QUOTED", quoted, (ulonglong) -1, 0, 3, 1), + HA_TOPTION_NUMBER("ENDING", ending, (ulonglong) -1, 0, INT_MAX32, 1), + HA_TOPTION_NUMBER("COMPRESS", compressed, 0, 0, 2, 1), + HA_TOPTION_BOOL("MAPPED", mapped, 0), + HA_TOPTION_BOOL("HUGE", huge, 0), + HA_TOPTION_BOOL("SPLIT", split, 0), + HA_TOPTION_BOOL("READONLY", readonly, 0), + HA_TOPTION_BOOL("SEPINDEX", sepindex, 0), + HA_TOPTION_BOOL("ZIPPED", zipped, 0), + HA_TOPTION_END +}; + + +/** + CREATE TABLE option list (field options) + + These can be specified in the CREATE TABLE per field: + CREATE TABLE ( field ... {...here...}, ... ) +*/ +ha_create_table_option connect_field_option_list[]= +{ + HA_FOPTION_NUMBER("FLAG", offset, (ulonglong) -1, 0, INT_MAX32, 1), + HA_FOPTION_NUMBER("MAX_DIST", freq, 0, 0, INT_MAX32, 1), // BLK_INDX + HA_FOPTION_NUMBER("FIELD_LENGTH", fldlen, 0, 0, INT_MAX32, 1), + HA_FOPTION_STRING("DATE_FORMAT", dateformat), + HA_FOPTION_STRING("FIELD_FORMAT", fieldformat), + HA_FOPTION_STRING("JPATH", jsonpath), + HA_FOPTION_STRING("XPATH", xmlpath), + HA_FOPTION_STRING("SPECIAL", special), + HA_FOPTION_ENUM("DISTRIB", opt, "scattered,clustered,sorted", 0), + HA_FOPTION_END +}; + +/* + CREATE TABLE option list (index options) + + These can be specified in the CREATE TABLE per index: + CREATE TABLE ( field ..., .., INDEX .... *here*, ... ) +*/ +ha_create_table_option connect_index_option_list[]= +{ + HA_IOPTION_BOOL("DYNAM", dynamic, 0), + HA_IOPTION_BOOL("MAPPED", mapped, 0), + HA_IOPTION_END +}; + +/***********************************************************************/ +/* Push G->Message as a MySQL warning. */ +/***********************************************************************/ +bool PushWarning(PGLOBAL g, PTDB tdbp, int level) +{ + PHC phc; + THD *thd; + MYCAT *cat= (MYCAT*)tdbp->GetDef()->GetCat(); + + if (!cat || !(phc= cat->GetHandler()) || !phc->GetTable() || + !(thd= (phc->GetTable())->in_use)) + return true; + + PushWarning(g, thd, level); + return false; +} // end of PushWarning + +void PushWarning(PGLOBAL g, THD *thd, int level) + { + if (thd) { + Sql_condition::enum_warning_level wlvl; + + wlvl= (Sql_condition::enum_warning_level)level; + push_warning(thd, wlvl, 0, g->Message); + } else + htrc("%s\n", g->Message); + + } // end of PushWarning + +#ifdef HAVE_PSI_INTERFACE +static PSI_mutex_key con_key_mutex_CONNECT_SHARE_mutex; + +static PSI_mutex_info all_connect_mutexes[]= +{ + { &con_key_mutex_CONNECT_SHARE_mutex, "CONNECT_SHARE::mutex", 0} +}; + +static void init_connect_psi_keys() +{ + const char* category= "connect"; + int count; + + if (PSI_server == NULL) + return; + + count= array_elements(all_connect_mutexes); + PSI_server->register_mutex(category, all_connect_mutexes, count); +} +#else +static void init_connect_psi_keys() {} +#endif + + +DllExport LPCSTR PlugSetPath(LPSTR to, LPCSTR name, LPCSTR dir) +{ + const char *res= PlugSetPath(to, mysql_data_home, name, dir); + return res; +} + + +/** + @brief + If frm_error() is called then we will use this to determine + the file extensions that exist for the storage engine. This is also + used by the default rename_table and delete_table method in + handler.cc. + + For engines that have two file name extensions (separate meta/index file + and data file), the order of elements is relevant. First element of engine + file name extensions array should be meta/index file extension. Second + element - data file extension. This order is assumed by + prepare_for_repair() when REPAIR TABLE ... USE_FRM is issued. + + @see + rename_table method in handler.cc and + delete_table method in handler.cc +*/ +static const char *ha_connect_exts[]= { + ".dos", ".fix", ".csv", ".bin", ".fmt", ".dbf", ".xml", ".json", ".ini", + ".vec", ".dnx", ".fnx", ".bnx", ".vnx", ".dbx", ".dop", ".fop", ".bop", + ".vop", NULL}; + +/** + @brief + Plugin initialization +*/ +static int connect_init_func(void *p) +{ + DBUG_ENTER("connect_init_func"); + +// added from Sergei mail +#if 0 // (defined(LINUX)) + Dl_info dl_info; + if (dladdr(&connect_hton, &dl_info)) + { + if (dlopen(dl_info.dli_fname, RTLD_NOLOAD | RTLD_NOW | RTLD_GLOBAL) == 0) + { + sql_print_information("CONNECT: dlopen() failed, OEM table type is not supported"); + sql_print_information("CONNECT: %s", dlerror()); + } + } + else + { + sql_print_information("CONNECT: dladdr() failed, OEM table type is not supported"); + sql_print_information("CONNECT: %s", dlerror()); + } +#endif // 0 (LINUX) + +#if defined(_WIN32) + sql_print_information("CONNECT: %s", compver); +#else // !_WIN32 + sql_print_information("CONNECT: %s", version); +#endif // !_WIN32 + pthread_mutex_init(&parmut, NULL); + pthread_mutex_init(&usrmut, NULL); + pthread_mutex_init(&tblmut, NULL); + +#if defined(LIBXML2_SUPPORT) + XmlInitParserLib(); +#endif // LIBXML2_SUPPORT + +#if 0 //defined(CMGO_SUPPORT) + mongo_init(true); +#endif // CMGO_SUPPORT + + init_connect_psi_keys(); + + connect_hton= (handlerton *)p; + connect_hton->create= connect_create_handler; + connect_hton->flags= HTON_TEMPORARY_NOT_SUPPORTED; + connect_hton->table_options= connect_table_option_list; + connect_hton->field_options= connect_field_option_list; + connect_hton->index_options= connect_index_option_list; + connect_hton->tablefile_extensions= ha_connect_exts; + connect_hton->discover_table_structure= connect_assisted_discovery; + + if (trace(128)) + sql_print_information("connect_init: hton=%p", p); + + DTVAL::SetTimeShift(); // Initialize time zone shift once for all + BINCOL::SetEndian(); // Initialize host endian setting +#if defined(JAVA_SUPPORT) + JAVAConn::SetJVM(); +#endif // JAVA_SUPPORT + DBUG_RETURN(0); +} // end of connect_init_func + + +/** + @brief + Plugin clean up +*/ +int connect_done_func(void *) +{ + int error= 0; + PCONNECT pc, pn; + DBUG_ENTER("connect_done_func"); + +#ifdef LIBXML2_SUPPORT + XmlCleanupParserLib(); +#endif // LIBXML2_SUPPORT + +#if defined(CMGO_SUPPORT) + CMgoConn::mongo_init(false); +#endif // CMGO_SUPPORT + +#ifdef JAVA_SUPPORT + JAVAConn::ResetJVM(); +#endif // JAVA_SUPPORT + +#if !defined(_WIN32) + PROFILE_End(); +#endif // !_WIN32 + + pthread_mutex_lock(&usrmut); + for (pc= user_connect::to_users; pc; pc= pn) { + if (pc->g) + PlugCleanup(pc->g, true); + + pn= pc->next; + delete pc; + } // endfor pc + + pthread_mutex_unlock(&usrmut); + + pthread_mutex_destroy(&usrmut); + pthread_mutex_destroy(&parmut); + pthread_mutex_destroy(&tblmut); + connect_hton= NULL; + DBUG_RETURN(error); +} // end of connect_done_func + + +/** + @brief + Example of simple lock controls. The "share" it creates is a + structure we will pass to each CONNECT handler. Do you have to have + one of these? Well, you have pieces that are used for locking, and + they are needed to function. +*/ + +CONNECT_SHARE *ha_connect::get_share() +{ + CONNECT_SHARE *tmp_share; + + lock_shared_ha_data(); + + if (!(tmp_share= static_cast<CONNECT_SHARE*>(get_ha_share_ptr()))) { + tmp_share= new CONNECT_SHARE; + if (!tmp_share) + goto err; + mysql_mutex_init(con_key_mutex_CONNECT_SHARE_mutex, + &tmp_share->mutex, MY_MUTEX_INIT_FAST); + set_ha_share_ptr(static_cast<Handler_share*>(tmp_share)); + } // endif tmp_share + + err: + unlock_shared_ha_data(); + return tmp_share; +} // end of get_share + + +static handler* connect_create_handler(handlerton *hton, + TABLE_SHARE *table, + MEM_ROOT *mem_root) +{ + handler *h= new (mem_root) ha_connect(hton, table); + + if (trace(128)) + htrc("New CONNECT %p, table: %.*s\n", h, + table ? table->table_name.length : 6, + table ? table->table_name.str : "<null>"); + + return h; +} // end of connect_create_handler + +/****************************************************************************/ +/* ha_connect constructor. */ +/****************************************************************************/ +ha_connect::ha_connect(handlerton *hton, TABLE_SHARE *table_arg) + :handler(hton, table_arg) +{ + hnum= ++num; + xp= (table) ? GetUser(ha_thd(), NULL) : NULL; + if (xp) + xp->SetHandler(this); +#if defined(_WIN32) + datapath= ".\\"; +#else // !_WIN32 + datapath= "./"; +#endif // !_WIN32 + tdbp= NULL; + sdvalin1= sdvalin2= sdvalin3= sdvalin4= NULL; + sdvalout= NULL; + xmod= MODE_ANY; + istable= false; + memset(partname, 0, sizeof(partname)); + bzero((char*) &xinfo, sizeof(XINFO)); + valid_info= false; + valid_query_id= 0; + creat_query_id= (table && table->in_use) ? table->in_use->query_id : 0; + stop= false; + alter= false; + mrr= false; + nox= true; + abort= false; + indexing= -1; + locked= 0; + part_id= NULL; + data_file_name= NULL; + index_file_name= NULL; + enable_activate_all_index= 0; + int_table_flags= (HA_NO_TRANSACTIONS | HA_NO_PREFIX_CHAR_KEYS); + ref_length= sizeof(int); + share= NULL; + tshp= NULL; +} // end of ha_connect constructor + + +/****************************************************************************/ +/* ha_connect destructor. */ +/****************************************************************************/ +ha_connect::~ha_connect(void) +{ + if (trace(128)) + htrc("Delete CONNECT %p, table: %.*s, xp=%p count=%d\n", this, + table ? table->s->table_name.length : 6, + table ? table->s->table_name.str : "<null>", + xp, xp ? xp->count : 0); + + PopUser(xp); +} // end of ha_connect destructor + + +/****************************************************************************/ +/* Check whether this user can be removed. */ +/****************************************************************************/ +static void PopUser(PCONNECT xp) +{ + if (xp) { + pthread_mutex_lock(&usrmut); + xp->count--; + + if (!xp->count) { + PCONNECT p; + + for (p= user_connect::to_users; p; p= p->next) + if (p == xp) + break; + + if (p) { + if (p->next) + p->next->previous= p->previous; + + if (p->previous) + p->previous->next= p->next; + else + user_connect::to_users= p->next; + + } // endif p + + PlugCleanup(xp->g, true); + delete xp; + } // endif count + + pthread_mutex_unlock(&usrmut); + } // endif xp + +} // end of PopUser + + +/****************************************************************************/ +/* Get a pointer to the user of this handler. */ +/****************************************************************************/ +static PCONNECT GetUser(THD *thd, PCONNECT xp) +{ + if (!thd) + return NULL; + + if (xp) { + if (thd == xp->thdp) + return xp; + + PopUser(xp); // Avoid memory leak + } // endif xp + + pthread_mutex_lock(&usrmut); + + for (xp= user_connect::to_users; xp; xp= xp->next) + if (thd == xp->thdp) + break; + + if (xp) + xp->count++; + + pthread_mutex_unlock(&usrmut); + + if (!xp) { + xp= new user_connect(thd); + + if (xp->user_init()) { + delete xp; + xp= NULL; + } // endif user_init + + } // endif xp + + //} else + // xp->count++; + + return xp; +} // end of GetUser + +/****************************************************************************/ +/* Get the global pointer of the user of this handler. */ +/****************************************************************************/ +static PGLOBAL GetPlug(THD *thd, PCONNECT& lxp) +{ + lxp= GetUser(thd, lxp); + return (lxp) ? lxp->g : NULL; +} // end of GetPlug + +/****************************************************************************/ +/* Get the implied table type. */ +/****************************************************************************/ +TABTYPE ha_connect::GetRealType(PTOS pos) +{ + TABTYPE type= TAB_UNDEF; + + if (pos || (pos= GetTableOptionStruct())) { + type= GetTypeID(pos->type); + + if (type == TAB_UNDEF && !pos->http) + type= pos->srcdef ? TAB_MYSQL : pos->tabname ? TAB_PRX : TAB_DOS; +#if defined(REST_SUPPORT) + else if (pos->http) + switch (type) { + case TAB_JSON: + case TAB_XML: + case TAB_CSV: + case TAB_UNDEF: + type = TAB_REST; + break; + case TAB_REST: + type = TAB_NIY; + break; + default: + break; + } // endswitch type +#endif // REST_SUPPORT + + } // endif pos + + return type; +} // end of GetRealType + +/** @brief + The name of the index type that will be used for display. + Don't implement this method unless you really have indexes. + */ +const char *ha_connect::index_type(uint inx) +{ + switch (GetIndexType(GetRealType())) { + case 1: + if (table_share) + return (GetIndexOption(&table_share->key_info[inx], "Dynamic")) + ? "KINDEX" : "XINDEX"; + else + return "XINDEX"; + + case 2: return "REMOTE"; + case 3: return "VIRTUAL"; + } // endswitch + + return "Unknown"; +} // end of index_type + +/** @brief + This is a bitmap of flags that indicates how the storage engine + implements indexes. The current index flags are documented in + handler.h. If you do not implement indexes, just return zero here. + + @details + part is the key part to check. First key part is 0. + If all_parts is set, MySQL wants to know the flags for the combined + index, up to and including 'part'. +*/ +//ong ha_connect::index_flags(uint inx, uint part, bool all_parts) const +ulong ha_connect::index_flags(uint, uint, bool) const +{ + ulong flags= HA_READ_NEXT | HA_READ_RANGE | + HA_KEYREAD_ONLY | HA_KEY_SCAN_NOT_ROR; + ha_connect *hp= (ha_connect*)this; + PTOS pos= hp->GetTableOptionStruct(); + + if (pos) { + TABTYPE type= hp->GetRealType(pos); + + switch (GetIndexType(type)) { + case 1: flags|= (HA_READ_ORDER | HA_READ_PREV); break; + case 2: flags|= HA_READ_AFTER_KEY; break; + } // endswitch + + } // endif pos + + return flags; +} // end of index_flags + +/** @brief + This is a list of flags that indicate what functionality the storage + engine implements. The current table flags are documented in handler.h +*/ +ulonglong ha_connect::table_flags() const +{ + ulonglong flags= HA_CAN_VIRTUAL_COLUMNS | HA_REC_NOT_IN_SEQ | + HA_NO_AUTO_INCREMENT | HA_NO_PREFIX_CHAR_KEYS | + HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE | + HA_PARTIAL_COLUMN_READ | HA_FILE_BASED | +// HA_NULL_IN_KEY | not implemented yet +// HA_FAST_KEY_READ | causes error when sorting (???) + HA_NO_TRANSACTIONS | HA_DUPLICATE_KEY_NOT_IN_ORDER | + HA_NO_BLOBS | HA_MUST_USE_TABLE_CONDITION_PUSHDOWN | + HA_REUSES_FILE_NAMES; + ha_connect *hp= (ha_connect*)this; + PTOS pos= hp->GetTableOptionStruct(); + + if (pos) { + TABTYPE type= hp->GetRealType(pos); + + if (IsFileType(type)) + flags|= HA_FILE_BASED; + + if (IsExactType(type)) + flags|= (HA_HAS_RECORDS | HA_STATS_RECORDS_IS_EXACT); + + // No data change on ALTER for outward tables + if (!IsFileType(type) || hp->FileExists(pos->filename, true)) + flags|= HA_NO_COPY_ON_ALTER; + + } // endif pos + + return flags; +} // end of table_flags + +/****************************************************************************/ +/* Return the value of an option specified in an option list. */ +/****************************************************************************/ +PCSZ GetListOption(PGLOBAL g, PCSZ opname, PCSZ oplist, PCSZ def) +{ + if (!oplist) + return (char*)def; + + char key[16], val[256]; + char *pv, *pn, *pk= (char*)oplist; + PCSZ opval= def; + int n; + + while (*pk == ' ') + pk++; + + for (; pk; pk= pn) { + pn= strchr(pk, ','); + pv= strchr(pk, '='); + + if (pv && (!pn || pv < pn)) { + n= MY_MIN(static_cast<size_t>(pv - pk), sizeof(key) - 1); + memcpy(key, pk, n); + + while (n && key[n - 1] == ' ') + n--; + + key[n]= 0; + + while (*(++pv) == ' '); + + n= MY_MIN((pn ? pn - pv : strlen(pv)), sizeof(val) - 1); + memcpy(val, pv, n); + + while (n && val[n - 1] == ' ') + n--; + + val[n]= 0; + } else { + n= MY_MIN((pn ? pn - pk : strlen(pk)), sizeof(key) - 1); + memcpy(key, pk, n); + + while (n && key[n - 1] == ' ') + n--; + + key[n]= 0; + val[0]= 0; + } // endif pv + + if (!stricmp(opname, key)) { + opval= PlugDup(g, val); + break; + } else if (!pn) + break; + + while (*(++pn) == ' '); + } // endfor pk + + return opval; +} // end of GetListOption + +/****************************************************************************/ +/* Return the value of a string option or NULL if not specified. */ +/****************************************************************************/ +PCSZ GetStringTableOption(PGLOBAL g, PTOS options, PCSZ opname, PCSZ sdef) +{ + PCSZ opval= NULL; + + if (!options) + return sdef; + else if (!stricmp(opname, "Type")) + opval= options->type; + else if (!stricmp(opname, "Filename")) + opval= options->filename; + else if (!stricmp(opname, "Optname")) + opval= options->optname; + else if (!stricmp(opname, "Tabname")) + opval= options->tabname; + else if (!stricmp(opname, "Tablist")) + opval= options->tablist; + else if (!stricmp(opname, "Database") || + !stricmp(opname, "DBname")) + opval= options->dbname; + else if (!stricmp(opname, "Separator")) + opval= options->separator; + else if (!stricmp(opname, "Qchar")) + opval= options->qchar; + else if (!stricmp(opname, "Module")) + opval= options->module; + else if (!stricmp(opname, "Subtype")) + opval= options->subtype; + else if (!stricmp(opname, "Catfunc")) + opval= options->catfunc; + else if (!stricmp(opname, "Srcdef")) + opval= options->srcdef; + else if (!stricmp(opname, "Colist")) + opval= options->colist; + else if (!stricmp(opname, "Filter")) + opval= options->filter; + else if (!stricmp(opname, "Data_charset")) + opval= options->data_charset; + else if (!stricmp(opname, "Http") || !stricmp(opname, "URL")) + opval= options->http; + else if (!stricmp(opname, "Uri")) + opval= options->uri; + + if (!opval && options->oplist) + opval= GetListOption(g, opname, options->oplist); + + return opval ? (char*)opval : sdef; +} // end of GetStringTableOption + +/****************************************************************************/ +/* Return the value of a Boolean option or bdef if not specified. */ +/****************************************************************************/ +bool GetBooleanTableOption(PGLOBAL g, PTOS options, PCSZ opname, bool bdef) +{ + bool opval= bdef; + PCSZ pv; + + if (!options) + return bdef; + else if (!stricmp(opname, "Mapped")) + opval= options->mapped; + else if (!stricmp(opname, "Huge")) + opval= options->huge; + else if (!stricmp(opname, "Split")) + opval= options->split; + else if (!stricmp(opname, "Readonly")) + opval= options->readonly; + else if (!stricmp(opname, "SepIndex")) + opval= options->sepindex; + else if (!stricmp(opname, "Header")) + opval= (options->header != 0); // Is Boolean for some table types + else if (!stricmp(opname, "Zipped")) + opval= options->zipped; + else if (options->oplist) + if ((pv= GetListOption(g, opname, options->oplist))) + opval= (!*pv || *pv == 'y' || *pv == 'Y' || atoi(pv) != 0); + + return opval; +} // end of GetBooleanTableOption + +/****************************************************************************/ +/* Return the value of an integer option or NO_IVAL if not specified. */ +/****************************************************************************/ +int GetIntegerTableOption(PGLOBAL g, PTOS options, PCSZ opname, int idef) +{ + ulonglong opval= (ulonglong) NO_IVAL; + + if (!options) + return idef; + else if (!stricmp(opname, "Lrecl")) + opval= options->lrecl; + else if (!stricmp(opname, "Elements")) + opval= options->elements; + else if (!stricmp(opname, "Multiple")) + opval= options->multiple; + else if (!stricmp(opname, "Header")) + opval= options->header; + else if (!stricmp(opname, "Quoted")) + opval= options->quoted; + else if (!stricmp(opname, "Ending")) + opval= options->ending; + else if (!stricmp(opname, "Compressed")) + opval= (options->compressed); + + if ((ulonglong) opval == (ulonglong)NO_IVAL) { + PCSZ pv; + + if ((pv = GetListOption(g, opname, options->oplist))) { + // opval = CharToNumber((char*)pv, strlen(pv), ULONGLONG_MAX, false); + return atoi(pv); + } else + return idef; + + } // endif opval + + return (int)opval; +} // end of GetIntegerTableOption + +/****************************************************************************/ +/* Return the table option structure. */ +/****************************************************************************/ +PTOS ha_connect::GetTableOptionStruct(TABLE_SHARE *s) +{ + TABLE_SHARE *tsp= (tshp) ? tshp : (s) ? s : table_share; + + return (tsp && (!tsp->db_plugin || + !stricmp(plugin_name(tsp->db_plugin)->str, "connect") || + !stricmp(plugin_name(tsp->db_plugin)->str, "partition"))) + ? tsp->option_struct : NULL; +} // end of GetTableOptionStruct + +/****************************************************************************/ +/* Return the string eventually formatted with partition name. */ +/****************************************************************************/ +char *ha_connect::GetRealString(PCSZ s) +{ + char *sv; + + if (IsPartitioned() && s && *partname) { + sv= (char*)PlugSubAlloc(xp->g, NULL, 0); + sprintf(sv, s, partname); + PlugSubAlloc(xp->g, NULL, strlen(sv) + 1); + } else + sv= (char*)s; + + return sv; +} // end of GetRealString + +/****************************************************************************/ +/* Return the value of a string option or sdef if not specified. */ +/****************************************************************************/ +PCSZ ha_connect::GetStringOption(PCSZ opname, PCSZ sdef) +{ + PCSZ opval= NULL; + PTOS options= GetTableOptionStruct(); + + if (!stricmp(opname, "Connect")) { + LEX_CSTRING cnc= (tshp) ? tshp->connect_string + : table->s->connect_string; + + if (cnc.length) + opval= strz(xp->g, cnc); + else + opval= GetListOption(xp->g, opname, options->oplist); + + } else if (!stricmp(opname, "Query_String")) { +// This escapes everything and returns a wrong query +// opval= thd_query_string(table->in_use)->str; + opval= (PCSZ)PlugSubAlloc(xp->g, NULL, + thd_query_string(table->in_use)->length + 1); + strcpy((char*)opval, thd_query_string(table->in_use)->str); +// sprintf((char*)opval, "%s", thd_query_string(table->in_use)->str); + } else if (!stricmp(opname, "Partname")) + opval= partname; + else if (!stricmp(opname, "Table_charset")) { + const CHARSET_INFO *chif= (tshp) ? tshp->table_charset + : table->s->table_charset; + + if (chif) + opval= (char*)chif->csname; + + } else + opval= GetStringTableOption(xp->g, options, opname, NULL); + + if (opval && (!stricmp(opname, "connect") + || !stricmp(opname, "tabname") + || !stricmp(opname, "filename") + || !stricmp(opname, "optname") + || !stricmp(opname, "entry"))) + opval= GetRealString(opval); + + if (!opval) { + if (sdef && !strcmp(sdef, "*")) { + // Return the handler default value + if (!stricmp(opname, "Dbname") || !stricmp(opname, "Database")) + opval= (char*)GetDBName(NULL); // Current database + else if (!stricmp(opname, "Type")) // Default type + opval= (!options) ? NULL : + (options->srcdef) ? (char*)"MYSQL" : + (options->tabname) ? (char*)"PROXY" : (char*)"DOS"; + else if (!stricmp(opname, "User")) // Connected user + opval= (char *) "root"; + else if (!stricmp(opname, "Host")) // Connected user host + opval= (char *) "localhost"; + else + opval= sdef; // Caller default + + } else + opval= sdef; // Caller default + + } // endif !opval + + return opval; +} // end of GetStringOption + +/****************************************************************************/ +/* Return the value of a Boolean option or bdef if not specified. */ +/****************************************************************************/ +bool ha_connect::GetBooleanOption(PCSZ opname, bool bdef) +{ + bool opval; + PTOS options= GetTableOptionStruct(); + + if (!stricmp(opname, "View")) + opval= (tshp) ? tshp->is_view : table_share->is_view; + else + opval= GetBooleanTableOption(xp->g, options, opname, bdef); + + return opval; +} // end of GetBooleanOption + +/****************************************************************************/ +/* Set the value of the opname option (does not work for oplist options) */ +/* Currently used only to set the Sepindex value. */ +/****************************************************************************/ +bool ha_connect::SetBooleanOption(PCSZ opname, bool b) +{ + PTOS options= GetTableOptionStruct(); + + if (!options) + return true; + + if (!stricmp(opname, "SepIndex")) + options->sepindex= b; + else + return true; + + return false; +} // end of SetBooleanOption + +/****************************************************************************/ +/* Return the value of an integer option or NO_IVAL if not specified. */ +/****************************************************************************/ +int ha_connect::GetIntegerOption(PCSZ opname) +{ + int opval; + PTOS options= GetTableOptionStruct(); + TABLE_SHARE *tsp= (tshp) ? tshp : table_share; + + if (!stricmp(opname, "Avglen")) + opval= (int)tsp->avg_row_length; + else if (!stricmp(opname, "Estimate")) + opval= (int)tsp->max_rows; + else + opval= GetIntegerTableOption(xp->g, options, opname, NO_IVAL); + + return opval; +} // end of GetIntegerOption + +/****************************************************************************/ +/* Set the value of the opname option (does not work for oplist options) */ +/* Currently used only to set the Lrecl value. */ +/****************************************************************************/ +bool ha_connect::SetIntegerOption(PCSZ opname, int n) +{ + PTOS options= GetTableOptionStruct(); + + if (!options) + return true; + + if (!stricmp(opname, "Lrecl")) + options->lrecl= n; + else if (!stricmp(opname, "Elements")) + options->elements= n; +//else if (!stricmp(opname, "Estimate")) +// options->estimate= n; + else if (!stricmp(opname, "Multiple")) + options->multiple= n; + else if (!stricmp(opname, "Header")) + options->header= n; + else if (!stricmp(opname, "Quoted")) + options->quoted= n; + else if (!stricmp(opname, "Ending")) + options->ending= n; + else if (!stricmp(opname, "Compressed")) + options->compressed= n; + else + return true; +//else if (options->oplist) +// SetListOption(opname, options->oplist, n); + + return false; +} // end of SetIntegerOption + +/****************************************************************************/ +/* Return a field option structure. */ +/****************************************************************************/ +PFOS ha_connect::GetFieldOptionStruct(Field *fdp) +{ + return fdp->option_struct; +} // end of GetFildOptionStruct + +/****************************************************************************/ +/* Returns the column description structure used to make the column. */ +/****************************************************************************/ +void *ha_connect::GetColumnOption(PGLOBAL g, void *field, PCOLINFO pcf) +{ + const char *cp; + char *chset, v= 0; + ha_field_option_struct *fop; + Field* fp; + Field* *fldp; + + // Double test to be on the safe side + if (!table) + return NULL; + + // Find the column to describe + if (field) { + fldp= (Field**)field; + fldp++; + } else + fldp= (tshp) ? tshp->field : table->field; + + if (!fldp || !(fp= *fldp)) + return NULL; + + // Get the CONNECT field options structure + fop= GetFieldOptionStruct(fp); + pcf->Flags= 0; + + // Now get column information + pcf->Name= (char*)fp->field_name.str; + chset = (char*)fp->charset()->name; + + if (fop && fop->special) { + pcf->Fieldfmt= (char*)fop->special; + pcf->Flags= U_SPECIAL; + return fldp; + } // endif special + + pcf->Scale= 0; + pcf->Opt= (fop) ? (int)fop->opt : 0; + + if (fp->field_length >= 0) { + pcf->Length= fp->field_length; + + // length is bytes for Connect, not characters + if (!strnicmp(chset, "utf8", 4)) + pcf->Length /= 3; + + } else + pcf->Length= 256; // BLOB? + + pcf->Precision= pcf->Length; + + if (fop) { + pcf->Offset= (int)fop->offset; + pcf->Freq= (int)fop->freq; + pcf->Datefmt= (char*)fop->dateformat; + pcf->Fieldfmt= fop->fieldformat ? (char*)fop->fieldformat + : fop->jsonpath ? (char*)fop->jsonpath : (char*)fop->xmlpath; + } else { + pcf->Offset= -1; + pcf->Freq= 0; + pcf->Datefmt= NULL; + pcf->Fieldfmt= NULL; + } // endif fop + + if (!strcmp(chset, "binary")) + v = 'B'; // Binary string + + switch (fp->type()) { + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + pcf->Flags |= U_VAR; + // fall through + default: + pcf->Type= MYSQLtoPLG(fp->type(), &v); + break; + } // endswitch SQL type + + switch (pcf->Type) { + case TYPE_STRING: + case TYPE_BIN: + // Do something for case + cp= chset; + + // Find if collation name ends by _ci + if (!strcmp(cp + strlen(cp) - 3, "_ci")) { + pcf->Scale= 1; // Case insensitive + pcf->Opt= 0; // Prevent index opt until it is safe + } // endif ci + + break; + case TYPE_DOUBLE: + pcf->Scale= MY_MAX(MY_MIN(fp->decimals(), ((unsigned)pcf->Length - 2)), 0); + break; + case TYPE_DECIM: + pcf->Precision= ((Field_new_decimal*)fp)->precision; + pcf->Length= pcf->Precision; + pcf->Scale= fp->decimals(); + break; + case TYPE_DATE: + // Field_length is only used for DATE columns + if (fop && fop->fldlen) + pcf->Length= (int)fop->fldlen; + else { + int len; + + if (pcf->Datefmt) { + // Find the (max) length produced by the date format + char buf[256]; + PGLOBAL g= GetPlug(table->in_use, xp); + PDTP pdtp= MakeDateFormat(g, pcf->Datefmt, false, true, 0); + struct tm datm; + bzero(&datm, sizeof(datm)); + datm.tm_mday= 12; + datm.tm_mon= 11; + datm.tm_year= 112; + mktime(&datm); // set other fields get proper day name + len= strftime(buf, 256, pdtp->OutFmt, &datm); + } else + len= 0; + + // 11 is for signed numeric representation of the date + pcf->Length= (len) ? len : 11; + } // endelse + + // For Value setting + pcf->Precision= MY_MAX(pcf->Precision, pcf->Length); + break; + default: + break; + } // endswitch type + + if (fp->flags & UNSIGNED_FLAG) + pcf->Flags |= U_UNSIGNED; + + if (fp->flags & ZEROFILL_FLAG) + pcf->Flags |= U_ZEROFILL; + + // This is used to skip null bit + if (fp->real_maybe_null()) + pcf->Flags |= U_NULLS; + + // Mark virtual columns as such + if (fp->vcol_info && !fp->stored_in_db) + pcf->Flags |= U_VIRTUAL; + + pcf->Key= 0; // Not used when called from MySQL + + // Get the comment if any + if (fp->comment.str && fp->comment.length) + pcf->Remark= strz(g, fp->comment); + else + pcf->Remark= NULL; + + return fldp; +} // end of GetColumnOption + +/****************************************************************************/ +/* Return an index option structure. */ +/****************************************************************************/ +PXOS ha_connect::GetIndexOptionStruct(KEY *kp) +{ + return kp->option_struct; +} // end of GetIndexOptionStruct + +/****************************************************************************/ +/* Return a Boolean index option or false if not specified. */ +/****************************************************************************/ +bool ha_connect::GetIndexOption(KEY *kp, PCSZ opname) +{ + bool opval= false; + PXOS options= GetIndexOptionStruct(kp); + + if (options) { + if (!stricmp(opname, "Dynamic")) + opval= options->dynamic; + else if (!stricmp(opname, "Mapped")) + opval= options->mapped; + + } else if (kp->comment.str && kp->comment.length) { + PCSZ pv, oplist= strz(xp->g, kp->comment); + + if ((pv= GetListOption(xp->g, opname, oplist))) + opval= (!*pv || *pv == 'y' || *pv == 'Y' || atoi(pv) != 0); + + } // endif comment + + return opval; +} // end of GetIndexOption + +/****************************************************************************/ +/* Returns the index description structure used to make the index. */ +/****************************************************************************/ +bool ha_connect::IsUnique(uint n) +{ + return (table->key_info[n].flags & HA_NOSAME) != 0; +} // end of IsUnique + +/****************************************************************************/ +/* Returns the index description structure used to make the index. */ +/****************************************************************************/ +PIXDEF ha_connect::GetIndexInfo(TABLE_SHARE *s) +{ + char *name, *pn; + bool unique; + PIXDEF xdp, pxd=NULL, toidx= NULL; + PKPDEF kpp, pkp; + KEY kp; + PGLOBAL& g= xp->g; + + if (!s) + s= table->s; + + for (int n= 0; (unsigned)n < s->keynames.count; n++) { + if (trace(1)) + htrc("Getting created index %d info\n", n + 1); + + // Find the index to describe + kp= s->key_info[n]; + + // Now get index information + pn= (char*)s->keynames.type_names[n]; + name= PlugDup(g, pn); + unique= (kp.flags & 1) != 0; + pkp= NULL; + + // Allocate the index description block + xdp= new(g) INDEXDEF(name, unique, n); + + // Get the the key parts info + for (int k= 0; (unsigned)k < kp.user_defined_key_parts; k++) { + pn= (char*)kp.key_part[k].field->field_name.str; + name= PlugDup(g, pn); + + // Allocate the key part description block + kpp= new(g) KPARTDEF(name, k + 1); + kpp->SetKlen(kp.key_part[k].length); + +#if 0 // NIY + // Index on auto increment column can be an XXROW index + if (kp.key_part[k].field->flags & AUTO_INCREMENT_FLAG && + kp.uder_defined_key_parts == 1) { + char *type= GetStringOption("Type", "DOS"); + TABTYPE typ= GetTypeID(type); + + xdp->SetAuto(IsTypeFixed(typ)); + } // endif AUTO_INCREMENT +#endif // 0 + + if (pkp) + pkp->SetNext(kpp); + else + xdp->SetToKeyParts(kpp); + + pkp= kpp; + } // endfor k + + xdp->SetNParts(kp.user_defined_key_parts); + xdp->Dynamic= GetIndexOption(&kp, "Dynamic"); + xdp->Mapped= GetIndexOption(&kp, "Mapped"); + + if (pxd) + pxd->SetNext(xdp); + else + toidx= xdp; + + pxd= xdp; + } // endfor n + + return toidx; +} // end of GetIndexInfo + +/****************************************************************************/ +/* Returns the index description structure used to make the index. */ +/****************************************************************************/ +bool ha_connect::CheckVirtualIndex(TABLE_SHARE *s) +{ + + char *rid; + KEY kp; + Field *fp; + PGLOBAL& g= xp->g; + + if (!s) + s= table->s; + + for (int n= 0; (unsigned)n < s->keynames.count; n++) { + kp= s->key_info[n]; + + // Now get index information + + // Get the the key parts info + for (int k= 0; (unsigned)k < kp.user_defined_key_parts; k++) { + fp= kp.key_part[k].field; + rid= (fp->option_struct) ? fp->option_struct->special : NULL; + + if (!rid || (stricmp(rid, "ROWID") && stricmp(rid, "ROWNUM"))) { + strcpy(g->Message, "Invalid virtual index"); + return true; + } // endif rowid + + } // endfor k + + } // endfor n + + return false; +} // end of CheckVirtualIndex + +bool ha_connect::IsPartitioned(void) +{ +#ifdef WITH_PARTITION_STORAGE_ENGINE + if (tshp) + return tshp->partition_info_str_len > 0; + else if (table && table->part_info) + return true; + else +#endif + return false; + +} // end of IsPartitioned + +PCSZ ha_connect::GetDBName(PCSZ name) +{ + return (name) ? name : table->s->db.str; +} // end of GetDBName + +const char *ha_connect::GetTableName(void) +{ + const char *path= tshp ? tshp->path.str : table_share->path.str; + const char *name= strrchr(path, slash); + return name ? name + 1 : path; +} // end of GetTableName + +char *ha_connect::GetPartName(void) +{ + return (IsPartitioned()) ? partname : (char*)GetTableName(); +} // end of GetTableName + +#if 0 +/****************************************************************************/ +/* Returns the column real or special name length of a field. */ +/****************************************************************************/ +int ha_connect::GetColNameLen(Field *fp) +{ + int n; + PFOS fop= GetFieldOptionStruct(fp); + + // Now get the column name length + if (fop && fop->special) + n= strlen(fop->special) + 1; + else + n= fp->field_name.length; + + return n; +} // end of GetColNameLen + +/****************************************************************************/ +/* Returns the column real or special name of a field. */ +/****************************************************************************/ +char *ha_connect::GetColName(Field *fp) +{ + PFOS fop= GetFieldOptionStruct(fp); + + return (fop && fop->special) ? fop->special : (char*)fp->field_name.str; +} // end of GetColName + +/****************************************************************************/ +/* Adds the column real or special name of a field to a string. */ +/****************************************************************************/ +void ha_connect::AddColName(char *cp, Field *fp) +{ + PFOS fop= GetFieldOptionStruct(fp); + + // Now add the column name + if (fop && fop->special) + // The prefix * mark the column as "special" + strcat(strcpy(cp, "*"), strupr(fop->special)); + else + strcpy(cp, fp->field_name.str); + +} // end of AddColName +#endif // 0 + +/***********************************************************************/ +/* This function sets the current database path. */ +/***********************************************************************/ +bool ha_connect::SetDataPath(PGLOBAL g, PCSZ path) +{ + return (!(datapath= SetPath(g, path))); +} // end of SetDataPath + +/****************************************************************************/ +/* Get the table description block of a CONNECT table. */ +/****************************************************************************/ +PTDB ha_connect::GetTDB(PGLOBAL g) +{ + const char *table_name; + PTDB tp; + + // Double test to be on the safe side + if (!g || !table) + return NULL; + + table_name= GetTableName(); + + if (!xp->CheckQuery(valid_query_id) && tdbp + && !stricmp(tdbp->GetName(), table_name) + && (tdbp->GetMode() == xmod + || (tdbp->GetMode() == MODE_READ && xmod == MODE_READX) + || tdbp->GetAmType() == TYPE_AM_XML)) { + tp= tdbp; + tp->SetMode(xmod); + } else if ((tp= CntGetTDB(g, table_name, xmod, this))) { + valid_query_id= xp->last_query_id; +// tp->SetMode(xmod); + } else + htrc("GetTDB: %s\n", g->Message); + + return tp; +} // end of GetTDB + +/****************************************************************************/ +/* Open a CONNECT table, restricting column list if cols is true. */ +/****************************************************************************/ +int ha_connect::OpenTable(PGLOBAL g, bool del) +{ + bool rc= false; + char *c1= NULL, *c2=NULL; + + // Double test to be on the safe side + if (!g || !table) { + htrc("OpenTable logical error; g=%p table=%p\n", g, table); + return HA_ERR_INITIALIZATION; + } // endif g + + if (!(tdbp= GetTDB(g))) + return RC_FX; + else if (tdbp->IsReadOnly()) + switch (xmod) { + case MODE_WRITE: + case MODE_INSERT: + case MODE_UPDATE: + case MODE_DELETE: + strcpy(g->Message, MSG(READ_ONLY)); + return HA_ERR_TABLE_READONLY; + default: + break; + } // endswitch xmode + + // g->More is 1 when executing commands from triggers + if (!g->More && (xmod != MODE_INSERT + || tdbp->GetAmType() == TYPE_AM_MYSQL + || tdbp->GetAmType() == TYPE_AM_ODBC + || tdbp->GetAmType() == TYPE_AM_JDBC)) { + // Get the list of used fields (columns) + char *p; + unsigned int k1, k2, n1, n2; + Field* *field; + Field* fp; + MY_BITMAP *map= (xmod == MODE_INSERT) ? table->write_set : table->read_set; + MY_BITMAP *ump= (xmod == MODE_UPDATE) ? table->write_set : NULL; + + k1= k2= 0; + n1= n2= 1; // 1 is space for final null character + + for (field= table->field; (fp= *field); field++) { + if (bitmap_is_set(map, fp->field_index)) { + n1+= (fp->field_name.length + 1); + k1++; + } // endif + + if (ump && bitmap_is_set(ump, fp->field_index)) { + n2+= (fp->field_name.length + 1); + k2++; + } // endif + + } // endfor field + + if (k1) { + p= c1= (char*)PlugSubAlloc(g, NULL, n1); + + for (field= table->field; (fp= *field); field++) + if (bitmap_is_set(map, fp->field_index)) { + strcpy(p, fp->field_name.str); + p+= (fp->field_name.length + 1); + } // endif used field + + *p= '\0'; // mark end of list + } // endif k1 + + if (k2) { + p= c2= (char*)PlugSubAlloc(g, NULL, n2); + + for (field= table->field; (fp= *field); field++) + if (bitmap_is_set(ump, fp->field_index)) { + strcpy(p, fp->field_name.str); + + if (part_id && bitmap_is_set(part_id, fp->field_index)) { + // Trying to update a column used for partitioning + // This cannot be currently done because it may require + // a row to be moved in another partition. + sprintf(g->Message, + "Cannot update column %s because it is used for partitioning", + p); + return HA_ERR_INTERNAL_ERROR; + } // endif part_id + + p+= (strlen(p) + 1); + } // endif used field + + *p= '\0'; // mark end of list + } // endif k2 + + } // endif xmod + + // Open the table + if (!(rc= CntOpenTable(g, tdbp, xmod, c1, c2, del, this))) { + istable= true; +// strmake(tname, table_name, sizeof(tname)-1); + +#ifdef NOT_USED_VARIABLE + // We may be in a create index query + if (xmod == MODE_ANY && *tdbp->GetName() != '#') { + // The current indexes + PIXDEF oldpix= GetIndexInfo(); + } // endif xmod +#endif + + } else + htrc("OpenTable: %s\n", g->Message); + + if (rc) { + tdbp= NULL; + valid_info= false; + } // endif rc + + return (rc) ? HA_ERR_INITIALIZATION : 0; +} // end of OpenTable + + +/****************************************************************************/ +/* CheckColumnList: check that all bitmap columns do exist. */ +/****************************************************************************/ +bool ha_connect::CheckColumnList(PGLOBAL g) +{ + // Check the list of used fields (columns) + bool brc= false; + PCOL colp; + Field* *field; + Field* fp; + MY_BITMAP *map= table->read_set; + + try { + for (field= table->field; (fp= *field); field++) + if (bitmap_is_set(map, fp->field_index)) { + if (!(colp= tdbp->ColDB(g, (PSZ)fp->field_name.str, 0))) { + sprintf(g->Message, "Column %s not found in %s", + fp->field_name.str, tdbp->GetName()); + throw 1; + } // endif colp + + if ((brc= colp->InitValue(g))) + throw 2; + + colp->AddColUse(U_P); // For PLG tables + } // endif + + } catch (int n) { + if (trace(1)) + htrc("Exception %d: %s\n", n, g->Message); + brc= true; + } catch (const char *msg) { + strcpy(g->Message, msg); + brc= true; + } // end catch + + return brc; +} // end of CheckColumnList + + +/****************************************************************************/ +/* IsOpened: returns true if the table is already opened. */ +/****************************************************************************/ +bool ha_connect::IsOpened(void) +{ + return (!xp->CheckQuery(valid_query_id) && tdbp + && tdbp->GetUse() == USE_OPEN); +} // end of IsOpened + + +/****************************************************************************/ +/* Close a CONNECT table. */ +/****************************************************************************/ +int ha_connect::CloseTable(PGLOBAL g) +{ + int rc= CntCloseTable(g, tdbp, nox, abort); + tdbp= NULL; + sdvalin1= sdvalin2= sdvalin3= sdvalin4= NULL; + sdvalout=NULL; + valid_info= false; + indexing= -1; + nox= true; + abort= false; + return rc; +} // end of CloseTable + + +/***********************************************************************/ +/* Make a pseudo record from current row values. Specific to MySQL. */ +/***********************************************************************/ +int ha_connect::MakeRecord(char *buf) +{ + PCSZ fmt; + char *p, val[32]; + int rc= 0; + Field* *field; + Field *fp; + CHARSET_INFO *charset= tdbp->data_charset(); +//MY_BITMAP readmap; + MY_BITMAP *map; + PVAL value; + PCOL colp= NULL; + DBUG_ENTER("ha_connect::MakeRecord"); + + if (trace(2)) + htrc("Maps: read=%08X write=%08X defr=%08X defw=%08X\n", + *table->read_set->bitmap, *table->write_set->bitmap, + *table->def_read_set.bitmap, *table->def_write_set.bitmap); + + // Avoid asserts in field::store() for columns that are not updated + MY_BITMAP *org_bitmap= dbug_tmp_use_all_columns(table, &table->write_set); + + // This is for variable_length rows + memset(buf, 0, table->s->null_bytes); + + // When sorting read_set selects all columns, so we use def_read_set + map= (MY_BITMAP *)&table->def_read_set; + + // Make the pseudo record from field values + for (field= table->field; *field && !rc; field++) { + fp= *field; + + if (fp->vcol_info && !fp->stored_in_db) + continue; // This is a virtual column + + if (bitmap_is_set(map, fp->field_index) || alter) { + // This is a used field, fill the buffer with value + for (colp= tdbp->GetColumns(); colp; colp= colp->GetNext()) + if ((!mrr || colp->GetKcol()) && + !stricmp(colp->GetName(), fp->field_name.str)) + break; + + if (!colp) { + if (mrr) + continue; + + htrc("Column %s not found\n", fp->field_name.str); + dbug_tmp_restore_column_map(&table->write_set, org_bitmap); + DBUG_RETURN(HA_ERR_WRONG_IN_RECORD); + } // endif colp + + value= colp->GetValue(); + p= NULL; + + // All this was better optimized + if (!value->IsNull()) { + switch (value->GetType()) { + case TYPE_DATE: + if (!sdvalout) + sdvalout= AllocateValue(xp->g, TYPE_STRING, 20); + + switch (fp->type()) { + case MYSQL_TYPE_DATE: + fmt= "%Y-%m-%d"; + break; + case MYSQL_TYPE_TIME: + fmt= "%H:%M:%S"; + break; + case MYSQL_TYPE_YEAR: + fmt= "%Y"; + break; + default: + fmt= "%Y-%m-%d %H:%M:%S"; + break; + } // endswitch type + + // Get date in the format required by MySQL fields + value->FormatValue(sdvalout, fmt); + p= sdvalout->GetCharValue(); + rc= fp->store(p, strlen(p), charset, CHECK_FIELD_WARN); + break; + case TYPE_STRING: + case TYPE_DECIM: + p= value->GetCharString(val); + charset= tdbp->data_charset(); + rc= fp->store_text(p, strlen(p), charset, CHECK_FIELD_WARN); + break; + case TYPE_BIN: + p= value->GetCharValue(); + charset= &my_charset_bin; + rc= fp->store(p, value->GetSize(), charset, CHECK_FIELD_WARN); + break; + case TYPE_DOUBLE: + rc= fp->store(value->GetFloatValue()); + break; + default: + rc= fp->store(value->GetBigintValue(), value->IsUnsigned()); + break; + } // endswitch Type + + // Store functions returns 1 on overflow and -1 on fatal error + if (rc > 0) { + char buf[256]; + THD *thd= ha_thd(); + + sprintf(buf, "Out of range value %.140s for column '%s' at row %ld", + value->GetCharString(val), + fp->field_name.str, + thd->get_stmt_da()->current_row_for_warning()); + + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, buf); + DBUG_PRINT("MakeRecord", ("%s", buf)); + rc= 0; + } else if (rc < 0) + rc= HA_ERR_WRONG_IN_RECORD; + + fp->set_notnull(); + } else + fp->set_null(); + + } // endif bitmap + + } // endfor field + + // This is sometimes required for partition tables because the buf + // can be different from the table->record[0] buffer + if (buf != (char*)table->record[0]) + memcpy(buf, table->record[0], table->s->stored_rec_length); + + // This is copied from ha_tina and is necessary to avoid asserts + dbug_tmp_restore_column_map(&table->write_set, org_bitmap); + DBUG_RETURN(rc); +} // end of MakeRecord + + +/***********************************************************************/ +/* Set row values from a MySQL pseudo record. Specific to MySQL. */ +/***********************************************************************/ +int ha_connect::ScanRecord(PGLOBAL g, const uchar *) +{ + char attr_buffer[1024]; + char data_buffer[1024]; + PCSZ fmt; + int rc= 0; + PCOL colp; + PVAL value, sdvalin; + Field *fp; +//PTDBASE tp= (PTDBASE)tdbp; + String attribute(attr_buffer, sizeof(attr_buffer), + table->s->table_charset); + MY_BITMAP *bmap= dbug_tmp_use_all_columns(table, &table->read_set); + const CHARSET_INFO *charset= tdbp->data_charset(); + String data_charset_value(data_buffer, sizeof(data_buffer), charset); + + // Scan the pseudo record for field values and set column values + for (Field **field=table->field ; *field ; field++) { + fp= *field; + + if ((fp->vcol_info && !fp->stored_in_db) || + fp->option_struct->special) + continue; // Is a virtual column possible here ??? + + if ((xmod == MODE_INSERT && tdbp->GetAmType() != TYPE_AM_MYSQL + && tdbp->GetAmType() != TYPE_AM_ODBC + && tdbp->GetAmType() != TYPE_AM_JDBC) || + bitmap_is_set(table->write_set, fp->field_index)) { + for (colp= tdbp->GetSetCols(); colp; colp= colp->GetNext()) + if (!stricmp(colp->GetName(), fp->field_name.str)) + break; + + if (!colp) { + htrc("Column %s not found\n", fp->field_name.str); + rc= HA_ERR_WRONG_IN_RECORD; + goto err; + } else + value= colp->GetValue(); + + // This is a used field, fill the value from the row buffer + // All this could be better optimized + if (fp->is_null()) { + if (colp->IsNullable()) + value->SetNull(true); + + value->Reset(); + } else switch (value->GetType()) { + case TYPE_DOUBLE: + value->SetValue(fp->val_real()); + break; + case TYPE_DATE: + // Get date in the format produced by MySQL fields + switch (fp->type()) { + case MYSQL_TYPE_DATE: + if (!sdvalin2) { + sdvalin2= (DTVAL*)AllocateValue(xp->g, TYPE_DATE, 19); + fmt= "YYYY-MM-DD"; + ((DTVAL*)sdvalin2)->SetFormat(g, fmt, strlen(fmt)); + } // endif sdvalin1 + + sdvalin= sdvalin2; + break; + case MYSQL_TYPE_TIME: + if (!sdvalin3) { + sdvalin3= (DTVAL*)AllocateValue(xp->g, TYPE_DATE, 19); + fmt= "hh:mm:ss"; + ((DTVAL*)sdvalin3)->SetFormat(g, fmt, strlen(fmt)); + } // endif sdvalin1 + + sdvalin= sdvalin3; + break; + case MYSQL_TYPE_YEAR: + if (!sdvalin4) { + sdvalin4= (DTVAL*)AllocateValue(xp->g, TYPE_DATE, 19); + fmt= "YYYY"; + ((DTVAL*)sdvalin4)->SetFormat(g, fmt, strlen(fmt)); + } // endif sdvalin1 + + sdvalin= sdvalin4; + break; + default: + if (!sdvalin1) { + sdvalin1= (DTVAL*)AllocateValue(xp->g, TYPE_DATE, 19); + fmt= "YYYY-MM-DD hh:mm:ss"; + ((DTVAL*)sdvalin1)->SetFormat(g, fmt, strlen(fmt)); + } // endif sdvalin1 + + sdvalin= sdvalin1; + } // endswitch type + + sdvalin->SetNullable(colp->IsNullable()); + fp->val_str(&attribute); + sdvalin->SetValue_psz(attribute.c_ptr_safe()); + value->SetValue_pval(sdvalin); + break; + default: + fp->val_str(&attribute); + + if (charset != &my_charset_bin) { + // Convert from SQL field charset to DATA_CHARSET + uint cnv_errors; + + data_charset_value.copy(attribute.ptr(), attribute.length(), + attribute.charset(), charset, &cnv_errors); + value->SetValue_psz(data_charset_value.c_ptr_safe()); + } else + value->SetValue_psz(attribute.c_ptr_safe()); + + break; + } // endswitch Type + +#ifdef NEWCHANGE + } else if (xmod == MODE_UPDATE) { + PCOL cp; + + for (cp= tdbp->GetColumns(); cp; cp= cp->GetNext()) + if (!stricmp(colp->GetName(), cp->GetName())) + break; + + if (!cp) { + rc= HA_ERR_WRONG_IN_RECORD; + goto err; + } // endif cp + + value->SetValue_pval(cp->GetValue()); + } else // mode Insert + value->Reset(); +#else + } // endif bitmap_is_set +#endif + + } // endfor field + + err: + dbug_tmp_restore_column_map(&table->read_set, bmap); + return rc; +} // end of ScanRecord + + +/***********************************************************************/ +/* Check change in index column. Specific to MySQL. */ +/* Should be elaborated to check for real changes. */ +/***********************************************************************/ +int ha_connect::CheckRecord(PGLOBAL g, const uchar *, const uchar *newbuf) +{ + return ScanRecord(g, newbuf); +} // end of dummy CheckRecord + + +/***********************************************************************/ +/* Return true if this field is used in current indexing. */ +/***********************************************************************/ +bool ha_connect::IsIndexed(Field *fp) +{ + if (active_index < MAX_KEY) { + KEY_PART_INFO *kpart; + KEY *kfp= &table->key_info[active_index]; + uint rem= kfp->user_defined_key_parts; + + for (kpart= kfp->key_part; rem; rem--, kpart++) + if (kpart->field == fp) + return true; + + } // endif active_index + + return false; +} // end of IsIndexed + + +/***********************************************************************/ +/* Return the where clause for remote indexed read. */ +/***********************************************************************/ +bool ha_connect::MakeKeyWhere(PGLOBAL g, PSTRG qry, OPVAL vop, char q, + const key_range *kr) +{ + const uchar *ptr; +//uint i, rem, len, klen, stlen; + uint i, rem, len, stlen; + bool nq, both, oom; + OPVAL op; + Field *fp; + const key_range *ranges[2]; + MY_BITMAP *old_map; + KEY *kfp; + KEY_PART_INFO *kpart; + + if (active_index == MAX_KEY) + return false; + + ranges[0]= kr; + ranges[1]= (end_range && !eq_range) ? &save_end_range : NULL; + + if (!ranges[0] && !ranges[1]) { + strcpy(g->Message, "MakeKeyWhere: No key"); + return true; + } else + both= ranges[0] && ranges[1]; + + kfp= &table->key_info[active_index]; + old_map= dbug_tmp_use_all_columns(table, &table->write_set); + + for (i= 0; i <= 1; i++) { + if (ranges[i] == NULL) + continue; + + if (both && i > 0) + qry->Append(") AND ("); + else + qry->Append(" WHERE ("); + +// klen= len= ranges[i]->length; + len= ranges[i]->length; + rem= kfp->user_defined_key_parts; + ptr= ranges[i]->key; + + for (kpart= kfp->key_part; rem; rem--, kpart++) { + fp= kpart->field; + stlen= kpart->store_length; + nq= fp->str_needs_quotes(); + + if (kpart != kfp->key_part) + qry->Append(" AND "); + + if (q) { + qry->Append(q); + qry->Append((PSZ)fp->field_name.str); + qry->Append(q); + } else + qry->Append((PSZ)fp->field_name.str); + + switch (ranges[i]->flag) { + case HA_READ_KEY_EXACT: +// op= (stlen >= len || !nq || fp->result_type() != STRING_RESULT) +// ? OP_EQ : OP_LIKE; + op= OP_EQ; + break; + case HA_READ_AFTER_KEY: + op= (stlen >= len || i > 0) ? (i > 0 ? OP_LE : OP_GT) : OP_GE; + break; + case HA_READ_KEY_OR_NEXT: + op= OP_GE; + break; + case HA_READ_BEFORE_KEY: + op= (stlen >= len) ? OP_LT : OP_LE; + break; + case HA_READ_KEY_OR_PREV: + op= OP_LE; + break; + default: + sprintf(g->Message, "cannot handle flag %d", ranges[i]->flag); + goto err; + } // endswitch flag + + qry->Append((PSZ)GetValStr(op, false)); + + if (nq) + qry->Append('\''); + + if (kpart->key_part_flag & HA_VAR_LENGTH_PART) { + String varchar; + uint var_length= uint2korr(ptr); + + varchar.set_quick((char*)ptr + HA_KEY_BLOB_LENGTH, + var_length, &my_charset_bin); + qry->Append(varchar.ptr(), varchar.length(), nq); + } else { + char strbuff[MAX_FIELD_WIDTH]; + String str(strbuff, sizeof(strbuff), kpart->field->charset()), *res; + + res= fp->val_str(&str, ptr); + qry->Append(res->ptr(), res->length(), nq); + } // endif flag + + if (nq) + qry->Append('\''); + + if (stlen >= len) + break; + + len-= stlen; + + /* For nullable columns, null-byte is already skipped before, that is + ptr was incremented by 1. Since store_length still counts null-byte, + we need to subtract 1 from store_length. */ + ptr+= stlen - MY_TEST(kpart->null_bit); + } // endfor kpart + + } // endfor i + + qry->Append(')'); + + if ((oom= qry->IsTruncated())) + strcpy(g->Message, "Out of memory"); + + dbug_tmp_restore_column_map(&table->write_set, old_map); + return oom; + +err: + dbug_tmp_restore_column_map(&table->write_set, old_map); + return true; +} // end of MakeKeyWhere + + +/***********************************************************************/ +/* Return the string representing an operator. */ +/***********************************************************************/ +const char *ha_connect::GetValStr(OPVAL vop, bool neg) +{ + const char *val; + + switch (vop) { + case OP_EQ: + val= "= "; + break; + case OP_NE: + val= " <> "; + break; + case OP_GT: + val= " > "; + break; + case OP_GE: + val= " >= "; + break; + case OP_LT: + val= " < "; + break; + case OP_LE: + val= " <= "; + break; + case OP_IN: + val= (neg) ? " NOT IN (" : " IN ("; + break; + case OP_NULL: + val= (neg) ? " IS NOT NULL" : " IS NULL"; + break; + case OP_LIKE: + val= (neg) ? " NOT LIKE " : " LIKE "; + break; + case OP_XX: + val= (neg) ? " NOT BETWEEN " : " BETWEEN "; + break; + case OP_EXIST: + val= (neg) ? " NOT EXISTS " : " EXISTS "; + break; + case OP_AND: + val= " AND "; + break; + case OP_OR: + val= " OR "; + break; + case OP_NOT: + val= " NOT "; + break; + case OP_CNC: + val= " || "; + break; + case OP_ADD: + val= " + "; + break; + case OP_SUB: + val= " - "; + break; + case OP_MULT: + val= " * "; + break; + case OP_DIV: + val= " / "; + break; + default: + val= " ? "; + break; + } /* endswitch */ + + return val; +} // end of GetValStr + +#if 0 +/***********************************************************************/ +/* Check the WHERE condition and return a CONNECT filter. */ +/***********************************************************************/ +PFIL ha_connect::CheckFilter(PGLOBAL g) +{ + return CondFilter(g, (Item *)pushed_cond); +} // end of CheckFilter +#endif // 0 + +/***********************************************************************/ +/* Check the WHERE condition and return a CONNECT filter. */ +/***********************************************************************/ +PFIL ha_connect::CondFilter(PGLOBAL g, Item *cond) +{ + unsigned int i; + bool ismul= false; + OPVAL vop= OP_XX; + PFIL filp= NULL; + + if (!cond) + return NULL; + + if (trace(1)) + htrc("Cond type=%d\n", cond->type()); + + if (cond->type() == COND::COND_ITEM) { + PFIL fp; + Item_cond *cond_item= (Item_cond *)cond; + + if (trace(1)) + htrc("Cond: Ftype=%d name=%s\n", cond_item->functype(), + cond_item->func_name()); + + switch (cond_item->functype()) { + case Item_func::COND_AND_FUNC: vop= OP_AND; break; + case Item_func::COND_OR_FUNC: vop= OP_OR; break; + default: return NULL; + } // endswitch functype + + List<Item>* arglist= cond_item->argument_list(); + List_iterator<Item> li(*arglist); + Item *subitem; + + for (i= 0; i < arglist->elements; i++) + if ((subitem= li++)) { + if (!(fp= CondFilter(g, subitem))) { + if (vop == OP_OR) + return NULL; + } else + filp= (filp) ? MakeFilter(g, filp, vop, fp) : fp; + + } else + return NULL; + + } else if (cond->type() == COND::FUNC_ITEM) { + unsigned int i; + bool iscol, neg= FALSE; + PCOL colp[2]= {NULL,NULL}; + PPARM pfirst= NULL, pprec= NULL; + POPER pop; + Item_func *condf= (Item_func *)cond; + Item* *args= condf->arguments(); + + if (trace(1)) + htrc("Func type=%d argnum=%d\n", condf->functype(), + condf->argument_count()); + + switch (condf->functype()) { + case Item_func::EQUAL_FUNC: + case Item_func::EQ_FUNC: vop= OP_EQ; break; + case Item_func::NE_FUNC: vop= OP_NE; break; + case Item_func::LT_FUNC: vop= OP_LT; break; + case Item_func::LE_FUNC: vop= OP_LE; break; + case Item_func::GE_FUNC: vop= OP_GE; break; + case Item_func::GT_FUNC: vop= OP_GT; break; + case Item_func::IN_FUNC: vop= OP_IN; /* fall through */ + case Item_func::BETWEEN: + ismul= true; + neg= ((Item_func_opt_neg *)condf)->negated; + break; + default: return NULL; + } // endswitch functype + + pop= (POPER)PlugSubAlloc(g, NULL, sizeof(OPER)); + pop->Name= NULL; + pop->Val=vop; + pop->Mod= 0; + + if (condf->argument_count() < 2) + return NULL; + + for (i= 0; i < condf->argument_count(); i++) { + if (trace(1)) + htrc("Argtype(%d)=%d\n", i, args[i]->type()); + + if (i >= 2 && !ismul) { + if (trace(1)) + htrc("Unexpected arg for vop=%d\n", vop); + + continue; + } // endif i + + if ((iscol= args[i]->type() == COND::FIELD_ITEM)) { + Item_field *pField= (Item_field *)args[i]; + + // IN and BETWEEN clauses should be col VOP list + if (i && ismul) + return NULL; + + if (pField->field->table != table || + !(colp[i]= tdbp->ColDB(g, (PSZ)pField->field->field_name.str, 0))) + return NULL; // Column does not belong to this table + + // These types are not yet implemented (buggy) + switch (pField->field->type()) { + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + return NULL; + default: + break; + } // endswitch type + + if (trace(1)) { + htrc("Field index=%d\n", pField->field->field_index); + htrc("Field name=%s\n", pField->field->field_name.str); + } // endif trace + + } else { + char buff[256]; + String *res, tmp(buff, sizeof(buff), &my_charset_bin); + PPARM pp= (PPARM)PlugSubAlloc(g, NULL, sizeof(PARM)); + + // IN and BETWEEN clauses should be col VOP list + if (!i && (ismul)) + return NULL; + + switch (args[i]->real_type()) { + case COND::CONST_ITEM: + { + Item *pval= (Item *)args[i]; + switch (args[i]->cmp_type()) { + case STRING_RESULT: + res= pval->val_str(&tmp); + pp->Value= PlugSubAllocStr(g, NULL, res->ptr(), res->length()); + pp->Type= (pp->Value) ? TYPE_STRING : TYPE_ERROR; + break; + case INT_RESULT: + pp->Type= TYPE_INT; + pp->Value= PlugSubAlloc(g, NULL, sizeof(int)); + *((int*)pp->Value)= (int)pval->val_int(); + break; + case TIME_RESULT: + pp->Type= TYPE_DATE; + pp->Value= PlugSubAlloc(g, NULL, sizeof(int)); + *((int*)pp->Value)= (int) Temporal_hybrid(pval).to_longlong(); + break; + case REAL_RESULT: + case DECIMAL_RESULT: + pp->Type= TYPE_DOUBLE; + pp->Value= PlugSubAlloc(g, NULL, sizeof(double)); + *((double*)pp->Value)= pval->val_real(); + break; + case ROW_RESULT: + DBUG_ASSERT(0); + return NULL; + } + } + break; + case COND::CACHE_ITEM: // Possible ??? + case COND::NULL_ITEM: // TODO: handle this + default: + return NULL; + } // endswitch type + + if (trace(1)) + htrc("Value type=%hd\n", pp->Type); + + // Append the value to the argument list + if (pprec) + pprec->Next= pp; + else + pfirst= pp; + + pp->Domain= i; + pp->Next= NULL; + pprec= pp; + } // endif type + + } // endfor i + + filp= MakeFilter(g, colp, pop, pfirst, neg); + } else { + if (trace(1)) + htrc("Unsupported condition\n"); + + return NULL; + } // endif's type + + return filp; +} // end of CondFilter + +/***********************************************************************/ +/* Check the WHERE condition and return a MYSQL/ODBC/JDBC/WQL filter. */ +/***********************************************************************/ +PCFIL ha_connect::CheckCond(PGLOBAL g, PCFIL filp, const Item *cond) +{ + AMT tty= filp->Type; + char *body= filp->Body; + char *havg= filp->Having; + unsigned int i; + bool ismul= false, x= (tty == TYPE_AM_MYX || tty == TYPE_AM_XDBC); + bool nonul= ((tty == TYPE_AM_ODBC || tty == TYPE_AM_JDBC) && + (tdbp->GetMode() == MODE_INSERT || tdbp->GetMode() == MODE_DELETE)); + OPVAL vop= OP_XX; + + if (!cond) + return NULL; + + if (trace(1)) + htrc("Cond type=%d\n", cond->type()); + + if (cond->type() == COND::COND_ITEM) { + char *pb0, *pb1, *pb2, *ph0= 0, *ph1= 0, *ph2= 0; + bool bb= false, bh= false; + Item_cond *cond_item= (Item_cond *)cond; + + if (x) + return NULL; + else + pb0= pb1= pb2= ph0= ph1= ph2= NULL; + + if (trace(1)) + htrc("Cond: Ftype=%d name=%s\n", cond_item->functype(), + cond_item->func_name()); + + switch (cond_item->functype()) { + case Item_func::COND_AND_FUNC: vop= OP_AND; break; + case Item_func::COND_OR_FUNC: vop= OP_OR; break; + default: return NULL; + } // endswitch functype + + List<Item>* arglist= cond_item->argument_list(); + List_iterator<Item> li(*arglist); + const Item *subitem; + + pb0= pb1= body + strlen(body); + strcpy(pb0, "("); + pb2= pb1 + 1; + + if (havg) { + ph0= ph1= havg + strlen(havg); + strcpy(ph0, "("); + ph2= ph1 + 1; + } // endif havg + + for (i= 0; i < arglist->elements; i++) + if ((subitem= li++)) { + if (!CheckCond(g, filp, subitem)) { + if (vop == OP_OR || nonul) + return NULL; + else { + *pb2= 0; + if (havg) *ph2= 0; + } // endelse + + } else { + if (filp->Bd) { + pb1= pb2 + strlen(pb2); + strcpy(pb1, GetValStr(vop, false)); + pb2= pb1 + strlen(pb1); + } // endif Bd + + if (filp->Hv) { + ph1= ph2 + strlen(ph2); + strcpy(ph1, GetValStr(vop, false)); + ph2= ph1 + strlen(ph1); + } // endif Hv + + } // endif CheckCond + + bb |= filp->Bd; + bh |= filp->Hv; + filp->Bd= filp->Hv= false; + } else + return NULL; + + if (bb) { + strcpy(pb1, ")"); + filp->Bd= bb; + } else + *pb0= 0; + + if (havg) { + if (bb && bh && vop == OP_OR) { + // Cannot or'ed a where clause with a having clause + bb= bh= 0; + *pb0= 0; + *ph0= 0; + } else if (bh) { + strcpy(ph1, ")"); + filp->Hv= bh; + } else + *ph0= 0; + + } // endif havg + + if (!bb && !bh) + return NULL; + + } else if (cond->type() == COND::FUNC_ITEM) { + unsigned int i; + bool iscol, ishav= false, neg= false; + Item_func *condf= (Item_func *)cond; + Item* *args= condf->arguments(); + + filp->Bd= filp->Hv= false; + + if (trace(1)) + htrc("Func type=%d argnum=%d\n", condf->functype(), + condf->argument_count()); + + switch (condf->functype()) { + case Item_func::EQUAL_FUNC: + case Item_func::EQ_FUNC: vop= OP_EQ; break; + case Item_func::NE_FUNC: vop= OP_NE; break; + case Item_func::LT_FUNC: vop= OP_LT; break; + case Item_func::LE_FUNC: vop= OP_LE; break; + case Item_func::GE_FUNC: vop= OP_GE; break; + case Item_func::GT_FUNC: vop= OP_GT; break; +#if MYSQL_VERSION_ID > 100200 + case Item_func::LIKE_FUNC: + vop = OP_LIKE; + neg= ((Item_func_like*)condf)->negated; + break; +#endif // VERSION_ID > 100200 + case Item_func::ISNOTNULL_FUNC: + neg= true; + // fall through + case Item_func::ISNULL_FUNC: vop= OP_NULL; break; + case Item_func::IN_FUNC: vop= OP_IN; /* fall through */ + case Item_func::BETWEEN: + ismul= true; + neg= ((Item_func_opt_neg *)condf)->negated; + break; + default: return NULL; + } // endswitch functype + + if (condf->argument_count() < 2) + return NULL; + else if (ismul && tty == TYPE_AM_WMI) + return NULL; // Not supported by WQL + + if (x && (neg || !(vop == OP_EQ || vop == OP_IN || vop == OP_NULL))) + return NULL; + + for (i= 0; i < condf->argument_count(); i++) { + if (trace(1)) + htrc("Argtype(%d)=%d\n", i, args[i]->type()); + + if (i >= 2 && !ismul) { + if (trace(1)) + htrc("Unexpected arg for vop=%d\n", vop); + + continue; + } // endif i + + if ((iscol= args[i]->type() == COND::FIELD_ITEM)) { + const char *fnm; + ha_field_option_struct *fop; + Item_field *pField= (Item_field *)args[i]; + + // IN and BETWEEN clauses should be col VOP list + if (i && (x || ismul)) + return NULL; // IN and BETWEEN clauses should be col VOP list + else if (pField->field->table != table) + return NULL; // Field does not belong to this table + else if (tty != TYPE_AM_WMI && IsIndexed(pField->field)) + return NULL; // Will be handled by ReadKey + else + fop= GetFieldOptionStruct(pField->field); + + if (fop && fop->special) { + if (tty == TYPE_AM_TBL && !stricmp(fop->special, "TABID")) + fnm= "TABID"; + else if (tty == TYPE_AM_PLG) + fnm= fop->special; + else + return NULL; + + } else if (tty == TYPE_AM_TBL) { + return NULL; + } else { + bool h; + + fnm= filp->Chk(pField->field->field_name.str, &h); + + if (h && i && !ishav) + return NULL; // Having should be col VOP arg + else + ishav= h; + + } // endif's + + if (trace(1)) { + htrc("Field index=%d\n", pField->field->field_index); + htrc("Field name=%s\n", pField->field->field_name.str); + htrc("Field type=%d\n", pField->field->type()); + htrc("Field_type=%d\n", args[i]->field_type()); + } // endif trace + + strcat((ishav ? havg : body), fnm); + } else if (args[i]->type() == COND::FUNC_ITEM) { + if (tty == TYPE_AM_MYSQL) { + if (!CheckCond(g, filp, args[i])) + return NULL; + + } else + return NULL; + + } else { + char buff[256]; + String *res, tmp(buff, sizeof(buff), &my_charset_bin); + Item *pval= (Item *)args[i]; + Item::Type type= args[i]->real_type(); + + switch (type) { + case COND::CONST_ITEM: + case COND::NULL_ITEM: + case COND::CACHE_ITEM: + break; + default: + return NULL; + } // endswitch type + + if ((res= pval->val_str(&tmp)) == NULL) + return NULL; // To be clarified + + if (trace(1)) + htrc("Value=%.*s\n", res->length(), res->ptr()); + + // IN and BETWEEN clauses should be col VOP list + if (!i && (x || ismul)) + return NULL; + + if (!x) { + const char *p; + char *s= (ishav) ? havg : body; + uint j, k, n; + + // Append the value to the filter + switch (args[i]->field_type()) { + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATETIME: + if (tty == TYPE_AM_ODBC) { + strcat(s, "{ts '"); + strncat(s, res->ptr(), res->length()); + + if (res->length() < 19) + strcat(s, &"1970-01-01 00:00:00"[res->length()]); + + strcat(s, "'}"); + break; + } // endif ODBC + // fall through + case MYSQL_TYPE_DATE: + if (tty == TYPE_AM_ODBC) { + strcat(s, "{d '"); + strcat(strncat(s, res->ptr(), res->length()), "'}"); + break; + } // endif ODBC + // fall through + + case MYSQL_TYPE_TIME: + if (tty == TYPE_AM_ODBC) { + strcat(s, "{t '"); + strcat(strncat(s, res->ptr(), res->length()), "'}"); + break; + } // endif ODBC + // fall through + + case MYSQL_TYPE_VARCHAR: + if (tty == TYPE_AM_ODBC && i) { + switch (args[0]->field_type()) { + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATETIME: + strcat(s, "{ts '"); + strncat(s, res->ptr(), res->length()); + + if (res->length() < 19) + strcat(s, &"1970-01-01 00:00:00"[res->length()]); + + strcat(s, "'}"); + break; + case MYSQL_TYPE_DATE: + strcat(s, "{d '"); + strncat(s, res->ptr(), res->length()); + strcat(s, "'}"); + break; + case MYSQL_TYPE_TIME: + strcat(s, "{t '"); + strncat(s, res->ptr(), res->length()); + strcat(s, "'}"); + break; + default: + j= strlen(s); + s[j++]= '\''; + p= res->ptr(); + n= res->length(); + + for (k= 0; k < n; k++) { + if (p[k] == '\'') + s[j++]= '\''; + + s[j++]= p[k]; + } // endfor k + + s[j++]= '\''; + s[j]= 0; + } // endswitch field type + + } else { + j= strlen(s); + s[j++]= '\''; + p= res->ptr(); + n= res->length(); + + for (k= 0; k < n; k++) { + if (p[k] == '\'') + s[j++]= '\''; + + s[j++]= p[k]; + } // endfor k + + s[j++]= '\''; + s[j]= 0; + } // endif tty + + break; + default: + strncat(s, res->ptr(), res->length()); + } // endswitch field type + + } else { + if (args[i]->field_type() == MYSQL_TYPE_VARCHAR) { + // Add the command to the list + PCMD *ncp, cmdp= new(g) CMD(g, (char*)res->c_ptr()); + + for (ncp= &filp->Cmds; *ncp; ncp= &(*ncp)->Next) ; + + *ncp= cmdp; + } else + return NULL; + + } // endif x + + } // endif's Type + + if (!x) { + char *s= (ishav) ? havg : body; + + if (!i) + strcat(s, GetValStr(vop, neg)); + else if (vop == OP_XX && i == 1) + strcat(s, " AND "); + else if (vop == OP_IN) + strcat(s, (i == condf->argument_count() - 1) ? ")" : ","); + + } // endif x + + } // endfor i + + if (x) + filp->Op= vop; + else if (ishav) + filp->Hv= true; + else + filp->Bd= true; + + } else { + if (trace(1)) + htrc("Unsupported condition\n"); + + return NULL; + } // endif's type + + return filp; +} // end of CheckCond + + + /** + Push condition down to the table handler. + + @param cond Condition to be pushed. The condition tree must not be + modified by the caller. + + @return + The 'remainder' condition that caller must use to filter out records. + NULL means the handler will not return rows that do not match the + passed condition. + + @note + CONNECT handles the filtering only for table types that construct + an SQL or WQL query, but still leaves it to MySQL because only some + parts of the filter may be relevant. + The first suballocate finds the position where the string will be + constructed in the sarea. The second one does make the suballocation + with the proper length. + */ +const COND *ha_connect::cond_push(const COND *cond) +{ + DBUG_ENTER("ha_connect::cond_push"); + + if (tdbp && CondPushEnabled()) { + PGLOBAL& g= xp->g; + AMT tty= tdbp->GetAmType(); + bool x= (tty == TYPE_AM_MYX || tty == TYPE_AM_XDBC); + bool b= (tty == TYPE_AM_WMI || tty == TYPE_AM_ODBC || + tty == TYPE_AM_TBL || tty == TYPE_AM_MYSQL || + tty == TYPE_AM_PLG || tty == TYPE_AM_JDBC || x); + + // This should never happen but is done to avoid crashing + try { + if (b) { + PCFIL filp; + int rc; + + if ((filp= tdbp->GetCondFil()) && tdbp->GetCond() == cond && + filp->Idx == active_index && filp->Type == tty) + goto fin; + + filp= new(g) CONDFIL(active_index, tty); + rc= filp->Init(g, this); + + if (rc == RC_INFO) { + filp->Having= (char*)PlugSubAlloc(g, NULL, 256); + *filp->Having= 0; + } else if (rc == RC_FX) + goto fin; + + filp->Body= (char*)PlugSubAlloc(g, NULL, (x) ? 128 : 0); + *filp->Body= 0; + + if (CheckCond(g, filp, cond)) { + if (filp->Having && strlen(filp->Having) > 255) + goto fin; // Memory collapse + + if (trace(1)) + htrc("cond_push: %s\n", filp->Body); + + tdbp->SetCond(cond); + + if (!x) + PlugSubAlloc(g, NULL, strlen(filp->Body) + 1); + else + cond= NULL; // Does this work? + + tdbp->SetCondFil(filp); + } else if (x && cond) + tdbp->SetCondFil(filp); // Wrong filter + + } else if (tdbp->CanBeFiltered()) { + if (!tdbp->GetCond() || tdbp->GetCond() != cond) { + tdbp->SetFilter(CondFilter(g, (Item *)cond)); + + if (tdbp->GetFilter()) + tdbp->SetCond(cond); + + } // endif cond + + } // endif tty + + } catch (int n) { + if (trace(1)) + htrc("Exception %d: %s\n", n, g->Message); + } catch (const char *msg) { + strcpy(g->Message, msg); + } // end catch + + fin:; + } // endif tdbp + + // Let MySQL do the filtering + DBUG_RETURN(cond); +} // end of cond_push + +/** + Number of rows in table. It will only be called if + (table_flags() & (HA_HAS_RECORDS | HA_STATS_RECORDS_IS_EXACT)) != 0 +*/ +ha_rows ha_connect::records() +{ + if (!valid_info) + info(HA_STATUS_VARIABLE); + + if (tdbp) + return stats.records; + else + return HA_POS_ERROR; + +} // end of records + + +int ha_connect::check(THD* thd, HA_CHECK_OPT* check_opt) +{ + int rc= HA_ADMIN_OK; + PGLOBAL g= ((table && table->in_use) ? GetPlug(table->in_use, xp) : + (xp) ? xp->g : NULL); + DBUG_ENTER("ha_connect::check"); + + if (!g || !table || xmod != MODE_READ) + DBUG_RETURN(HA_ADMIN_INTERNAL_ERROR); + + // Do not close the table if it was opened yet (possible?) + if (IsOpened()) { + if (IsPartitioned() && CheckColumnList(g)) // map can have been changed + rc= HA_ADMIN_CORRUPT; + else if (tdbp->OpenDB(g)) // Rewind table + rc= HA_ADMIN_CORRUPT; + + } else if (xp->CheckQuery(valid_query_id)) { + tdbp= NULL; // Not valid anymore + + if (OpenTable(g, false)) + rc= HA_ADMIN_CORRUPT; + + } else // possible? + DBUG_RETURN(HA_ADMIN_INTERNAL_ERROR); + + if (rc == HA_ADMIN_OK) { + TABTYPE type= GetTypeID(GetStringOption("Type", "*")); + + if (IsFileType(type)) { + if (check_opt->flags & T_MEDIUM) { + // TO DO + do { + if ((rc= CntReadNext(g, tdbp)) == RC_FX) + break; + + } while (rc != RC_EF); + + rc= (rc == RC_EF) ? HA_ADMIN_OK : HA_ADMIN_CORRUPT; + } else if (check_opt->flags & T_EXTEND) { + // TO DO + } // endif's flags + + } // endif file type + + } else + PushWarning(g, thd, 1); + + DBUG_RETURN(rc); +} // end of check + + +/** + Return an error message specific to this handler. + + @param error error code previously returned by handler + @param buf pointer to String where to add error message + + @return + Returns true if this is a temporary error +*/ +bool ha_connect::get_error_message(int error, String* buf) +{ + DBUG_ENTER("ha_connect::get_error_message"); + + if (xp && xp->g) { + PGLOBAL g= xp->g; + + if (trace(1)) + htrc("GEM(%d): %s\n", error, g->Message); + + buf->append(ErrConvString(g->Message, strlen(g->Message), + &my_charset_latin1).ptr()); + } else + buf->append("Cannot retrieve error message"); + + DBUG_RETURN(false); +} // end of get_error_message + +/** + Convert a filename partition name to system +*/ +static char *decode(PGLOBAL g, const char *pn) + { + char *buf= (char*)PlugSubAlloc(g, NULL, strlen(pn) + 1); + uint dummy_errors; + uint32 len= copy_and_convert(buf, strlen(pn) + 1, + system_charset_info, + pn, strlen(pn), + &my_charset_filename, + &dummy_errors); + buf[len]= '\0'; + return buf; + } // end of decode + +/** + @brief + Used for opening tables. The name will be the name of the file. + + @details + A table is opened when it needs to be opened; e.g. when a request comes in + for a SELECT on the table (tables are not open and closed for each request, + they are cached). + + Called from handler.cc by handler::ha_open(). The server opens all tables by + calling ha_open() which then calls the handler specific open(). + + @note + For CONNECT no open can be done here because field information is not yet + updated. >>>>> TO BE CHECKED <<<<< + (Thread information could be get by using 'ha_thd') + + @see + handler::ha_open() in handler.cc +*/ +int ha_connect::open(const char *name, int mode, uint test_if_locked) +{ + int rc= 0; + DBUG_ENTER("ha_connect::open"); + + if (trace(1)) + htrc("open: name=%s mode=%d test=%u\n", name, mode, test_if_locked); + + if (!(share= get_share())) + DBUG_RETURN(1); + + thr_lock_data_init(&share->lock,&lock,NULL); + + // Try to get the user if possible + xp= GetUser(ha_thd(), xp); + PGLOBAL g= (xp) ? xp->g : NULL; + + // Try to set the database environment + if (g) { + rc= (CntCheckDB(g, this, name)) ? (-2) : 0; + + if (g->Mrr) { + // This should only happen for the mrr secondary handler + mrr= true; + g->Mrr= false; + } else + mrr= false; + +#if defined(WITH_PARTITION_STORAGE_ENGINE) + if (table->part_info) { + if (GetStringOption("Filename") || GetStringOption("Tabname") + || GetStringOption("Connect")) { + strncpy(partname, decode(g, strrchr(name, '#') + 1), sizeof(partname) - 1); +// strcpy(partname, table->part_info->curr_part_elem->partition_name); +// part_id= &table->part_info->full_part_field_set; + } else // Inward table + strncpy(partname, strrchr(name, slash) + 1, sizeof(partname) - 1); + + part_id= &table->part_info->full_part_field_set; // Temporary + } // endif part_info +#endif // WITH_PARTITION_STORAGE_ENGINE + } else + rc= HA_ERR_INTERNAL_ERROR; + + DBUG_RETURN(rc); +} // end of open + +/** + @brief + Make the indexes for this table +*/ +int ha_connect::optimize(THD* thd, HA_CHECK_OPT*) +{ + int rc= 0; + PGLOBAL& g= xp->g; + PDBUSER dup= PlgGetUser(g); + + try { + // Ignore error on the opt file + dup->Check &= ~CHK_OPT; + tdbp= GetTDB(g); + dup->Check |= CHK_OPT; + + if (tdbp && !tdbp->IsRemote()) { + bool dop= IsTypeIndexable(GetRealType(NULL)); + bool dox= (tdbp->GetDef()->Indexable() == 1); + + if ((rc= ((PTDBASE)tdbp)->ResetTableOpt(g, dop, dox))) { + if (rc == RC_INFO) { + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + rc= 0; + } else + rc= HA_ERR_CRASHED_ON_USAGE; // Table must be repaired + + } // endif rc + + } else if (!tdbp) + rc= HA_ERR_INTERNAL_ERROR; + + } catch (int n) { + if (trace(1)) + htrc("Exception %d: %s\n", n, g->Message); + rc= HA_ERR_INTERNAL_ERROR; + } catch (const char *msg) { + strcpy(g->Message, msg); + rc= HA_ERR_INTERNAL_ERROR; + } // end catch + + if (rc) + my_message(ER_WARN_DATA_OUT_OF_RANGE, g->Message, MYF(0)); + + return rc; +} // end of optimize + +/** + @brief + Closes a table. + + @details + Called from sql_base.cc, sql_select.cc, and table.cc. In sql_select.cc it is + only used to close up temporary tables or during the process where a + temporary table is converted over to being a myisam table. + + For sql_base.cc look at close_data_tables(). + + @see + sql_base.cc, sql_select.cc and table.cc +*/ +int ha_connect::close(void) +{ + int rc= 0; + DBUG_ENTER("ha_connect::close"); + + // If this is called by a later query, the table may have + // been already closed and the tdbp is not valid anymore. + if (tdbp && xp->last_query_id == valid_query_id) + rc= CloseTable(xp->g); + + DBUG_RETURN(rc); +} // end of close + + +/** + @brief + write_row() inserts a row. No extra() hint is given currently if a bulk load + is happening. buf() is a byte array of data. You can use the field + information to extract the data from the native byte array type. + + @details + Example of this would be: + @code + for (Field **field=table->field ; *field ; field++) + { + ... + } + @endcode + + See ha_tina.cc for an example of extracting all of the data as strings. + ha_berekly.cc has an example of how to store it intact by "packing" it + for ha_berkeley's own native storage type. + + See the note for update_row() on auto_increments and timestamps. This + case also applies to write_row(). + + Called from item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc, + sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc, and sql_update.cc. + + @see + item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc, + sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc and sql_update.cc +*/ +int ha_connect::write_row(const uchar *buf) +{ + int rc= 0; + PGLOBAL& g= xp->g; + DBUG_ENTER("ha_connect::write_row"); + + // This is not tested yet + if (xmod == MODE_ALTER) { + if (IsPartitioned() && GetStringOption("Filename", NULL)) + // Why does this happen now that check_if_supported_inplace_alter is called? + DBUG_RETURN(0); // Alter table on an outward partition table + + xmod= MODE_INSERT; + } else if (xmod == MODE_ANY) + DBUG_RETURN(0); // Probably never met + + // Open the table if it was not opened yet (locked) + if (!IsOpened() || xmod != tdbp->GetMode()) { + if (IsOpened()) + CloseTable(g); + + if ((rc= OpenTable(g))) + DBUG_RETURN(rc); + + } // endif isopened + +#if 0 // AUTO_INCREMENT NIY + if (table->next_number_field && buf == table->record[0]) { + int error; + + if ((error= update_auto_increment())) + return error; + + } // endif nex_number_field +#endif // 0 + + // Set column values from the passed pseudo record + if ((rc= ScanRecord(g, buf))) + DBUG_RETURN(rc); + + // Return result code from write operation + if (CntWriteRow(g, tdbp)) { + DBUG_PRINT("write_row", ("%s", g->Message)); + htrc("write_row: %s\n", g->Message); + rc= HA_ERR_INTERNAL_ERROR; + } else // Table is modified + nox= false; // Indexes to be remade + + DBUG_RETURN(rc); +} // end of write_row + + +/** + @brief + Yes, update_row() does what you expect, it updates a row. old_data will have + the previous row record in it, while new_data will have the newest data in it. + Keep in mind that the server can do updates based on ordering if an ORDER BY + clause was used. Consecutive ordering is not guaranteed. + + @details + Currently new_data will not have an updated auto_increament record, or + and updated timestamp field. You can do these for example by doing: + @code + if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) + table->timestamp_field->set_time(); + if (table->next_number_field && record == table->record[0]) + update_auto_increment(); + @endcode + + Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc. + + @see + sql_select.cc, sql_acl.cc, sql_update.cc and sql_insert.cc +*/ +int ha_connect::update_row(const uchar *old_data, const uchar *new_data) +{ + int rc= 0; + PGLOBAL& g= xp->g; + DBUG_ENTER("ha_connect::update_row"); + + if (trace(2)) + htrc("update_row: old=%s new=%s\n", old_data, new_data); + + // Check values for possible change in indexed column + if ((rc= CheckRecord(g, old_data, new_data))) + DBUG_RETURN(rc); + + if (CntUpdateRow(g, tdbp)) { + DBUG_PRINT("update_row", ("%s", g->Message)); + htrc("update_row CONNECT: %s\n", g->Message); + rc= HA_ERR_INTERNAL_ERROR; + } else + nox= false; // Table is modified + + DBUG_RETURN(rc); +} // end of update_row + + +/** + @brief + This will delete a row. buf will contain a copy of the row to be deleted. + The server will call this right after the current row has been called (from + either a previous rnd_nexT() or index call). + + @details + If you keep a pointer to the last row or can access a primary key it will + make doing the deletion quite a bit easier. Keep in mind that the server does + not guarantee consecutive deletions. ORDER BY clauses can be used. + + Called in sql_acl.cc and sql_udf.cc to manage internal table + information. Called in sql_delete.cc, sql_insert.cc, and + sql_select.cc. In sql_select it is used for removing duplicates + while in insert it is used for REPLACE calls. + + @see + sql_acl.cc, sql_udf.cc, sql_delete.cc, sql_insert.cc and sql_select.cc +*/ +int ha_connect::delete_row(const uchar *) +{ + int rc= 0; + DBUG_ENTER("ha_connect::delete_row"); + + if (CntDeleteRow(xp->g, tdbp, false)) { + rc= HA_ERR_INTERNAL_ERROR; + htrc("delete_row CONNECT: %s\n", xp->g->Message); + } else + nox= false; // To remake indexes + + DBUG_RETURN(rc); +} // end of delete_row + + +/****************************************************************************/ +/* We seem to come here at the begining of an index use. */ +/****************************************************************************/ +int ha_connect::index_init(uint idx, bool sorted) +{ + int rc; + PGLOBAL& g= xp->g; + DBUG_ENTER("index_init"); + + if (trace(1)) + htrc("index_init: this=%p idx=%u sorted=%d\n", this, idx, sorted); + + if (GetIndexType(GetRealType()) == 2) { + if (xmod == MODE_READ) + // This is a remote index + xmod= MODE_READX; + + if (!(rc= rnd_init(0))) { +// if (xmod == MODE_READX) { + active_index= idx; + indexing= IsUnique(idx) ? 1 : 2; +// } else { +// active_index= MAX_KEY; +// indexing= 0; +// } // endif xmod + + } //endif rc + + DBUG_RETURN(rc); + } // endif index type + + if ((rc= rnd_init(0))) + DBUG_RETURN(rc); + + if (locked == 2) { + // Indexes are not updated in lock write mode + active_index= MAX_KEY; + indexing= 0; + DBUG_RETURN(0); + } // endif locked + + indexing= CntIndexInit(g, tdbp, (signed)idx, sorted); + + if (indexing <= 0) { + DBUG_PRINT("index_init", ("%s", g->Message)); + htrc("index_init CONNECT: %s\n", g->Message); + active_index= MAX_KEY; + rc= HA_ERR_INTERNAL_ERROR; + } else if (tdbp->GetKindex()) { + if (((PTDBDOS)tdbp)->GetKindex()->GetNum_K()) { + if (tdbp->GetFtype() != RECFM_NAF) + ((PTDBDOS)tdbp)->GetTxfp()->ResetBuffer(g); + + active_index= idx; +// } else { // Void table +// active_index= MAX_KEY; +// indexing= 0; + } // endif Num + + rc= 0; + } // endif indexing + + if (trace(1)) + htrc("index_init: rc=%d indexing=%d active_index=%d\n", + rc, indexing, active_index); + + DBUG_RETURN(rc); +} // end of index_init + +/****************************************************************************/ +/* We seem to come here at the end of an index use. */ +/****************************************************************************/ +int ha_connect::index_end() +{ + DBUG_ENTER("index_end"); + active_index= MAX_KEY; + ds_mrr.dsmrr_close(); + DBUG_RETURN(rnd_end()); +} // end of index_end + + +/****************************************************************************/ +/* This is internally called by all indexed reading functions. */ +/****************************************************************************/ +int ha_connect::ReadIndexed(uchar *buf, OPVAL op, const key_range *kr) +{ + int rc; + +//statistic_increment(ha_read_key_count, &LOCK_status); + + switch (CntIndexRead(xp->g, tdbp, op, kr, mrr)) { + case RC_OK: + xp->fnd++; + rc= MakeRecord((char*)buf); + break; + case RC_EF: // End of file + rc= HA_ERR_END_OF_FILE; + break; + case RC_NF: // Not found + xp->nfd++; + rc= (op == OP_SAME) ? HA_ERR_END_OF_FILE : HA_ERR_KEY_NOT_FOUND; + break; + default: // Read error + DBUG_PRINT("ReadIndexed", ("%s", xp->g->Message)); + htrc("ReadIndexed: %s\n", xp->g->Message); + rc= HA_ERR_INTERNAL_ERROR; + break; + } // endswitch RC + + if (trace(2)) + htrc("ReadIndexed: op=%d rc=%d\n", op, rc); + + table->status= (rc == RC_OK) ? 0 : STATUS_NOT_FOUND; + return rc; +} // end of ReadIndexed + + +#ifdef NOT_USED +/** + @brief + Positions an index cursor to the index specified in the handle. Fetches the + row if available. If the key value is null, begin at the first key of the + index. +*/ +int ha_connect::index_read_map(uchar *buf, const uchar *key, + key_part_map keypart_map __attribute__((unused)), + enum ha_rkey_function find_flag + __attribute__((unused))) +{ + DBUG_ENTER("ha_connect::index_read"); + DBUG_RETURN(HA_ERR_WRONG_COMMAND); +} +#endif // NOT_USED + + +/****************************************************************************/ +/* This is called by handler::index_read_map. */ +/****************************************************************************/ +int ha_connect::index_read(uchar * buf, const uchar * key, uint key_len, + enum ha_rkey_function find_flag) +{ + int rc; + OPVAL op= OP_XX; + DBUG_ENTER("ha_connect::index_read"); + + switch(find_flag) { + case HA_READ_KEY_EXACT: op= OP_EQ; break; + case HA_READ_AFTER_KEY: op= OP_GT; break; + case HA_READ_KEY_OR_NEXT: op= OP_GE; break; + default: DBUG_RETURN(-1); break; + } // endswitch find_flag + + if (trace(2)) + htrc("%p index_read: op=%d\n", this, op); + + if (indexing > 0) { + start_key.key= key; + start_key.length= key_len; + start_key.flag= find_flag; + start_key.keypart_map= 0; + + rc= ReadIndexed(buf, op, &start_key); + + if (rc == HA_ERR_INTERNAL_ERROR) { + nox= true; // To block making indexes + abort= true; // Don't rename temp file + } // endif rc + + } else + rc= HA_ERR_INTERNAL_ERROR; // HA_ERR_KEY_NOT_FOUND ? + + DBUG_RETURN(rc); +} // end of index_read + + +/** + @brief + Used to read forward through the index. +*/ +int ha_connect::index_next(uchar *buf) +{ + int rc; + DBUG_ENTER("ha_connect::index_next"); + //statistic_increment(ha_read_next_count, &LOCK_status); + + if (indexing > 0) + rc= ReadIndexed(buf, OP_NEXT); + else if (!indexing) + rc= rnd_next(buf); + else + rc= HA_ERR_INTERNAL_ERROR; + + DBUG_RETURN(rc); +} // end of index_next + + +/** + @brief + Used to read backwards through the index. +*/ +int ha_connect::index_prev(uchar *buf) +{ + DBUG_ENTER("ha_connect::index_prev"); + int rc; + + if (indexing > 0) { + rc= ReadIndexed(buf, OP_PREV); + } else + rc= HA_ERR_WRONG_COMMAND; + + DBUG_RETURN(rc); +} // end of index_prev + + +/** + @brief + index_first() asks for the first key in the index. + + @details + Called from opt_range.cc, opt_sum.cc, sql_handler.cc, and sql_select.cc. + + @see + opt_range.cc, opt_sum.cc, sql_handler.cc and sql_select.cc +*/ +int ha_connect::index_first(uchar *buf) +{ + int rc; + DBUG_ENTER("ha_connect::index_first"); + + if (indexing > 0) + rc= ReadIndexed(buf, OP_FIRST); + else if (indexing < 0) + rc= HA_ERR_INTERNAL_ERROR; + else if (CntRewindTable(xp->g, tdbp)) { + table->status= STATUS_NOT_FOUND; + rc= HA_ERR_INTERNAL_ERROR; + } else + rc= rnd_next(buf); + + DBUG_RETURN(rc); +} // end of index_first + + +/** + @brief + index_last() asks for the last key in the index. + + @details + Called from opt_range.cc, opt_sum.cc, sql_handler.cc, and sql_select.cc. + + @see + opt_range.cc, opt_sum.cc, sql_handler.cc and sql_select.cc +*/ +int ha_connect::index_last(uchar *buf) +{ + DBUG_ENTER("ha_connect::index_last"); + int rc; + + if (indexing <= 0) { + rc= HA_ERR_INTERNAL_ERROR; + } else + rc= ReadIndexed(buf, OP_LAST); + + DBUG_RETURN(rc); +} + + +/****************************************************************************/ +/* This is called to get more rows having the same index value. */ +/****************************************************************************/ +//t ha_connect::index_next_same(uchar *buf, const uchar *key, uint keylen) +int ha_connect::index_next_same(uchar *buf, const uchar *, uint) +{ + int rc; + DBUG_ENTER("ha_connect::index_next_same"); +//statistic_increment(ha_read_next_count, &LOCK_status); + + if (!indexing) + rc= rnd_next(buf); + else if (indexing > 0) + rc= ReadIndexed(buf, OP_SAME); + else + rc= HA_ERR_INTERNAL_ERROR; + + DBUG_RETURN(rc); +} // end of index_next_same + + +/** + @brief + rnd_init() is called when the system wants the storage engine to do a table + scan. See the example in the introduction at the top of this file to see when + rnd_init() is called. + + @details + Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc, + and sql_update.cc. + + @note + We always call open and extern_lock/start_stmt before comming here. + + @see + filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc and sql_update.cc +*/ +int ha_connect::rnd_init(bool scan) +{ + PGLOBAL g= ((table && table->in_use) ? GetPlug(table->in_use, xp) : + (xp) ? xp->g : NULL); + DBUG_ENTER("ha_connect::rnd_init"); + + // This is not tested yet + if (xmod == MODE_ALTER) { + xmod= MODE_READ; + alter= 1; + } // endif xmod + + if (trace(1)) + htrc("rnd_init: this=%p scan=%d xmod=%d alter=%d\n", + this, scan, xmod, alter); + + if (!g || !table || xmod == MODE_INSERT) + DBUG_RETURN(HA_ERR_INITIALIZATION); + + // Do not close the table if it was opened yet (locked?) + if (IsOpened()) { + if (IsPartitioned() && xmod != MODE_INSERT) + if (CheckColumnList(g)) // map can have been changed + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + + if (tdbp->OpenDB(g)) // Rewind table + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + else + DBUG_RETURN(0); + + } else if (xp->CheckQuery(valid_query_id)) + tdbp= NULL; // Not valid anymore + + // When updating, to avoid skipped update, force the table + // handler to retrieve write-only fields to be able to compare + // records and detect data change. + if (xmod == MODE_UPDATE) + bitmap_union(table->read_set, table->write_set); + + if (OpenTable(g, xmod == MODE_DELETE)) + DBUG_RETURN(HA_ERR_INITIALIZATION); + + xp->nrd= xp->fnd= xp->nfd= 0; + xp->tb1= my_interval_timer(); + DBUG_RETURN(0); +} // end of rnd_init + +/** + @brief + Not described. + + @note + The previous version said: + Stop scanning of table. Note that this may be called several times during + execution of a sub select. + =====> This has been moved to external lock to avoid closing subselect tables. +*/ +int ha_connect::rnd_end() +{ + int rc= 0; + DBUG_ENTER("ha_connect::rnd_end"); + + // If this is called by a later query, the table may have + // been already closed and the tdbp is not valid anymore. +// if (tdbp && xp->last_query_id == valid_query_id) +// rc= CloseTable(xp->g); + + ds_mrr.dsmrr_close(); + DBUG_RETURN(rc); +} // end of rnd_end + + +/** + @brief + This is called for each row of the table scan. When you run out of records + you should return HA_ERR_END_OF_FILE. Fill buff up with the row information. + The Field structure for the table is the key to getting data into buf + in a manner that will allow the server to understand it. + + @details + Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc, + and sql_update.cc. + + @see + filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc and sql_update.cc +*/ +int ha_connect::rnd_next(uchar *buf) +{ + int rc; + DBUG_ENTER("ha_connect::rnd_next"); +//statistic_increment(ha_read_rnd_next_count, &LOCK_status); + + if (tdbp->GetMode() == MODE_ANY) { + // We will stop on next read + if (!stop) { + stop= true; + DBUG_RETURN(RC_OK); + } else + DBUG_RETURN(HA_ERR_END_OF_FILE); + + } // endif Mode + + switch (CntReadNext(xp->g, tdbp)) { + case RC_OK: + rc= MakeRecord((char*)buf); + break; + case RC_EF: // End of file + rc= HA_ERR_END_OF_FILE; + break; + case RC_NF: // Not found + rc= HA_ERR_RECORD_DELETED; + break; + default: // Read error + htrc("rnd_next CONNECT: %s\n", xp->g->Message); + rc= (records()) ? HA_ERR_INTERNAL_ERROR : HA_ERR_END_OF_FILE; + break; + } // endswitch RC + + if (trace(2) && (rc || !(xp->nrd++ % 16384))) { + ulonglong tb2= my_interval_timer(); + double elapsed= (double) (tb2 - xp->tb1) / 1000000000ULL; + DBUG_PRINT("rnd_next", ("rc=%d nrd=%u fnd=%u nfd=%u sec=%.3lf\n", + rc, (uint)xp->nrd, (uint)xp->fnd, + (uint)xp->nfd, elapsed)); + htrc("rnd_next: rc=%d nrd=%u fnd=%u nfd=%u sec=%.3lf\n", + rc, (uint)xp->nrd, (uint)xp->fnd, + (uint)xp->nfd, elapsed); + xp->tb1= tb2; + xp->fnd= xp->nfd= 0; + } // endif nrd + + table->status= (!rc) ? 0 : STATUS_NOT_FOUND; + DBUG_RETURN(rc); +} // end of rnd_next + + +/** + @brief + position() is called after each call to rnd_next() if the data needs + to be ordered. You can do something like the following to store + the position: + @code + my_store_ptr(ref, ref_length, current_position); + @endcode + + @details + The server uses ref to store data. ref_length in the above case is + the size needed to store current_position. ref is just a byte array + that the server will maintain. If you are using offsets to mark rows, then + current_position should be the offset. If it is a primary key like in + BDB, then it needs to be a primary key. + + Called from filesort.cc, sql_select.cc, sql_delete.cc, and sql_update.cc. + + @see + filesort.cc, sql_select.cc, sql_delete.cc and sql_update.cc +*/ +void ha_connect::position(const uchar *) +{ + DBUG_ENTER("ha_connect::position"); + my_store_ptr(ref, ref_length, (my_off_t)tdbp->GetRecpos()); + + if (trace(2)) + htrc("position: pos=%d\n", tdbp->GetRecpos()); + + DBUG_VOID_RETURN; +} // end of position + + +/** + @brief + This is like rnd_next, but you are given a position to use + to determine the row. The position will be of the type that you stored in + ref. You can use my_get_ptr(pos,ref_length) to retrieve whatever key + or position you saved when position() was called. + + @details + Called from filesort.cc, records.cc, sql_insert.cc, sql_select.cc, and sql_update.cc. + + @note + Is this really useful? It was never called even when sorting. + + @see + filesort.cc, records.cc, sql_insert.cc, sql_select.cc and sql_update.cc +*/ +int ha_connect::rnd_pos(uchar *buf, uchar *pos) +{ + int rc; +//PTDBASE tp= (PTDBASE)tdbp; + DBUG_ENTER("ha_connect::rnd_pos"); + + if (!tdbp->SetRecpos(xp->g, (int)my_get_ptr(pos, ref_length))) { + if (trace(1)) + htrc("rnd_pos: %d\n", tdbp->GetRecpos()); + + tdbp->SetFilter(NULL); + rc= rnd_next(buf); + } else { + PGLOBAL g= GetPlug((table) ? table->in_use : NULL, xp); +// strcpy(g->Message, "Not supported by this table type"); + my_message(ER_ILLEGAL_HA, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + } // endif SetRecpos + + DBUG_RETURN(rc); +} // end of rnd_pos + + +/** + @brief + ::info() is used to return information to the optimizer. See my_base.h for + the complete description. + + @details + Currently this table handler doesn't implement most of the fields really needed. + SHOW also makes use of this data. + + You will probably want to have the following in your code: + @code + if (records < 2) + records= 2; + @endcode + The reason is that the server will optimize for cases of only a single + record. If, in a table scan, you don't know the number of records, it + will probably be better to set records to two so you can return as many + records as you need. Along with records, a few more variables you may wish + to set are: + records + deleted + data_file_length + index_file_length + delete_length + check_time + Take a look at the public variables in handler.h for more information. + + Called in filesort.cc, ha_heap.cc, item_sum.cc, opt_sum.cc, sql_delete.cc, + sql_delete.cc, sql_derived.cc, sql_select.cc, sql_select.cc, sql_select.cc, + sql_select.cc, sql_select.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_show.cc, + sql_table.cc, sql_union.cc, and sql_update.cc. + + @see + filesort.cc, ha_heap.cc, item_sum.cc, opt_sum.cc, sql_delete.cc, sql_delete.cc, + sql_derived.cc, sql_select.cc, sql_select.cc, sql_select.cc, sql_select.cc, + sql_select.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_table.cc, + sql_union.cc and sql_update.cc +*/ +int ha_connect::info(uint flag) +{ + bool pure= false; + PGLOBAL g= GetPlug((table) ? table->in_use : NULL, xp); + + DBUG_ENTER("ha_connect::info"); + + if (!g) { + my_message(ER_UNKNOWN_ERROR, "Cannot get g pointer", MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif g + + if (trace(1)) + htrc("%p In info: flag=%u valid_info=%d\n", this, flag, valid_info); + + // tdbp must be available to get updated info + if (xp->CheckQuery(valid_query_id) || !tdbp) { + + if (xmod == MODE_ANY || xmod == MODE_ALTER) { + // Pure info, not a query + pure= true; + xp->CheckCleanup(xmod == MODE_ANY && valid_query_id == 0); + } // endif xmod + + // This is necessary for getting file length + if (table) { + if (SetDataPath(g, table->s->db.str)) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif SetDataPath + + } else + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); // Should never happen + + if (!(tdbp= GetTDB(g))) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif tdbp + + valid_info= false; + } // endif tdbp + + if (!valid_info) { + valid_info= CntInfo(g, tdbp, &xinfo); + + if (((signed)xinfo.records) < 0) + DBUG_RETURN(HA_ERR_INITIALIZATION); // Error in Cardinality + + } // endif valid_info + + if (flag & HA_STATUS_VARIABLE) { + stats.records= xinfo.records; + stats.deleted= 0; + stats.data_file_length= xinfo.data_file_length; + stats.index_file_length= 0; + stats.delete_length= 0; + stats.check_time= 0; + stats.mean_rec_length= xinfo.mean_rec_length; + } // endif HA_STATUS_VARIABLE + + if (flag & HA_STATUS_CONST) { + // This is imported from the previous handler and must be reconsidered + stats.max_data_file_length= 4294967295LL; + stats.max_index_file_length= 4398046510080LL; + stats.create_time= 0; + data_file_name= xinfo.data_file_name; + index_file_name= NULL; +// sortkey= (uint) - 1; // Table is not sorted + ref_length= sizeof(int); // Pointer size to row + table->s->db_options_in_use= 03; + stats.block_size= 1024; + table->s->keys_in_use.set_prefix(table->s->keys); + table->s->keys_for_keyread= table->s->keys_in_use; +// table->s->keys_for_keyread.subtract(table->s->read_only_keys); + table->s->db_record_offset= 0; + } // endif HA_STATUS_CONST + + if (flag & HA_STATUS_ERRKEY) { + errkey= 0; + } // endif HA_STATUS_ERRKEY + + if (flag & HA_STATUS_TIME) + stats.update_time= 0; + + if (flag & HA_STATUS_AUTO) + stats.auto_increment_value= 1; + + if (tdbp && pure) + CloseTable(g); // Not used anymore + + DBUG_RETURN(0); +} // end of info + + +/** + @brief + extra() is called whenever the server wishes to send a hint to + the storage engine. The myisam engine implements the most hints. + ha_innodb.cc has the most exhaustive list of these hints. + + @note + This is not yet implemented for CONNECT. + + @see + ha_innodb.cc +*/ +int ha_connect::extra(enum ha_extra_function /*operation*/) +{ + DBUG_ENTER("ha_connect::extra"); + DBUG_RETURN(0); +} // end of extra + + +/** + @brief + Used to delete all rows in a table, including cases of truncate and cases where + the optimizer realizes that all rows will be removed as a result of an SQL statement. + + @details + Called from item_sum.cc by Item_func_group_concat::clear(), + Item_sum_count_distinct::clear(), and Item_func_group_concat::clear(). + Called from sql_delete.cc by mysql_delete(). + Called from sql_select.cc by JOIN::reinit(). + Called from sql_union.cc by st_select_lex_unit::exec(). + + @see + Item_func_group_concat::clear(), Item_sum_count_distinct::clear() and + Item_func_group_concat::clear() in item_sum.cc; + mysql_delete() in sql_delete.cc; + JOIN::reinit() in sql_select.cc and + st_select_lex_unit::exec() in sql_union.cc. +*/ +int ha_connect::delete_all_rows() +{ + int rc= 0; + PGLOBAL g= xp->g; + DBUG_ENTER("ha_connect::delete_all_rows"); + + if (tdbp && tdbp->GetUse() == USE_OPEN && + tdbp->GetAmType() != TYPE_AM_XML && + tdbp->GetFtype() != RECFM_NAF) + // Close and reopen the table so it will be deleted + rc= CloseTable(g); + + if (!(rc= OpenTable(g))) { + if (CntDeleteRow(g, tdbp, true)) { + htrc("%s\n", g->Message); + rc= HA_ERR_INTERNAL_ERROR; + } else + nox= false; + + } // endif rc + + DBUG_RETURN(rc); +} // end of delete_all_rows + + +static bool checkPrivileges(THD *thd, TABTYPE type, PTOS options, + const char *db, TABLE *table, bool quick) +{ + switch (type) { + case TAB_UNDEF: +// case TAB_CATLG: + case TAB_PLG: + case TAB_JCT: + case TAB_DMY: + case TAB_NIY: + my_printf_error(ER_UNKNOWN_ERROR, + "Unsupported table type %s", MYF(0), options->type); + return true; + + case TAB_DOS: + case TAB_FIX: + case TAB_BIN: + case TAB_CSV: + case TAB_FMT: + case TAB_DBF: + case TAB_XML: + case TAB_INI: + case TAB_VEC: + case TAB_REST: + case TAB_JSON: +#if defined(BSON_SUPPORT) + case TAB_BSON: +#endif // BSON_SUPPORT + if (options->filename && *options->filename) { + if (!quick) { + char path[FN_REFLEN], dbpath[FN_REFLEN]; + + strcpy(dbpath, mysql_real_data_home); + + if (db) +#if defined(_WIN32) + strcat(strcat(dbpath, db), "\\"); +#else // !_WIN32 + strcat(strcat(dbpath, db), "/"); +#endif // !_WIN32 + + (void)fn_format(path, options->filename, dbpath, "", + MY_RELATIVE_PATH | MY_UNPACK_FILENAME); + + if (!is_secure_file_path(path)) { + my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--secure-file-priv"); + return true; + } // endif path + + } // endif !quick + + } else + return false; + + // Fall through + case TAB_MYSQL: + case TAB_DIR: + case TAB_ZIP: + case TAB_OEM: + if (table && table->pos_in_table_list) { // if SELECT +#if MYSQL_VERSION_ID > 100200 + Switch_to_definer_security_ctx backup_ctx(thd, table->pos_in_table_list); +#endif // VERSION_ID > 100200 + return check_global_access(thd, FILE_ACL); + } else + return check_global_access(thd, FILE_ACL); + case TAB_ODBC: + case TAB_JDBC: + case TAB_MONGO: + case TAB_MAC: + case TAB_WMI: + return false; + case TAB_TBL: + case TAB_XCL: + case TAB_PRX: + case TAB_OCCUR: + case TAB_PIVOT: + case TAB_VIR: + default: + // This is temporary until a solution is found + return false; + } // endswitch type + + my_printf_error(ER_UNKNOWN_ERROR, "check_privileges failed", MYF(0)); + return true; +} // end of checkPrivileges + +// Check whether the user has required (file) privileges +bool ha_connect::check_privileges(THD *thd, PTOS options, const char *dbn, + bool quick) +{ + const char *db= (dbn && *dbn) ? dbn : NULL; + TABTYPE type=GetRealType(options); + + return checkPrivileges(thd, type, options, db, table, quick); +} // end of check_privileges + +// Check that two indexes are equivalent +bool ha_connect::IsSameIndex(PIXDEF xp1, PIXDEF xp2) +{ + bool b= true; + PKPDEF kp1, kp2; + + if (stricmp(xp1->Name, xp2->Name)) + b= false; + else if (xp1->Nparts != xp2->Nparts || + xp1->MaxSame != xp2->MaxSame || + xp1->Unique != xp2->Unique) + b= false; + else for (kp1= xp1->ToKeyParts, kp2= xp2->ToKeyParts; + b && (kp1 || kp2); + kp1= kp1->Next, kp2= kp2->Next) + if (!kp1 || !kp2) + b= false; + else if (stricmp(kp1->Name, kp2->Name)) + b= false; + else if (kp1->Klen != kp2->Klen) + b= false; + + return b; +} // end of IsSameIndex + +MODE ha_connect::CheckMode(PGLOBAL g, THD *thd, + MODE newmode, bool *chk, bool *cras) +{ +#if defined(DEVELOPMENT) + if (true) { +#else + if (trace(65)) { +#endif + LEX_STRING *query_string= thd_query_string(thd); + htrc("%p check_mode: cmdtype=%d\n", this, thd_sql_command(thd)); + htrc("Cmd=%.*s\n", (int) query_string->length, query_string->str); + } // endif trace + + // Next code is temporarily replaced until sql_command is set + stop= false; + + if (newmode == MODE_WRITE) { + switch (thd_sql_command(thd)) { + case SQLCOM_LOCK_TABLES: + locked= 2; // fall through + case SQLCOM_CREATE_TABLE: + case SQLCOM_INSERT: + case SQLCOM_LOAD: + case SQLCOM_INSERT_SELECT: + newmode= MODE_INSERT; + break; +// case SQLCOM_REPLACE: +// case SQLCOM_REPLACE_SELECT: +// newmode= MODE_UPDATE; // To be checked +// break; + case SQLCOM_DELETE_MULTI: + *cras= true; + // fall through + case SQLCOM_DELETE: + case SQLCOM_TRUNCATE: + newmode= MODE_DELETE; + break; + case SQLCOM_UPDATE_MULTI: + *cras= true; + // fall through + case SQLCOM_UPDATE: + newmode= MODE_UPDATE; + break; + case SQLCOM_SELECT: + case SQLCOM_OPTIMIZE: + newmode= MODE_READ; + break; + case SQLCOM_FLUSH: + locked= 0; + // fall through + case SQLCOM_DROP_TABLE: + case SQLCOM_RENAME_TABLE: + newmode= MODE_ANY; + break; + case SQLCOM_CREATE_VIEW: + case SQLCOM_DROP_VIEW: + newmode= MODE_ANY; + break; + case SQLCOM_ALTER_TABLE: + newmode= MODE_ALTER; + break; + case SQLCOM_DROP_INDEX: + case SQLCOM_CREATE_INDEX: +// if (!IsPartitioned()) { + newmode= MODE_ANY; + break; +// } // endif partitioned + case SQLCOM_REPAIR: // TODO implement it + newmode= MODE_UPDATE; + break; + default: + htrc("Unsupported sql_command=%d\n", thd_sql_command(thd)); + strcpy(g->Message, "CONNECT Unsupported command"); + my_message(ER_NOT_ALLOWED_COMMAND, g->Message, MYF(0)); + newmode= MODE_ERROR; + break; + } // endswitch newmode + + } else if (newmode == MODE_READ) { + switch (thd_sql_command(thd)) { + case SQLCOM_CREATE_TABLE: + *chk= true; + break; + case SQLCOM_UPDATE_MULTI: + case SQLCOM_DELETE_MULTI: + *cras= true; + case SQLCOM_INSERT: + case SQLCOM_LOAD: + case SQLCOM_INSERT_SELECT: +// case SQLCOM_REPLACE: +// case SQLCOM_REPLACE_SELECT: + case SQLCOM_DELETE: + case SQLCOM_TRUNCATE: + case SQLCOM_UPDATE: + case SQLCOM_SELECT: + case SQLCOM_OPTIMIZE: + case SQLCOM_SET_OPTION: + break; + case SQLCOM_LOCK_TABLES: + locked= 1; + break; + case SQLCOM_DROP_TABLE: + case SQLCOM_RENAME_TABLE: + newmode= MODE_ANY; + break; + case SQLCOM_CREATE_VIEW: + case SQLCOM_DROP_VIEW: + case SQLCOM_CREATE_TRIGGER: + case SQLCOM_DROP_TRIGGER: + newmode= MODE_ANY; + break; + case SQLCOM_ALTER_TABLE: + *chk= true; + newmode= MODE_ALTER; + break; + case SQLCOM_DROP_INDEX: + case SQLCOM_CREATE_INDEX: +// if (!IsPartitioned()) { + *chk= true; + newmode= MODE_ANY; + break; +// } // endif partitioned + + case SQLCOM_CHECK: // TODO implement it + case SQLCOM_ANALYZE: // TODO implement it + case SQLCOM_END: // Met in procedures: IF(EXISTS(SELECT... + newmode= MODE_READ; + break; + default: + htrc("Unsupported sql_command=%d\n", thd_sql_command(thd)); + strcpy(g->Message, "CONNECT Unsupported command"); + my_message(ER_NOT_ALLOWED_COMMAND, g->Message, MYF(0)); + newmode= MODE_ERROR; + break; + } // endswitch newmode + + } // endif's newmode + + if (trace(1)) + htrc("New mode=%d\n", newmode); + + return newmode; +} // end of check_mode + +int ha_connect::start_stmt(THD *thd, thr_lock_type lock_type) +{ + int rc= 0; + bool chk=false, cras= false; + MODE newmode; + PGLOBAL g= GetPlug(thd, xp); + DBUG_ENTER("ha_connect::start_stmt"); + + if (check_privileges(thd, GetTableOptionStruct(), table->s->db.str, true)) + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + + // Action will depend on lock_type + switch (lock_type) { + case TL_WRITE_ALLOW_WRITE: + case TL_WRITE_CONCURRENT_INSERT: + case TL_WRITE_DELAYED: + case TL_WRITE_DEFAULT: + case TL_WRITE_LOW_PRIORITY: + case TL_WRITE: + case TL_WRITE_ONLY: + newmode= MODE_WRITE; + break; + case TL_READ: + case TL_READ_WITH_SHARED_LOCKS: + case TL_READ_HIGH_PRIORITY: + case TL_READ_NO_INSERT: + case TL_READ_DEFAULT: + newmode= MODE_READ; + break; + case TL_UNLOCK: + default: + newmode= MODE_ANY; + break; + } // endswitch mode + + if (newmode == MODE_ANY) { + if (CloseTable(g)) { + // Make error a warning to avoid crash + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + rc= 0; + } // endif Close + + locked= 0; + xmod= MODE_ANY; // For info commands + DBUG_RETURN(rc); + } // endif MODE_ANY + + newmode= CheckMode(g, thd, newmode, &chk, &cras); + + if (newmode == MODE_ERROR) + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + + DBUG_RETURN(check_stmt(g, newmode, cras)); +} // end of start_stmt + +/** + @brief + This create a lock on the table. If you are implementing a storage engine + that can handle transacations look at ha_berkely.cc to see how you will + want to go about doing this. Otherwise you should consider calling flock() + here. Hint: Read the section "locking functions for mysql" in lock.cc to understand + this. + + @details + Called from lock.cc by lock_external() and unlock_external(). Also called + from sql_table.cc by copy_data_between_tables(). + + @note + Following what we did in the MySQL XDB handler, we use this call to actually + physically open the table. This could be reconsider when finalizing this handler + design, which means we have a better understanding of what MariaDB does. + + @see + lock.cc by lock_external() and unlock_external() in lock.cc; + the section "locking functions for mysql" in lock.cc; + copy_data_between_tables() in sql_table.cc. + +*/ +int ha_connect::external_lock(THD *thd, int lock_type) +{ + int rc= 0; + bool xcheck=false, cras= false; + MODE newmode; + PTOS options= GetTableOptionStruct(); + PGLOBAL g= GetPlug(thd, xp); + DBUG_ENTER("ha_connect::external_lock"); + + DBUG_ASSERT(thd == current_thd); + + if (trace(1)) + htrc("external_lock: this=%p thd=%p xp=%p g=%p lock_type=%d\n", + this, thd, xp, g, lock_type); + + if (!g) + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + + // Action will depend on lock_type + switch (lock_type) { + case F_WRLCK: + newmode= MODE_WRITE; + break; + case F_RDLCK: + newmode= MODE_READ; + break; + case F_UNLCK: + default: + newmode= MODE_ANY; + break; + } // endswitch mode + + if (newmode == MODE_ANY) { + int sqlcom= thd_sql_command(thd); + + // This is unlocking, do it by closing the table + if (xp->CheckQueryID() && sqlcom != SQLCOM_UNLOCK_TABLES + && sqlcom != SQLCOM_LOCK_TABLES + && sqlcom != SQLCOM_FLUSH + && sqlcom != SQLCOM_BEGIN + && sqlcom != SQLCOM_DROP_TABLE) { + sprintf(g->Message, "external_lock: unexpected command %d", sqlcom); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + DBUG_RETURN(0); + } else if (g->Xchk) { + if (!tdbp) { + if (!(tdbp= GetTDB(g))) { +// DBUG_RETURN(HA_ERR_INTERNAL_ERROR); causes assert error + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + DBUG_RETURN(0); + } else if (!tdbp->GetDef()->Indexable()) { + sprintf(g->Message, "external_lock: Table %s is not indexable", tdbp->GetName()); +// DBUG_RETURN(HA_ERR_INTERNAL_ERROR); causes assert error + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + DBUG_RETURN(0); + } else if (tdbp->GetDef()->Indexable() == 1) { + bool oldsep= ((PCHK)g->Xchk)->oldsep; + bool newsep= ((PCHK)g->Xchk)->newsep; + PTDBDOS tdp= (PTDBDOS)tdbp; + + PDOSDEF ddp= (PDOSDEF)tdp->GetDef(); + PIXDEF xp, xp1, xp2, drp=NULL, adp= NULL; + PIXDEF oldpix= ((PCHK)g->Xchk)->oldpix; + PIXDEF newpix= ((PCHK)g->Xchk)->newpix; + PIXDEF *xlst, *xprc; + + ddp->SetIndx(oldpix); + + if (oldsep != newsep) { + // All indexes have to be remade + ddp->DeleteIndexFile(g, NULL); + oldpix= NULL; + ddp->SetIndx(NULL); + SetBooleanOption("Sepindex", newsep); + } else if (newsep) { + // Make the list of dropped indexes + xlst= &drp; xprc= &oldpix; + + for (xp2= oldpix; xp2; xp2= xp) { + for (xp1= newpix; xp1; xp1= xp1->Next) + if (IsSameIndex(xp1, xp2)) + break; // Index not to drop + + xp= xp2->GetNext(); + + if (!xp1) { + *xlst= xp2; + *xprc= xp; + *(xlst= &xp2->Next)= NULL; + } else + xprc= &xp2->Next; + + } // endfor xp2 + + if (drp) { + // Here we erase the index files + ddp->DeleteIndexFile(g, drp); + } // endif xp1 + + } else if (oldpix) { + // TODO: optimize the case of just adding new indexes + if (!newpix) + ddp->DeleteIndexFile(g, NULL); + + oldpix= NULL; // To remake all indexes + ddp->SetIndx(NULL); + } // endif sepindex + + // Make the list of new created indexes + xlst= &adp; xprc= &newpix; + + for (xp1= newpix; xp1; xp1= xp) { + for (xp2= oldpix; xp2; xp2= xp2->Next) + if (IsSameIndex(xp1, xp2)) + break; // Index already made + + xp= xp1->Next; + + if (!xp2) { + *xlst= xp1; + *xprc= xp; + *(xlst= &xp1->Next)= NULL; + } else + xprc= &xp1->Next; + + } // endfor xp1 + + if (adp) + // Here we do make the new indexes + if (tdp->MakeIndex(g, adp, true) == RC_FX) { + // Make it a warning to avoid crash + //push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + // 0, g->Message); + //rc= 0; + my_message(ER_TOO_MANY_KEYS, g->Message, MYF(0)); + rc= HA_ERR_INDEX_CORRUPT; + } // endif MakeIndex + + } else if (tdbp->GetDef()->Indexable() == 3) { + if (CheckVirtualIndex(NULL)) { + // Make it a warning to avoid crash + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + 0, g->Message); + rc= 0; + } // endif Check + + } // endif indexable + + } // endif Tdbp + + } // endelse Xchk + + if (CloseTable(g)) { + // This is an error while builing index + // Make it a warning to avoid crash + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + rc= 0; + } // endif Close + + locked= 0; + xmod= MODE_ANY; // For info commands + DBUG_RETURN(rc); + } else if (check_privileges(thd, options, table->s->db.str)) { + strcpy(g->Message, "This operation requires the FILE privilege"); + htrc("%s\n", g->Message); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif check_privileges + + + DBUG_ASSERT(table && table->s); + + // Table mode depends on the query type + newmode= CheckMode(g, thd, newmode, &xcheck, &cras); + + if (newmode == MODE_ERROR) + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + + DBUG_RETURN(check_stmt(g, newmode, cras)); +} // end of external_lock + + +int ha_connect::check_stmt(PGLOBAL g, MODE newmode, bool cras) +{ + int rc= 0; + DBUG_ENTER("ha_connect::check_stmt"); + + // If this is the start of a new query, cleanup the previous one + if (xp->CheckCleanup()) { + tdbp= NULL; + valid_info= false; + } // endif CheckCleanup + + if (cras) + g->Createas= true; // To tell external tables of a multi-table command + + if (trace(1)) + htrc("Calling CntCheckDB db=%s cras=%d\n", GetDBName(NULL), cras); + + // Set or reset the good database environment + if (CntCheckDB(g, this, GetDBName(NULL))) { + htrc("%p check_stmt: %s\n", this, g->Message); + rc= HA_ERR_INTERNAL_ERROR; + // This can NOT be called without open called first, but + // the table can have been closed since then + } else if (!tdbp || xp->CheckQuery(valid_query_id) || xmod != newmode) { + if (tdbp) { + // If this is called by a later query, the table may have + // been already closed and the tdbp is not valid anymore. + if (xp->last_query_id == valid_query_id) + rc= CloseTable(g); + else + tdbp= NULL; + + } // endif tdbp + + xmod= newmode; + + // Delay open until used fields are known + } // endif tdbp + + if (trace(1)) + htrc("check_stmt: rc=%d\n", rc); + + DBUG_RETURN(rc); +} // end of check_stmt + + +/** + @brief + The idea with handler::store_lock() is: The statement decides which locks + should be needed for the table. For updates/deletes/inserts we get WRITE + locks, for SELECT... we get read locks. + + @details + Before adding the lock into the table lock handler (see thr_lock.c), + mysqld calls store lock with the requested locks. Store lock can now + modify a write lock to a read lock (or some other lock), ignore the + lock (if we don't want to use MySQL table locks at all), or add locks + for many tables (like we do when we are using a MERGE handler). + + Berkeley DB, for example, changes all WRITE locks to TL_WRITE_ALLOW_WRITE + (which signals that we are doing WRITES, but are still allowing other + readers and writers). + + When releasing locks, store_lock() is also called. In this case one + usually doesn't have to do anything. + + In some exceptional cases MySQL may send a request for a TL_IGNORE; + This means that we are requesting the same lock as last time and this + should also be ignored. (This may happen when someone does a flush + table when we have opened a part of the tables, in which case mysqld + closes and reopens the tables and tries to get the same locks at last + time). In the future we will probably try to remove this. + + Called from lock.cc by get_lock_data(). + + @note + In this method one should NEVER rely on table->in_use, it may, in fact, + refer to a different thread! (this happens if get_lock_data() is called + from mysql_lock_abort_for_thread() function) + + @see + get_lock_data() in lock.cc +*/ +THR_LOCK_DATA **ha_connect::store_lock(THD *, + THR_LOCK_DATA **to, + enum thr_lock_type lock_type) +{ + if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) + lock.type=lock_type; + *to++= &lock; + return to; +} + + +/** + Searches for a pointer to the last occurrence of the + character c in the string src. + Returns true on failure, false on success. +*/ +static bool +strnrchr(LEX_CSTRING *ls, const char *src, size_t length, int c) +{ + const char *srcend, *s; + for (s= srcend= src + length; s > src; s--) + { + if (s[-1] == c) + { + ls->str= s; + ls->length= srcend - s; + return false; + } + } + return true; +} + + +/** + Split filename into database and table name. +*/ +static bool +filename_to_dbname_and_tablename(const char *filename, + char *database, size_t database_size, + char *table, size_t table_size) +{ + LEX_CSTRING d, t; + size_t length= strlen(filename); + + /* Find filename - the rightmost directory part */ + if (strnrchr(&t, filename, length, slash) || t.length + 1 > table_size) + return true; + memcpy(table, t.str, t.length); + table[t.length]= '\0'; + if (!(length-= t.length)) + return true; + + length--; /* Skip slash */ + + /* Find database name - the second rightmost directory part */ + if (strnrchr(&d, filename, length, slash) || d.length + 1 > database_size) + return true; + memcpy(database, d.str, d.length); + database[d.length]= '\0'; + return false; +} // end of filename_to_dbname_and_tablename + +/** + @brief + Used to delete or rename a table. By the time delete_table() has been + called all opened references to this table will have been closed + (and your globally shared references released) ===> too bad!!! + The variable name will just be the name of the table. + You will need to remove or rename any files you have created at + this point. + + @details + If you do not implement this, the default delete_table() is called from + handler.cc and it will delete all files with the file extensions returned + by bas_ext(). + + Called from handler.cc by delete_table and ha_create_table(). Only used + during create if the table_flag HA_DROP_BEFORE_CREATE was specified for + the storage engine. + + @see + delete_table and ha_create_table() in handler.cc +*/ +int ha_connect::delete_or_rename_table(const char *name, const char *to) +{ + DBUG_ENTER("ha_connect::delete_or_rename_table"); + char db[128], tabname[128]; + int rc= 0; + bool ok= false; + THD *thd= current_thd; + int sqlcom= thd_sql_command(thd); + + if (trace(1)) { + if (to) + htrc("rename_table: this=%p thd=%p sqlcom=%d from=%s to=%s\n", + this, thd, sqlcom, name, to); + else + htrc("delete_table: this=%p thd=%p sqlcom=%d name=%s\n", + this, thd, sqlcom, name); + + } // endif trace + + if (to && (filename_to_dbname_and_tablename(to, db, sizeof(db), + tabname, sizeof(tabname)) + || (*tabname == '#' && sqlcom == SQLCOM_CREATE_INDEX))) + DBUG_RETURN(0); + + if (filename_to_dbname_and_tablename(name, db, sizeof(db), + tabname, sizeof(tabname)) + || (*tabname == '#' && sqlcom == SQLCOM_CREATE_INDEX)) + DBUG_RETURN(0); + + // If a temporary file exists, all the tests below were passed + // successfully when making it, so they are not needed anymore + // in particular because they sometimes cause DBUG_ASSERT crash. + // Also, for partitioned tables, no test can be done because when + // this function is called, the .par file is already deleted and + // this causes the open_table_def function to fail. + // Not having any other clues (table and table_share are NULL) + // the only mean we have to test for partitioning is this: + if (*tabname != '#' && !strstr(tabname, "#P#")) { + // We have to retrieve the information about this table options. + ha_table_option_struct *pos; + char key[MAX_DBKEY_LENGTH]; + uint key_length; + TABLE_SHARE *share; + +// if ((p= strstr(tabname, "#P#"))) won't work, see above +// *p= 0; // Get the main the table name + + key_length= tdc_create_key(key, db, tabname); + + // share contains the option struct that we need + if (!(share= alloc_table_share(db, tabname, key, key_length))) + DBUG_RETURN(rc); + + // Get the share info from the .frm file + Dummy_error_handler error_handler; + thd->push_internal_handler(&error_handler); + bool got_error= open_table_def(thd, share); + thd->pop_internal_handler(); + if (!got_error && share->db_type() != connect_hton) + { + /* The .frm file is not for the connect engine. Something is wrong! */ + got_error= 1; + rc= HA_ERR_INTERNAL_ERROR; + my_error(HA_ERR_INTERNAL_ERROR, MYF(0), + "TABLE_SHARE is not for the CONNECT engine"); + } + if (!got_error) { + // Now we can work + if ((pos= share->option_struct)) { + if (check_privileges(thd, pos, db)) + rc= HA_ERR_INTERNAL_ERROR; // ??? + else + if (IsFileType(GetRealType(pos)) && !pos->filename) + ok= true; + + } // endif pos + + } // endif open_table_def + else + rc= ENOENT; + free_table_share(share); + } else // Temporary file + ok= true; + + if (ok) { + // Let the base handler do the job + if (to) + rc= handler::rename_table(name, to); + else if ((rc= handler::delete_table(name)) == ENOENT) + rc= 0; // No files is not an error for CONNECT + + } // endif ok + + DBUG_RETURN(rc); +} // end of delete_or_rename_table + +int ha_connect::delete_table(const char *name) +{ + return delete_or_rename_table(name, NULL); +} // end of delete_table + +int ha_connect::rename_table(const char *from, const char *to) +{ + return delete_or_rename_table(from, to); +} // end of rename_table + +/** + @brief + Given a starting key and an ending key, estimate the number of rows that + will exist between the two keys. + + @details + end_key may be empty, in which case determine if start_key matches any rows. + + Called from opt_range.cc by check_quick_keys(). + + @see + check_quick_keys() in opt_range.cc +*/ +ha_rows ha_connect::records_in_range(uint inx, + const key_range *min_key, + const key_range *max_key, + page_range *pages) + +{ + ha_rows rows; + DBUG_ENTER("ha_connect::records_in_range"); + + if (indexing < 0 || inx != active_index) + if (index_init(inx, false)) + DBUG_RETURN(HA_POS_ERROR); + + if (trace(1)) + htrc("records_in_range: inx=%d indexing=%d\n", inx, indexing); + + if (indexing > 0) { + int nval; + uint len[2]; + const uchar *key[2]; + bool incl[2]; + key_part_map kmap[2]; + + key[0]= (min_key) ? min_key->key : NULL; + key[1]= (max_key) ? max_key->key : NULL; + len[0]= (min_key) ? min_key->length : 0; + len[1]= (max_key) ? max_key->length : 0; + incl[0]= (min_key) ? (min_key->flag == HA_READ_KEY_EXACT) : false; + incl[1]= (max_key) ? (max_key->flag == HA_READ_AFTER_KEY) : false; + kmap[0]= (min_key) ? min_key->keypart_map : 0; + kmap[1]= (max_key) ? max_key->keypart_map : 0; + + if ((nval= CntIndexRange(xp->g, tdbp, key, len, incl, kmap)) < 0) + rows= HA_POS_ERROR; + else + rows= (ha_rows)nval; + + } else if (indexing == 0) + rows= 100000000; // Don't use missing index + else + rows= HA_POS_ERROR; + + if (trace(1)) + htrc("records_in_range: rows=%llu\n", rows); + + DBUG_RETURN(rows); +} // end of records_in_range + +// 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) +{ + if (src) + return false; + else if (host && stricmp(host, "localhost") && strcmp(host, "127.0.0.1")) + return false; + else if (db && stricmp(db, s->db.str)) + return false; + else if (tab && stricmp(tab, s->table_name.str)) + return false; + else if (port && port != (signed)GetDefaultPort()) + return false; + + strcpy(g->Message, "This MySQL table is defined on itself"); + return true; +} // end of CheckSelf + +/** + Convert an ISO-8859-1 column name to UTF-8 +*/ +static char *encode(PGLOBAL g, const char *cnm) + { + char *buf= (char*)PlugSubAlloc(g, NULL, strlen(cnm) * 3); + uint dummy_errors; + uint32 len= copy_and_convert(buf, strlen(cnm) * 3, + &my_charset_utf8mb3_general_ci, + cnm, strlen(cnm), + &my_charset_latin1, + &dummy_errors); + buf[len]= '\0'; + return buf; + } // end of encode + +/** + Store field definition for create. + + @return + Return 0 if ok +*/ +static bool add_field(String* sql, TABTYPE ttp, const char* field_name, int typ, + int len, int dec, char* key, uint tm, const char* rem, + char* dft, char* xtra, char* fmt, int flag, bool dbf, char v) +{ + char var = (len > 255) ? 'V' : v; + bool q, error = false; + const char* type = PLGtoMYSQLtype(typ, dbf, var); + + error |= sql->append('`'); + error |= sql->append(field_name); + error |= sql->append("` "); + error |= sql->append(type); + + if (typ == TYPE_STRING || + (len && typ != TYPE_DATE && (typ != TYPE_DOUBLE || dec >= 0))) { + error |= sql->append('('); + error |= sql->append_ulonglong(len); + + if (typ == TYPE_DOUBLE) { + error |= sql->append(','); + // dec must be < len and < 31 + error |= sql->append_ulonglong(MY_MIN(dec, (MY_MIN(len, 31) - 1))); + } else if (dec > 0 && !strcmp(type, "DECIMAL")) { + error |= sql->append(','); + // dec must be < len + error |= sql->append_ulonglong(MY_MIN(dec, len - 1)); + } // endif dec + + error |= sql->append(')'); + } // endif len + + if (v == 'U') + error |= sql->append(" UNSIGNED"); + else if (v == 'Z') + error |= sql->append(" ZEROFILL"); + + if (key && *key) { + error |= sql->append(" "); + error |= sql->append(key); + } // endif key + + if (tm) + error |= sql->append(STRING_WITH_LEN(" NOT NULL"), system_charset_info); + + if (dft && *dft) { + error |= sql->append(" DEFAULT "); + + if (typ == TYPE_DATE) + q = (strspn(dft, "0123456789 -:/") == strlen(dft)); + else + q = !IsTypeNum(typ); + + if (q) { + error |= sql->append("'"); + error |= sql->append_for_single_quote(dft, strlen(dft)); + error |= sql->append("'"); + } else + error |= sql->append(dft); + + } // endif dft + + if (xtra && *xtra) { + error |= sql->append(" "); + error |= sql->append(xtra); + } // endif rem + + if (rem && *rem) { + error |= sql->append(" COMMENT '"); + error |= sql->append_for_single_quote(rem, strlen(rem)); + error |= sql->append("'"); + } // endif rem + + if (fmt && *fmt) { + switch (ttp) { + case TAB_MONGO: + case TAB_BSON: + case TAB_JSON: error |= sql->append(" JPATH='"); break; + case TAB_XML: error |= sql->append(" XPATH='"); break; + default: error |= sql->append(" FIELD_FORMAT='"); + } // endswitch ttp + + error |= sql->append_for_single_quote(fmt, strlen(fmt)); + error |= sql->append("'"); + } // endif flag + + if (flag) { + error |= sql->append(" FLAG="); + error |= sql->append_ulonglong(flag); + } // endif flag + + error |= sql->append(','); + return error; +} // end of add_field + +/** + Initialise the table share with the new columns. + + @return + Return 0 if ok +*/ +static int init_table_share(THD* thd, + TABLE_SHARE *table_s, + HA_CREATE_INFO *create_info, + String *sql) +{ + bool oom= false; + PTOS topt= table_s->option_struct; + + sql->length(sql->length()-1); // remove the trailing comma + sql->append(')'); + + for (ha_create_table_option *opt= connect_table_option_list; + opt->name; opt++) { + ulonglong vull; + const char *vstr; + + switch (opt->type) { + case HA_OPTION_TYPE_ULL: + vull= *(ulonglong*)(((char*)topt) + opt->offset); + + if (vull != opt->def_value) { + oom|= sql->append(' '); + oom|= sql->append(opt->name); + oom|= sql->append('='); + oom|= sql->append_ulonglong(vull); + } // endif vull + + break; + case HA_OPTION_TYPE_STRING: + vstr= *(char**)(((char*)topt) + opt->offset); + + if (vstr) { + oom|= sql->append(' '); + oom|= sql->append(opt->name); + oom|= sql->append("='"); + oom|= sql->append_for_single_quote(vstr, strlen(vstr)); + oom|= sql->append('\''); + } // endif vstr + + break; + case HA_OPTION_TYPE_BOOL: + vull= *(bool*)(((char*)topt) + opt->offset); + + if (vull != opt->def_value) { + oom|= sql->append(' '); + oom|= sql->append(opt->name); + oom|= sql->append('='); + oom|= sql->append(vull ? "YES" : "NO"); + } // endif vull + + break; + default: // no enums here, good :) + break; + } // endswitch type + + if (oom) + return HA_ERR_OUT_OF_MEM; + + } // endfor opt + + if (create_info->connect_string.length) { + oom|= sql->append(' '); + oom|= sql->append("CONNECTION='"); + oom|= sql->append_for_single_quote(create_info->connect_string.str, + create_info->connect_string.length); + oom|= sql->append('\''); + + if (oom) + return HA_ERR_OUT_OF_MEM; + + } // endif string + + if (create_info->default_table_charset) { + oom|= sql->append(' '); + oom|= sql->append("CHARSET="); + oom|= sql->append(create_info->default_table_charset->csname); + + if (oom) + return HA_ERR_OUT_OF_MEM; + + } // endif charset + + if (trace(1)) + htrc("s_init: %.*s\n", sql->length(), sql->ptr()); + + return table_s->init_from_sql_statement_string(thd, true, + sql->ptr(), sql->length()); +} // end of init_table_share + +/** + @brief + connect_assisted_discovery() is called when creating a table with no columns. + + @details + When assisted discovery is used the .frm file have not already been + created. You can overwrite some definitions at this point but the + main purpose of it is to define the columns for some table types. + + @note + this function is no more called in case of CREATE .. SELECT +*/ +static int connect_assisted_discovery(handlerton *, THD* thd, + TABLE_SHARE *table_s, + HA_CREATE_INFO *create_info) +{ + char v=0; + PCSZ fncn= "?"; + PCSZ user, fn, db, host, pwd, sep, tbl, src; + PCSZ col, ocl, rnk, pic, fcl, skc, zfn; + char *tab, *dsn, *shm, *dpath, *url; +#if defined(_WIN32) + PCSZ nsp= NULL, cls= NULL; +#endif // _WIN32 +//int hdr, mxe; + int port= 0, mxr __attribute__((unused)) = 0, rc= 0, mul= 0; +//PCSZ tabtyp= NULL; +#if defined(ODBC_SUPPORT) + POPARM sop= NULL; + PCSZ ucnc= NULL; + bool cnc= false; + int cto= -1, qto= -1; +#endif // ODBC_SUPPORT +#if defined(JAVA_SUPPORT) + PJPARM sjp= NULL; + PCSZ driver= NULL; +#endif // JAVA_SUPPORT + uint tm, fnc= FNC_NO, supfnc= (FNC_NO | FNC_COL); + bool bif, ok= false, dbf= false; + TABTYPE ttp= TAB_UNDEF, ttr=TAB_UNDEF; + PQRYRES qrp= NULL; + PCOLRES crp; + PCONNECT xp= NULL; + PGLOBAL g= GetPlug(thd, xp); + + if (!g) + return HA_ERR_INTERNAL_ERROR; + + PTOS topt= table_s->option_struct; + char buf[1024]; + String sql(buf, sizeof(buf), system_charset_info); + + sql.copy(STRING_WITH_LEN("CREATE TABLE whatever ("), system_charset_info); + user= host= pwd= tbl= src= col= ocl= pic= fcl= skc= rnk= zfn= NULL; + dsn= url= NULL; + + // Get the useful create options + ttp= GetTypeID(topt->type); + fn= topt->filename; + tab= (char*)topt->tabname; + src= topt->srcdef; + db= topt->dbname; + fncn= topt->catfunc; + fnc= GetFuncID(fncn); + sep= topt->separator; + mul= (int)topt->multiple; + tbl= topt->tablist; + col= topt->colist; + + if (topt->oplist) { + host= GetListOption(g, "host", topt->oplist, "localhost"); + user= GetListOption(g, "user", topt->oplist, + ((ttp == TAB_ODBC || ttp == TAB_JDBC) ? NULL : "root")); + // Default value db can come from the DBNAME=xxx option. + db= GetListOption(g, "database", topt->oplist, db); + col= GetListOption(g, "colist", topt->oplist, col); + ocl= GetListOption(g, "occurcol", topt->oplist, NULL); + pic= GetListOption(g, "pivotcol", topt->oplist, NULL); + fcl= GetListOption(g, "fnccol", topt->oplist, NULL); + skc= GetListOption(g, "skipcol", topt->oplist, NULL); + rnk= GetListOption(g, "rankcol", topt->oplist, NULL); + pwd= GetListOption(g, "password", topt->oplist); +#if defined(_WIN32) + nsp= GetListOption(g, "namespace", topt->oplist); + cls= GetListOption(g, "class", topt->oplist); +#endif // _WIN32 + port= atoi(GetListOption(g, "port", topt->oplist, "0")); +#if defined(ODBC_SUPPORT) +// tabtyp= GetListOption(g, "Tabtype", topt->oplist, NULL); + mxr= atoi(GetListOption(g,"maxres", topt->oplist, "0")); + cto= atoi(GetListOption(g,"ConnectTimeout", topt->oplist, "-1")); + qto= atoi(GetListOption(g,"QueryTimeout", topt->oplist, "-1")); + + if ((ucnc= GetListOption(g, "UseDSN", topt->oplist))) + cnc= (!*ucnc || *ucnc == 'y' || *ucnc == 'Y' || atoi(ucnc) != 0); +#endif +#if defined(JAVA_SUPPORT) + driver= GetListOption(g, "Driver", topt->oplist, NULL); +#endif // JAVA_SUPPORT +#if defined(PROMPT_OK) + cop= atoi(GetListOption(g, "checkdsn", topt->oplist, "0")); +#endif // PROMPT_OK +#if defined(ZIP_SUPPORT) + zfn= GetListOption(g, "Zipfile", topt->oplist, NULL); +#endif // ZIP_SUPPORT + } else { + host= "localhost"; + user= ((ttp == TAB_ODBC || ttp == TAB_JDBC) ? NULL : "root"); + } // endif option_list + + if (!(shm= (char*)db)) + db= table_s->db.str; // Default value + + try { + // Check table type + if (ttp == TAB_UNDEF && !topt->http) { + topt->type= (src) ? "MYSQL" : (tab) ? "PROXY" : "DOS"; + ttp= GetTypeID(topt->type); + sprintf(g->Message, "No table_type. Was set to %s", topt->type); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, 0, g->Message); + } else if (ttp == TAB_NIY) { + sprintf(g->Message, "Unsupported table type %s", topt->type); + rc= HA_ERR_INTERNAL_ERROR; + goto err; +#if defined(REST_SUPPORT) + } else if (topt->http) { + if (ttp == TAB_UNDEF) { + ttr= TAB_JSON; + strcpy(g->Message, "No table_type. Was set to JSON"); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, 0, g->Message); + } else + ttr= ttp; + + switch (ttr) { + case TAB_JSON: +#if defined(BSON_SUPPORT) + case TAB_BSON: +#endif // BSON_SUPPORT + case TAB_XML: + case TAB_CSV: + ttp = TAB_REST; + break; + default: + break; + } // endswitch type +#endif // REST_SUPPORT + } // endif ttp + + if (fn && *fn) + switch (ttp) { + case TAB_FMT: + case TAB_DBF: + case TAB_XML: + case TAB_INI: + case TAB_VEC: + case TAB_REST: + case TAB_JSON: +#if defined(BSON_SUPPORT) + case TAB_BSON: +#endif // BSON_SUPPORT + if (checkPrivileges(thd, ttp, topt, db)) { + strcpy(g->Message, "This operation requires the FILE privilege"); + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif check_privileges + + break; + default: + break; + } // endswitch ttp + + if (!tab) { + if (ttp == TAB_TBL) { + // Make tab the first table of the list + char *p; + + if (!tbl) { + strcpy(g->Message, "Missing table list"); + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif tbl + + tab= PlugDup(g, tbl); + + if ((p= strchr(tab, ','))) + *p= 0; + + if ((p= strchr(tab, '.'))) { + *p= 0; + db= tab; + tab= p + 1; + } // endif p + + } else if (ttp != TAB_ODBC || !(fnc & (FNC_TABLE | FNC_COL))) + tab= (char*)table_s->table_name.str; // Default value + + } // endif tab + + switch (ttp) { +#if defined(ODBC_SUPPORT) + case TAB_ODBC: + dsn= strz(g, create_info->connect_string); + + if (fnc & (FNC_DSN | FNC_DRIVER)) { + ok= true; +#if defined(PROMPT_OK) + } else if (!stricmp(thd->main_security_ctx.host, "localhost") + && cop == 1) { + if ((dsn= ODBCCheckConnection(g, dsn, cop)) != NULL) { + thd->make_lex_string(&create_info->connect_string, dsn, strlen(dsn)); + ok= true; + } // endif dsn +#endif // PROMPT_OK + + } else if (!dsn) { + sprintf(g->Message, "Missing %s connection string", topt->type); + } else { + // Store ODBC additional parameters + sop= (POPARM)PlugSubAlloc(g, NULL, sizeof(ODBCPARM)); + sop->User= (char*)user; + sop->Pwd= (char*)pwd; + sop->Cto= cto; + sop->Qto= qto; + sop->UseCnc= cnc; + ok= true; + } // endif's + + supfnc |= (FNC_TABLE | FNC_DSN | FNC_DRIVER); + break; +#endif // ODBC_SUPPORT +#if defined(JAVA_SUPPORT) + case TAB_JDBC: + if (fnc & FNC_DRIVER) { + ok= true; + } else if (!(url= strz(g, create_info->connect_string))) { + strcpy(g->Message, "Missing URL"); + } else { + // Store JDBC additional parameters + int rc; + PJDBCDEF jdef= new(g) JDBCDEF(); + + jdef->SetName(create_info->alias.str); + sjp = (PJPARM)PlugSubAlloc(g, NULL, sizeof(JDBCPARM)); + sjp->Driver = driver; + // sjp->Properties = prop; + sjp->Fsize = 0; + sjp->Scrollable = false; + + if ((rc = jdef->ParseURL(g, url, false)) == RC_OK) { + sjp->Url = url; + sjp->User = (char*)user; + sjp->Pwd = (char*)pwd; + ok = true; + } else if (rc == RC_NF) { + if (jdef->GetTabname()) + tab= (char*)jdef->GetTabname(); + + ok= jdef->SetParms(sjp); + } // endif rc + + } // endif's + + supfnc |= (FNC_DRIVER | FNC_TABLE); + break; +#endif // JAVA_SUPPORT + case TAB_DBF: + dbf= true; + // fall through + case TAB_CSV: + if (!fn && fnc != FNC_NO) + sprintf(g->Message, "Missing %s file name", topt->type); + else if (sep && strlen(sep) > 1) + sprintf(g->Message, "Invalid separator %s", sep); + else + ok= true; + + break; + case TAB_MYSQL: + ok= true; + + if (create_info->connect_string.str && + create_info->connect_string.length) { + PMYDEF mydef= new(g) MYSQLDEF(); + + dsn= strz(g, create_info->connect_string); + mydef->SetName(create_info->alias.str); + + if (!mydef->ParseURL(g, dsn, false)) { + if (mydef->GetHostname()) + host= mydef->GetHostname(); + + if (mydef->GetUsername()) + user= mydef->GetUsername(); + + if (mydef->GetPassword()) + pwd= mydef->GetPassword(); + + if (mydef->GetTabschema()) + db= mydef->GetTabschema(); + + if (mydef->GetTabname()) + tab= (char*)mydef->GetTabname(); + + if (mydef->GetPortnumber()) + port= mydef->GetPortnumber(); + + } else + ok= false; + + } else if (!user) + user= "root"; + + if (ok && CheckSelf(g, table_s, host, db, tab, src, port)) + ok= false; + + break; +#if defined(_WIN32) + case TAB_WMI: + ok= true; + break; +#endif // _WIN32 + case TAB_PIVOT: + supfnc= FNC_NO; + // fall through + case TAB_PRX: + case TAB_TBL: + case TAB_XCL: + case TAB_OCCUR: + if (!src && !stricmp(tab, create_info->alias.str) && + (!db || !stricmp(db, table_s->db.str))) + sprintf(g->Message, "A %s table cannot refer to itself", topt->type); + else + ok= true; + + break; + case TAB_OEM: + if (topt->module && topt->subtype) + ok= true; + else + strcpy(g->Message, "Missing OEM module or subtype"); + + break; +#if defined(LIBXML2_SUPPORT) || defined(DOMDOC_SUPPORT) + case TAB_XML: +#endif // LIBXML2_SUPPORT || DOMDOC_SUPPORT + case TAB_JSON: +#if defined(BSON_SUPPORT) + case TAB_BSON: +#endif // BSON_SUPPORT + dsn= strz(g, create_info->connect_string); + + if (!fn && !zfn && !mul && !dsn) + sprintf(g->Message, "Missing %s file name", topt->type); + else if (dsn && !topt->tabname) + topt->tabname= tab; + + ok= true; + break; +#if defined(JAVA_SUPPORT) + case TAB_MONGO: + if (!topt->tabname) + topt->tabname= tab; + + ok= true; + break; +#endif // JAVA_SUPPORT +#if defined(REST_SUPPORT) + case TAB_REST: + if (!topt->http) + strcpy(g->Message, "Missing REST HTTP option"); + else + ok = true; + + break; +#endif // REST_SUPPORT + case TAB_VIR: + ok= true; + break; + default: + sprintf(g->Message, "Cannot get column info for table type %s", topt->type); + break; + } // endif ttp + + // Check for supported catalog function + if (ok && !(supfnc & fnc)) { + sprintf(g->Message, "Unsupported catalog function %s for table type %s", + fncn, topt->type); + ok= false; + } // endif supfnc + + if (src && fnc != FNC_NO) { + strcpy(g->Message, "Cannot make catalog table from srcdef"); + ok= false; + } // endif src + + if (ok) { + const char *cnm, *rem; + char *dft, *xtra, *key, *fmt; + int i, len, prec, dec, typ, flg; + + if (!(dpath= SetPath(g, table_s->db.str))) { + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif dpath + + if (src && ttp != TAB_PIVOT && ttp != TAB_ODBC && ttp != TAB_JDBC) { + qrp= SrcColumns(g, host, db, user, pwd, src, port); + + if (qrp && ttp == TAB_OCCUR) + if (OcrSrcCols(g, qrp, col, ocl, rnk)) { + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif OcrSrcCols + + } else switch (ttp) { + case TAB_DBF: + qrp= DBFColumns(g, dpath, fn, topt, fnc == FNC_COL); + break; +#if defined(ODBC_SUPPORT) + case TAB_ODBC: + switch (fnc) { + case FNC_NO: + case FNC_COL: + if (src) { + qrp= ODBCSrcCols(g, dsn, (char*)src, sop); + src= NULL; // for next tests + } else + qrp= ODBCColumns(g, dsn, shm, tab, NULL, + mxr, fnc == FNC_COL, sop); + + break; + case FNC_TABLE: + qrp= ODBCTables(g, dsn, shm, tab, NULL, mxr, true, sop); + break; + case FNC_DSN: + qrp= ODBCDataSources(g, mxr, true); + break; + case FNC_DRIVER: + qrp= ODBCDrivers(g, mxr, true); + break; + default: + sprintf(g->Message, "invalid catfunc %s", fncn); + break; + } // endswitch info + + break; +#endif // ODBC_SUPPORT +#if defined(JAVA_SUPPORT) + case TAB_JDBC: + switch (fnc) { + case FNC_NO: + case FNC_COL: + if (src) { + qrp= JDBCSrcCols(g, (char*)src, sjp); + src= NULL; // for next tests + } else + qrp= JDBCColumns(g, shm, tab, NULL, mxr, fnc == FNC_COL, sjp); + + break; + case FNC_TABLE: +// qrp= JDBCTables(g, shm, tab, tabtyp, mxr, true, sjp); + qrp= JDBCTables(g, shm, tab, NULL, mxr, true, sjp); + break; +#if 0 + case FNC_DSN: + qrp= JDBCDataSources(g, mxr, true); + break; +#endif // 0 + case FNC_DRIVER: + qrp= JDBCDrivers(g, mxr, true); + break; + default: + sprintf(g->Message, "invalid catfunc %s", fncn); + break; + } // endswitch info + + break; +#endif // JAVA_SUPPORT + case TAB_MYSQL: + qrp= MyColumns(g, thd, host, db, user, pwd, tab, + NULL, port, fnc == FNC_COL); + break; + case TAB_CSV: + qrp= CSVColumns(g, dpath, topt, fnc == FNC_COL); + break; +#if defined(_WIN32) + case TAB_WMI: + qrp= WMIColumns(g, nsp, cls, fnc == FNC_COL); + break; +#endif // _WIN32 + case TAB_PRX: + case TAB_TBL: + case TAB_XCL: + case TAB_OCCUR: + bif= fnc == FNC_COL; + qrp= TabColumns(g, thd, db, tab, bif); + + if (!qrp && bif && fnc != FNC_COL) // tab is a view + qrp= MyColumns(g, thd, host, db, user, pwd, tab, NULL, port, false); + + if (qrp && ttp == TAB_OCCUR && fnc != FNC_COL) + if (OcrColumns(g, qrp, col, ocl, rnk)) { + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif OcrColumns + + break; + case TAB_PIVOT: + qrp= PivotColumns(g, tab, src, pic, fcl, skc, host, db, user, pwd, port); + break; + case TAB_VIR: + qrp= VirColumns(g, fnc == FNC_COL); + break; + case TAB_JSON: +#if !defined(FORCE_BSON) + qrp= JSONColumns(g, db, dsn, topt, fnc == FNC_COL); + break; +#endif // !FORCE_BSON +#if defined(BSON_SUPPORT) + case TAB_BSON: + qrp= BSONColumns(g, db, dsn, topt, fnc == FNC_COL); + break; +#endif // BSON_SUPPORT +#if defined(JAVA_SUPPORT) + case TAB_MONGO: + url= strz(g, create_info->connect_string); + qrp= MGOColumns(g, db, url, topt, fnc == FNC_COL); + break; +#endif // JAVA_SUPPORT +#if defined(LIBXML2_SUPPORT) || defined(DOMDOC_SUPPORT) + case TAB_XML: + qrp= XMLColumns(g, (char*)db, tab, topt, fnc == FNC_COL); + break; +#endif // LIBXML2_SUPPORT || DOMDOC_SUPPORT +#if defined(REST_SUPPORT) + case TAB_REST: + qrp = RESTColumns(g, topt, tab, (char *)db, fnc == FNC_COL); + break; +#endif // REST_SUPPORT + case TAB_OEM: + qrp= OEMColumns(g, topt, tab, (char*)db, fnc == FNC_COL); + break; + default: + strcpy(g->Message, "System error during assisted discovery"); + break; + } // endswitch ttp + + if (!qrp) { + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif !qrp + + if (fnc != FNC_NO || src || ttp == TAB_PIVOT) { + // Catalog like table + for (crp= qrp->Colresp; !rc && crp; crp= crp->Next) { + cnm= (ttp == TAB_PIVOT) ? crp->Name : encode(g, crp->Name); + typ= crp->Type; + len= crp->Length; + dec= crp->Prec; + flg= crp->Flag; + v= (crp->Kdata->IsUnsigned()) ? 'U' : crp->Var; + tm= (crp->Kdata->IsNullable()) ? 0 : NOT_NULL_FLAG; + + if (!len && typ == TYPE_STRING) + len= 256; // STRBLK's have 0 length + + // Now add the field + if (add_field(&sql, ttp, cnm, typ, len, dec, NULL, tm, + NULL, NULL, NULL, NULL, flg, dbf, v)) + rc= HA_ERR_OUT_OF_MEM; + } // endfor crp + + } else { + char *schem __attribute__((unused)) = NULL; + char *tn= NULL; + + // Not a catalog table + if (!qrp->Nblin) { + if (tab) + sprintf(g->Message, "Cannot get columns from %s", tab); + else + strcpy(g->Message, "Fail to retrieve columns"); + + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif !nblin + + // Restore language type + if (ttp == TAB_REST) + ttp = ttr; + + for (i= 0; !rc && i < qrp->Nblin; i++) { + typ= len= prec= dec= flg= 0; + tm= NOT_NULL_FLAG; + cnm= (char*)"noname"; + dft= xtra= key= fmt= tn= NULL; + v= ' '; + rem= NULL; + + for (crp= qrp->Colresp; crp; crp= crp->Next) + switch (crp->Fld) { + case FLD_NAME: + if (ttp == TAB_PRX || + (ttp == TAB_CSV && topt->data_charset && + (!stricmp(topt->data_charset, "UTF8") || + !stricmp(topt->data_charset, "UTF-8")))) + cnm= crp->Kdata->GetCharValue(i); + else + cnm= encode(g, crp->Kdata->GetCharValue(i)); + + break; + case FLD_TYPE: + typ= crp->Kdata->GetIntValue(i); + v= (crp->Nulls) ? crp->Nulls[i] : 0; + break; + case FLD_TYPENAME: + tn= crp->Kdata->GetCharValue(i); + break; + case FLD_PREC: + // PREC must be always before LENGTH + len= prec= crp->Kdata->GetIntValue(i); + break; + case FLD_LENGTH: + len= crp->Kdata->GetIntValue(i); + break; + case FLD_SCALE: + dec= (!crp->Kdata->IsNull(i)) ? crp->Kdata->GetIntValue(i) : -1; + break; + case FLD_NULL: + if (crp->Kdata->GetIntValue(i)) + tm= 0; // Nullable + + break; + case FLD_FLAG: + flg = crp->Kdata->GetIntValue(i); + break; + case FLD_FORMAT: + fmt= (crp->Kdata) ? crp->Kdata->GetCharValue(i) : NULL; + break; + case FLD_REM: + rem= crp->Kdata->GetCharValue(i); + break; + // case FLD_CHARSET: + // No good because remote table is already translated + // if (*(csn= crp->Kdata->GetCharValue(i))) + // cs= get_charset_by_name(csn, 0); + + // break; + case FLD_DEFAULT: + dft= crp->Kdata->GetCharValue(i); + break; + case FLD_EXTRA: + xtra= crp->Kdata->GetCharValue(i); + + // Auto_increment is not supported yet + if (!stricmp(xtra, "AUTO_INCREMENT")) + xtra= NULL; + + break; + case FLD_KEY: + if (ttp == TAB_VIR) + key= crp->Kdata->GetCharValue(i); + + break; + case FLD_SCHEM: +#if defined(ODBC_SUPPORT) || defined(JAVA_SUPPORT) + if ((ttp == TAB_ODBC || ttp == TAB_JDBC) && crp->Kdata) { + if (schem && stricmp(schem, crp->Kdata->GetCharValue(i))) { + sprintf(g->Message, + "Several %s tables found, specify DBNAME", tab); + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } else if (!schem) + schem= crp->Kdata->GetCharValue(i); + + } // endif ttp +#endif // ODBC_SUPPORT || JAVA_SUPPORT + default: + break; // Ignore + } // endswitch Fld + +#if defined(ODBC_SUPPORT) + if (ttp == TAB_ODBC) { + int plgtyp; + bool w= false; // Wide character type + + // typ must be PLG type, not SQL type + if (!(plgtyp= TranslateSQLType(typ, dec, prec, v, w))) { + if (GetTypeConv() == TPC_SKIP) { + // Skip this column + sprintf(g->Message, "Column %s skipped (unsupported type %d)", + cnm, typ); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + continue; + } else { + sprintf(g->Message, "Unsupported SQL type %d", typ); + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif type_conv + + } else + typ= plgtyp; + + switch (typ) { + case TYPE_STRING: + if (w) { + sprintf(g->Message, "Column %s is wide characters", cnm); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, 0, g->Message); + } // endif w + + break; + case TYPE_DOUBLE: + // Some data sources do not count dec in length (prec) + prec += (dec + 2); // To be safe + break; + case TYPE_DECIM: + prec= len; + break; + default: + dec= 0; + } // endswitch typ + + } else +#endif // ODBC_SUPPORT +#if defined(JAVA_SUPPORT) + if (ttp == TAB_JDBC) { + int plgtyp; + + // typ must be PLG type, not SQL type + if (!(plgtyp= TranslateJDBCType(typ, tn, dec, prec, v))) { + if (GetTypeConv() == TPC_SKIP) { + // Skip this column + sprintf(g->Message, "Column %s skipped (unsupported type %d)", + cnm, typ); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + continue; + } else { + sprintf(g->Message, "Unsupported SQL type %d", typ); + rc= HA_ERR_INTERNAL_ERROR; + goto err; + } // endif type_conv + + } else + typ= plgtyp; + + switch (typ) { + case TYPE_DOUBLE: + case TYPE_DECIM: + // Some data sources do not count dec in length (prec) + prec += (dec + 2); // To be safe + break; + default: + dec= 0; + } // endswitch typ + + } else +#endif // ODBC_SUPPORT + // Make the arguments as required by add_fields + if (typ == TYPE_DOUBLE) + prec= len; + + if (typ == TYPE_DATE) + prec= 0; + + // Now add the field + if (add_field(&sql, ttp, cnm, typ, prec, dec, key, tm, rem, dft, xtra, + fmt, flg, dbf, v)) + rc= HA_ERR_OUT_OF_MEM; + } // endfor i + + } // endif fnc + + if (!rc) + rc= init_table_share(thd, table_s, create_info, &sql); + + //g->jump_level--; + //PopUser(xp); + //return rc; + } else { + rc= HA_ERR_UNSUPPORTED; + } // endif ok + + } catch (int n) { + if (trace(1)) + htrc("Exception %d: %s\n", n, g->Message); + rc= HA_ERR_INTERNAL_ERROR; + } catch (const char *msg) { + strcpy(g->Message, msg); + rc= HA_ERR_INTERNAL_ERROR; + } // end catch + + err: + if (rc) + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + + PopUser(xp); + return rc; +} // end of connect_assisted_discovery + +/** + Get the database name from a qualified table name. +*/ +char *ha_connect::GetDBfromName(const char *name) +{ + char *db, dbname[128], tbname[128]; + + if (filename_to_dbname_and_tablename(name, dbname, sizeof(dbname), + tbname, sizeof(tbname))) + *dbname= 0; + + if (*dbname) { + assert(xp && xp->g); + db= (char*)PlugSubAlloc(xp->g, NULL, strlen(dbname + 1)); + strcpy(db, dbname); + } else + db= NULL; + + return db; +} // end of GetDBfromName + + +/** + @brief + create() is called to create a database. The variable name will have the name + of the table. + + @details + When create() is called you do not need to worry about + opening the table. Also, the .frm file will have already been + created so adjusting create_info is not necessary. You can overwrite + the .frm file at this point if you wish to change the table + definition, but there are no methods currently provided for doing + so. + + Called from handle.cc by ha_create_table(). + + @note + Currently we do some checking on the create definitions and stop + creating if an error is found. We wish we could change the table + definition such as providing a default table type. However, as said + above, there are no method to do so. + + @see + ha_create_table() in handle.cc +*/ + +int ha_connect::create(const char *name, TABLE *table_arg, + HA_CREATE_INFO *create_info) +{ + int rc= RC_OK; + bool dbf, inward; + Field* *field; + Field *fp; + TABTYPE type; + TABLE *st= table; // Probably unuseful + THD *thd= ha_thd(); + LEX_CSTRING cnc = table_arg->s->connect_string; +#if defined(WITH_PARTITION_STORAGE_ENGINE) + partition_info *part_info= table_arg->part_info; +#else // !WITH_PARTITION_STORAGE_ENGINE +#define part_info 0 +#endif // !WITH_PARTITION_STORAGE_ENGINE + xp= GetUser(thd, xp); + PGLOBAL g= xp->g; + + DBUG_ENTER("ha_connect::create"); + /* + This assignment fixes test failures if some + "ALTER TABLE t1 ADD KEY(a)" query exits on ER_ACCESS_DENIED_ERROR + (e.g. on missing FILE_ACL). All following "CREATE TABLE" failed with + "ERROR 1105: CONNECT index modification should be in-place" + TODO: check with Olivier. + */ + g->Xchk= NULL; + int sqlcom= thd_sql_command(table_arg->in_use); + PTOS options= GetTableOptionStruct(table_arg->s); + + table= table_arg; // Used by called functions + + if (trace(1)) + htrc("create: this=%p thd=%p xp=%p g=%p sqlcom=%d name=%s\n", + this, thd, xp, g, sqlcom, GetTableName()); + + // CONNECT engine specific table options: + DBUG_ASSERT(options); + type= GetTypeID(options->type); + + // Check table type + if (type == TAB_UNDEF) { + options->type= (options->srcdef) ? "MYSQL" : +#if defined(REST_SUPPORT) + (options->http) ? "JSON" : +#endif // REST_SUPPORT + (options->tabname) ? "PROXY" : "DOS"; + type= GetTypeID(options->type); + sprintf(g->Message, "No table_type. Will be set to %s", options->type); + + if (sqlcom == SQLCOM_CREATE_TABLE) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + + } else if (type == TAB_NIY) { + sprintf(g->Message, "Unsupported table type %s", options->type); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif ttp + + if (check_privileges(thd, options, GetDBfromName(name))) + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + + inward= IsFileType(type) && !options->filename && + ((type != TAB_JSON && type != TAB_BSON) || !cnc.length); + + if (options->data_charset) { + const CHARSET_INFO *data_charset; + + if (!(data_charset= get_charset_by_csname(options->data_charset, + MY_CS_PRIMARY, MYF(0)))) { + my_error(ER_UNKNOWN_CHARACTER_SET, MYF(0), options->data_charset); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif charset + + if (type == TAB_XML && data_charset != &my_charset_utf8mb3_general_ci) { + my_printf_error(ER_UNKNOWN_ERROR, + "DATA_CHARSET='%s' is not supported for TABLE_TYPE=XML", + MYF(0), options->data_charset); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif utf8 + + } // endif charset + + if (!g) { + rc= HA_ERR_INTERNAL_ERROR; + DBUG_RETURN(rc); + } else + dbf= (GetTypeID(options->type) == TAB_DBF && !options->catfunc); + + // Can be null in ALTER TABLE + if (create_info->alias.str) + // Check whether a table is defined on itself + switch (type) { + case TAB_PRX: + case TAB_XCL: + case TAB_PIVOT: + case TAB_OCCUR: + if (options->srcdef) { + strcpy(g->Message, "Cannot check looping reference"); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + } else if (options->tabname) { + if (!stricmp(options->tabname, create_info->alias.str) && + (!options->dbname || + !stricmp(options->dbname, table_arg->s->db.str))) { + sprintf(g->Message, "A %s table cannot refer to itself", + options->type); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif tab + + } else { + strcpy(g->Message, "Missing object table name or definition"); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif tabname + + // fall through + case TAB_MYSQL: + if (!part_info) + {const char *src= options->srcdef; + PCSZ host, db, tab= options->tabname; + int port; + + host= GetListOption(g, "host", options->oplist, NULL); + db= GetStringOption("database", NULL); + port= atoi(GetListOption(g, "port", options->oplist, "0")); + + if (create_info->connect_string.str && + create_info->connect_string.length) { + char *dsn= strz(g, create_info->connect_string); + PMYDEF mydef= new(g) MYSQLDEF(); + + mydef->SetName(create_info->alias.str); + + if (!mydef->ParseURL(g, dsn, false)) { + if (mydef->GetHostname()) + host= mydef->GetHostname(); + + if (mydef->GetTabschema()) + db= mydef->GetTabschema(); + + if (mydef->GetTabname()) + tab= mydef->GetTabname(); + + if (mydef->GetPortnumber()) + port= mydef->GetPortnumber(); + + } else { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif ParseURL + + } // endif connect_string + + if (CheckSelf(g, table_arg->s, host, db, tab, src, port)) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif CheckSelf + + } break; + default: /* do nothing */; + break; + } // endswitch ttp + + if (type == TAB_XML) { + bool dom; // True: MS-DOM, False libxml2 + PCSZ xsup= GetListOption(g, "Xmlsup", options->oplist, "*"); + + // Note that if no support is specified, the default is MS-DOM + // on Windows and libxml2 otherwise + switch (toupper(*xsup)) { + case '*': +#if defined(_WIN32) + dom= true; +#else // !_WIN32 + dom= false; +#endif // !_WIN32 + break; + case 'M': + case 'D': + dom= true; + break; + default: + dom= false; + break; + } // endswitch xsup + +#if !defined(DOMDOC_SUPPORT) + if (dom) { + strcpy(g->Message, "MS-DOM not supported by this version"); + xsup= NULL; + } // endif DomDoc +#endif // !DOMDOC_SUPPORT + +#if !defined(LIBXML2_SUPPORT) + if (!dom) { + strcpy(g->Message, "libxml2 not supported by this version"); + xsup= NULL; + } // endif Libxml2 +#endif // !LIBXML2_SUPPORT + + if (!xsup) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + DBUG_RETURN(rc); + } // endif xsup + + } // endif type + + if (type == TAB_JSON) { + int pretty= atoi(GetListOption(g, "Pretty", options->oplist, "2")); + + if (!options->lrecl && pretty != 2) { + sprintf(g->Message, "LRECL must be specified for pretty=%d", pretty); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + DBUG_RETURN(rc); + } // endif lrecl + + } // endif type JSON + + if (type == TAB_CSV) { + const char *sep= options->separator; + + if (sep && strlen(sep) > 1) { + sprintf(g->Message, "Invalid separator %s", sep); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + DBUG_RETURN(rc); + } // endif sep + + } // endif type CSV + + // Check column types + for (field= table_arg->field; *field; field++) { + fp= *field; + + if (fp->vcol_info && !fp->stored_in_db) + continue; // This is a virtual column + + if (fp->flags & AUTO_INCREMENT_FLAG) { + strcpy(g->Message, "Auto_increment is not supported yet"); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + DBUG_RETURN(rc); + } // endif flags + + if (fp->flags & (BLOB_FLAG | ENUM_FLAG | SET_FLAG)) { + sprintf(g->Message, "Unsupported type for column %s", + fp->field_name.str); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + DBUG_RETURN(rc); + } // endif flags + + if (type == TAB_VIR) + if (!fp->option_struct || !fp->option_struct->special) { + strcpy(g->Message, "Virtual tables accept only special or virtual columns"); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + DBUG_RETURN(rc); + } // endif special + + switch (fp->type()) { + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_INT24: + break; // Ok + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: +#if 0 + if (!fp->field_length) { + sprintf(g->Message, "Unsupported 0 length for column %s", + fp->field_name.str); + rc= HA_ERR_INTERNAL_ERROR; + my_printf_error(ER_UNKNOWN_ERROR, + "Unsupported 0 length for column %s", + MYF(0), fp->field_name.str); + DBUG_RETURN(rc); + } // endif fp +#endif // 0 + break; // To be checked + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_NULL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_GEOMETRY: + default: +// fprintf(stderr, "Unsupported type column %s\n", fp->field_name.str); + sprintf(g->Message, "Unsupported type for column %s", + fp->field_name.str); + rc= HA_ERR_INTERNAL_ERROR; + my_printf_error(ER_UNKNOWN_ERROR, "Unsupported type for column %s", + MYF(0), fp->field_name.str); + DBUG_RETURN(rc); + break; + } // endswitch type + + if ((fp)->real_maybe_null() && !IsTypeNullable(type)) { + my_printf_error(ER_UNKNOWN_ERROR, + "Table type %s does not support nullable columns", + MYF(0), options->type); + DBUG_RETURN(HA_ERR_UNSUPPORTED); + } // endif !nullable + + if (dbf) { + bool b= false; + + if ((b= fp->field_name.length > 10)) + sprintf(g->Message, "DBF: Column name '%s' is too long (max=10)", + fp->field_name.str); + else if ((b= fp->field_length > 255)) + sprintf(g->Message, "DBF: Column length too big for '%s' (max=255)", + fp->field_name.str); + + if (b) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + DBUG_RETURN(rc); + } // endif b + + } // endif dbf + + } // endfor field + + if ((sqlcom == SQLCOM_CREATE_TABLE || *GetTableName() == '#') && inward) { + // The file name is not specified, create a default file in + // the database directory named table_name.table_type. + // (temporarily not done for XML because a void file causes + // the XML parsers to report an error on the first Insert) + char buf[_MAX_PATH], fn[_MAX_PATH], dbpath[_MAX_PATH], lwt[12]; + int h; + + // Check for incompatible options + if (options->sepindex) { + my_message(ER_UNKNOWN_ERROR, + "SEPINDEX is incompatible with unspecified file name", MYF(0)); + DBUG_RETURN(HA_ERR_UNSUPPORTED); + } else if (GetTypeID(options->type) == TAB_VEC) { + if (!table->s->max_rows || options->split) { + my_printf_error(ER_UNKNOWN_ERROR, + "%s tables whose file name is unspecified cannot be split", + MYF(0), options->type); + DBUG_RETURN(HA_ERR_UNSUPPORTED); + } else if (options->header == 2) { + my_printf_error(ER_UNKNOWN_ERROR, + "header=2 is not allowed for %s tables whose file name is unspecified", + MYF(0), options->type); + DBUG_RETURN(HA_ERR_UNSUPPORTED); + } // endif's + + } else if (options->zipped) { + my_message(ER_UNKNOWN_ERROR, + "ZIPPED is incompatible with unspecified file name", MYF(0)); + DBUG_RETURN(HA_ERR_UNSUPPORTED); + } // endif's options + + // Fold type to lower case + for (int i= 0; i < 12; i++) + if (!options->type[i]) { + lwt[i]= 0; + break; + } else + lwt[i]= tolower(options->type[i]); + + if (part_info) { + char *p; + + strcpy(dbpath, name); + p= strrchr(dbpath, slash); + strncpy(partname, ++p, sizeof(partname) - 1); + strcat(strcat(strcpy(buf, p), "."), lwt); + *p= 0; + } else { + strcat(strcat(strcpy(buf, GetTableName()), "."), lwt); + sprintf(g->Message, "No file name. Table will use %s", buf); + + if (sqlcom == SQLCOM_CREATE_TABLE) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + + strcat(strcat(strcpy(dbpath, "./"), table->s->db.str), "/"); + } // endif part_info + + PlugSetPath(fn, buf, dbpath); + + if ((h= ::open(fn, O_CREAT | O_EXCL, 0666)) == -1) { + if (errno == EEXIST) + sprintf(g->Message, "Default file %s already exists", fn); + else + sprintf(g->Message, "Error %d creating file %s", errno, fn); + + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + } else + ::close(h); + + if ((type == TAB_FMT || options->readonly) && sqlcom == SQLCOM_CREATE_TABLE) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, + "Congratulation, you just created a read-only void table!"); + + } // endif sqlcom + + if (trace(1)) + htrc("xchk=%p createas=%d\n", g->Xchk, g->Createas); + + if (options->zipped) { +#if defined(ZIP_SUPPORT) + // Check whether the zip entry must be made from a file + PCSZ fn= GetListOption(g, "Load", options->oplist, NULL); + + if (fn) { + char zbuf[_MAX_PATH], buf[_MAX_PATH], dbpath[_MAX_PATH]; + PCSZ entry= GetListOption(g, "Entry", options->oplist, NULL); + PCSZ a= GetListOption(g, "Append", options->oplist, "NO"); + bool append= *a == '1' || *a == 'Y' || *a == 'y' || !stricmp(a, "ON"); + PCSZ m= GetListOption(g, "Mulentries", options->oplist, "NO"); + bool mul= *m == '1' || *m == 'Y' || *m == 'y' || !stricmp(m, "ON"); + + strcat(strcat(strcpy(dbpath, "./"), table->s->db.str), "/"); + PlugSetPath(zbuf, options->filename, dbpath); + PlugSetPath(buf, fn, dbpath); + + if (ZipLoadFile(g, zbuf, buf, entry, append, mul)) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif LoadFile + + } // endif fn +#else // !ZIP_SUPPORT + my_message(ER_UNKNOWN_ERROR, "Option ZIP not supported", MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); +#endif // !ZIP_SUPPORT + } // endif zipped + + // To check whether indexes have to be made or remade + if (!g->Xchk) { + PIXDEF xdp; + + // We should be in CREATE TABLE, ALTER_TABLE or CREATE INDEX + if (!(sqlcom == SQLCOM_CREATE_TABLE || sqlcom == SQLCOM_ALTER_TABLE || + sqlcom == SQLCOM_CREATE_INDEX || sqlcom == SQLCOM_DROP_INDEX)) +// (sqlcom == SQLCOM_CREATE_INDEX && part_info) || +// (sqlcom == SQLCOM_DROP_INDEX && part_info))) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, + "Unexpected command in create, please contact CONNECT team"); + + if (part_info && !inward) + strncpy(partname, decode(g, strrchr(name, '#') + 1), sizeof(partname) - 1); +// strcpy(partname, part_info->curr_part_elem->partition_name); + + if (g->Alchecked == 0 && + (!IsFileType(type) || FileExists(options->filename, false))) { + if (part_info) { + sprintf(g->Message, "Data repartition in %s is unchecked", partname); + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, g->Message); + } else if (sqlcom == SQLCOM_ALTER_TABLE) { + // This is an ALTER to CONNECT from another engine. + // It cannot be accepted because the table data would be modified + // except when the target file does not exist. + strcpy(g->Message, "Operation denied. Table data would be modified."); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ERR_INTERNAL_ERROR); + } // endif part_info + + } // endif outward + + // Get the index definitions + if ((xdp= GetIndexInfo()) || sqlcom == SQLCOM_DROP_INDEX) { + if (options->multiple) { + strcpy(g->Message, "Multiple tables are not indexable"); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_UNSUPPORTED; + } else if (options->compressed) { + strcpy(g->Message, "Compressed tables are not indexable"); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_UNSUPPORTED; + } else if (GetIndexType(type) == 1) { + PDBUSER dup= PlgGetUser(g); + PCATLG cat= (dup) ? dup->Catalog : NULL; + + if (SetDataPath(g, table_arg->s->db.str)) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + } else if (cat) { + if (part_info) + strncpy(partname, + decode(g, strrchr(name, (inward ? slash : '#')) + 1), + sizeof(partname) - 1); + + if ((rc= optimize(table->in_use, NULL))) { + htrc("Create rc=%d %s\n", rc, g->Message); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_INTERNAL_ERROR; + } else + CloseTable(g); + + } // endif cat + + } else if (GetIndexType(type) == 3) { + if (CheckVirtualIndex(table_arg->s)) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_UNSUPPORTED; + } // endif Check + + } else if (!GetIndexType(type)) { + sprintf(g->Message, "Table type %s is not indexable", options->type); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + rc= HA_ERR_UNSUPPORTED; + } // endif index type + + } // endif xdp + + } else { + // This should not happen anymore with indexing new way + my_message(ER_UNKNOWN_ERROR, + "CONNECT index modification should be in-place", MYF(0)); + DBUG_RETURN(HA_ERR_UNSUPPORTED); + } // endif Xchk + + table= st; + DBUG_RETURN(rc); +} // end of create + +/** + Used to check whether a file based outward table can be populated by + an ALTER TABLE command. The conditions are: + - file does not exist or is void + - user has file privilege +*/ +bool ha_connect::FileExists(const char *fn, bool bf) +{ + if (!fn || !*fn) + return false; + else if (IsPartitioned() && bf) + return true; + + if (table) { + const char *s; + char tfn[_MAX_PATH], filename[_MAX_PATH], path[_MAX_PATH]; + bool b= false; + int n; + struct stat info; + +#if defined(_WIN32) + s= "\\"; +#else // !_WIN32 + s= "/"; +#endif // !_WIN32 + if (IsPartitioned()) { + sprintf(tfn, fn, GetPartName()); + + // This is to avoid an initialization error raised by the + // test on check_table_flags made in ha_partition::open + // that can fail if some partition files are empty. + b= true; + } else + strcpy(tfn, fn); + + strcat(strcat(strcat(strcpy(path, "."), s), table->s->db.str), s); + PlugSetPath(filename, tfn, path); + n= stat(filename, &info); + + if (n < 0) { + if (errno != ENOENT) { + char buf[_MAX_PATH + 20]; + + sprintf(buf, "Error %d for file %s", errno, filename); + push_warning(table->in_use, Sql_condition::WARN_LEVEL_WARN, 0, buf); + return true; + } else + return false; + + } else + return (info.st_size || b) ? true : false; + + } // endif table + + return true; +} // end of FileExists + +// Called by SameString and NoFieldOptionChange +bool ha_connect::CheckString(PCSZ str1, PCSZ str2) +{ + bool b1= (!str1 || !*str1), b2= (!str2 || !*str2); + + if (b1 && b2) + return true; + else if ((b1 && !b2) || (!b1 && b2) || stricmp(str1, str2)) + return false; + + return true; +} // end of CheckString + +/** + check whether a string option have changed + */ +bool ha_connect::SameString(TABLE *tab, PCSZ opn) +{ + PCSZ str1, str2; + + tshp= tab->s; // The altered table + str1= GetStringOption(opn); + tshp= NULL; + str2= GetStringOption(opn); + return CheckString(str1, str2); +} // end of SameString + +/** + check whether a Boolean option have changed + */ +bool ha_connect::SameBool(TABLE *tab, PCSZ opn) +{ + bool b1, b2; + + tshp= tab->s; // The altered table + b1= GetBooleanOption(opn, false); + tshp= NULL; + b2= GetBooleanOption(opn, false); + return (b1 == b2); +} // end of SameBool + +/** + check whether an integer option have changed + */ +bool ha_connect::SameInt(TABLE *tab, PCSZ opn) +{ + int i1, i2; + + tshp= tab->s; // The altered table + i1= GetIntegerOption(opn); + tshp= NULL; + i2= GetIntegerOption(opn); + + if (!stricmp(opn, "lrecl")) + return (i1 == i2 || !i1 || !i2); + else if (!stricmp(opn, "ending")) + return (i1 == i2 || i1 <= 0 || i2 <= 0); + else + return (i1 == i2); + +} // end of SameInt + +/** + check whether a field option have changed + */ +bool ha_connect::NoFieldOptionChange(TABLE *tab) +{ + bool rc= true; + ha_field_option_struct *fop1, *fop2; + Field* *fld1= table->s->field; + Field* *fld2= tab->s->field; + + for (; rc && *fld1 && *fld2; fld1++, fld2++) { + fop1= (*fld1)->option_struct; + fop2= (*fld2)->option_struct; + + rc= (fop1->offset == fop2->offset && + fop1->fldlen == fop2->fldlen && + CheckString(fop1->dateformat, fop2->dateformat) && + CheckString(fop1->fieldformat, fop2->fieldformat) && + CheckString(fop1->special, fop2->special)); + } // endfor fld + + return rc; +} // end of NoFieldOptionChange + + /** + Check if a storage engine supports a particular alter table in-place + + @param altered_table TABLE object for new version of table. + @param ha_alter_info Structure describing changes to be done + by ALTER TABLE and holding data used + during in-place alter. + + @retval HA_ALTER_ERROR Unexpected error. + @retval HA_ALTER_INPLACE_NOT_SUPPORTED Not supported, must use copy. + @retval HA_ALTER_INPLACE_EXCLUSIVE_LOCK Supported, but requires X lock. + @retval HA_ALTER_INPLACE_COPY_LOCK + Supported, but requires SNW lock + during main phase. Prepare phase + requires X lock. + @retval HA_ALTER_INPLACE_SHARED_LOCK Supported, but requires SNW lock. + @retval HA_ALTER_INPLACE_COPY_NO_LOCK + Supported, concurrent reads/writes + allowed. However, prepare phase + requires X lock. + @retval HA_ALTER_INPLACE_NO_LOCK Supported, concurrent + reads/writes allowed. + + @note The default implementation uses the old in-place ALTER API + to determine if the storage engine supports in-place ALTER or not. + + @note Called without holding thr_lock.c lock. + */ +enum_alter_inplace_result +ha_connect::check_if_supported_inplace_alter(TABLE *altered_table, + Alter_inplace_info *ha_alter_info) +{ + DBUG_ENTER("check_if_supported_alter"); + + bool idx= false, outward= false; + THD *thd= ha_thd(); + int sqlcom= thd_sql_command(thd); + TABTYPE newtyp, type= TAB_UNDEF; + HA_CREATE_INFO *create_info= ha_alter_info->create_info; + PTOS newopt, oldopt; + xp= GetUser(thd, xp); + PGLOBAL g= xp->g; + + if (!g || !table) { + my_message(ER_UNKNOWN_ERROR, "Cannot check ALTER operations", MYF(0)); + DBUG_RETURN(HA_ALTER_ERROR); + } // endif Xchk + + newopt= altered_table->s->option_struct; + oldopt= table->s->option_struct; + + // If this is the start of a new query, cleanup the previous one + if (xp->CheckCleanup()) { + tdbp= NULL; + valid_info= false; + } // endif CheckCleanup + + g->Alchecked= 1; // Tested in create + g->Xchk= NULL; + type= GetRealType(oldopt); + newtyp= GetRealType(newopt); + + // No copy algorithm for outward tables + outward= (!IsFileType(type) || (oldopt->filename && *oldopt->filename)); + + // Index operations + alter_table_operations index_operations= + ALTER_ADD_INDEX | + ALTER_DROP_INDEX | + ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX | + ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX | + ALTER_ADD_UNIQUE_INDEX | + ALTER_DROP_UNIQUE_INDEX | + ALTER_ADD_PK_INDEX | + ALTER_DROP_PK_INDEX; + + alter_table_operations inplace_offline_operations= + ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE | + ALTER_COLUMN_NAME | + ALTER_COLUMN_DEFAULT | + ALTER_CHANGE_CREATE_OPTION | + ALTER_RENAME | + ALTER_PARTITIONED | index_operations; + + if (ha_alter_info->handler_flags & index_operations || + !SameString(altered_table, "optname") || + !SameBool(altered_table, "sepindex")) { + if (newopt->multiple) { + strcpy(g->Message, "Multiple tables are not indexable"); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ALTER_ERROR); + } else if (newopt->compressed) { + strcpy(g->Message, "Compressed tables are not indexable"); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ALTER_ERROR); + } else if (GetIndexType(type) == 1) { + g->Xchk= new(g) XCHK; + PCHK xcp= (PCHK)g->Xchk; + + xcp->oldpix= GetIndexInfo(table->s); + xcp->newpix= GetIndexInfo(altered_table->s); + xcp->oldsep= GetBooleanOption("sepindex", false); + xcp->oldsep= xcp->SetName(g, GetStringOption("optname")); + tshp= altered_table->s; + xcp->newsep= GetBooleanOption("sepindex", false); + xcp->newsep= xcp->SetName(g, GetStringOption("optname")); + tshp= NULL; + + if (trace(1) && g->Xchk) + htrc( + "oldsep=%d newsep=%d oldopn=%s newopn=%s oldpix=%p newpix=%p\n", + xcp->oldsep, xcp->newsep, + SVP(xcp->oldopn), SVP(xcp->newopn), + xcp->oldpix, xcp->newpix); + + if (sqlcom == SQLCOM_ALTER_TABLE) + idx= true; + else + DBUG_RETURN(HA_ALTER_INPLACE_EXCLUSIVE_LOCK); + + } else if (GetIndexType(type) == 3) { + if (CheckVirtualIndex(altered_table->s)) { + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ALTER_ERROR); + } // endif Check + + } else if (!GetIndexType(type)) { + sprintf(g->Message, "Table type %s is not indexable", oldopt->type); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ALTER_ERROR); + } // endif index type + + } // endif index operation + + if (!SameString(altered_table, "filename")) { + if (!outward) { + // Conversion to outward table is only allowed for file based + // tables whose file does not exist. + tshp= altered_table->s; + PCSZ fn= GetStringOption("filename"); + tshp= NULL; + + if (FileExists(fn, false)) { + strcpy(g->Message, "Operation denied. Table data would be lost."); + my_message(ER_UNKNOWN_ERROR, g->Message, MYF(0)); + DBUG_RETURN(HA_ALTER_ERROR); + } else + goto fin; + + } else + goto fin; + + } // endif filename + + /* Is there at least one operation that requires copy algorithm? */ + if (ha_alter_info->handler_flags & ~inplace_offline_operations) + goto fin; + + /* + ALTER TABLE tbl_name CONVERT TO CHARACTER SET .. and + ALTER TABLE table_name DEFAULT CHARSET= .. most likely + change column charsets and so not supported in-place through + old API. + + Changing of PACK_KEYS, MAX_ROWS and ROW_FORMAT options were + not supported as in-place operations in old API either. + */ + if (create_info->used_fields & (HA_CREATE_USED_CHARSET | + HA_CREATE_USED_DEFAULT_CHARSET | + HA_CREATE_USED_PACK_KEYS | + HA_CREATE_USED_MAX_ROWS) || + (table->s->row_type != create_info->row_type)) + goto fin; + +#if 0 + uint table_changes= (ha_alter_info->handler_flags & + ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE) ? + IS_EQUAL_PACK_LENGTH : IS_EQUAL_YES; + + if (table->file->check_if_incompatible_data(create_info, table_changes) + == COMPATIBLE_DATA_YES) + DBUG_RETURN(HA_ALTER_INPLACE_EXCLUSIVE_LOCK); +#endif // 0 + + // This was in check_if_incompatible_data + if (NoFieldOptionChange(altered_table) && + type == newtyp && + SameInt(altered_table, "lrecl") && + SameInt(altered_table, "elements") && + SameInt(altered_table, "header") && + SameInt(altered_table, "quoted") && + SameInt(altered_table, "ending") && + SameInt(altered_table, "compressed")) + DBUG_RETURN(HA_ALTER_INPLACE_EXCLUSIVE_LOCK); + +fin: + if (idx) { + // Indexing is only supported inplace + my_message(ER_ALTER_OPERATION_NOT_SUPPORTED, + "Alter operations not supported together by CONNECT", MYF(0)); + DBUG_RETURN(HA_ALTER_ERROR); + } else if (outward) { + if (IsFileType(type)) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, 0, + "This is an outward table, table data were not modified."); + + DBUG_RETURN(HA_ALTER_INPLACE_EXCLUSIVE_LOCK); + } else + DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED); + +} // end of check_if_supported_inplace_alter + + +/** + check_if_incompatible_data() called if ALTER TABLE can't detect otherwise + if new and old definition are compatible + + @details If there are no other explicit signs like changed number of + fields this function will be called by compare_tables() + (sql/sql_tables.cc) to decide should we rewrite whole table or only .frm + file. + + @note: This function is no more called by check_if_supported_inplace_alter +*/ + +bool ha_connect::check_if_incompatible_data(HA_CREATE_INFO *, uint) +{ + DBUG_ENTER("ha_connect::check_if_incompatible_data"); + // TO DO: really implement and check it. + push_warning(ha_thd(), Sql_condition::WARN_LEVEL_WARN, 0, + "Unexpected call to check_if_incompatible_data."); + DBUG_RETURN(COMPATIBLE_DATA_NO); +} // end of check_if_incompatible_data + +/**************************************************************************** + * CONNECT MRR implementation: use DS-MRR + This is just copied from myisam + ***************************************************************************/ + +int ha_connect::multi_range_read_init(RANGE_SEQ_IF *seq, void *seq_init_param, + uint n_ranges, uint mode, + HANDLER_BUFFER *buf) +{ + return ds_mrr.dsmrr_init(this, seq, seq_init_param, n_ranges, mode, buf); +} // end of multi_range_read_init + +int ha_connect::multi_range_read_next(range_id_t *range_info) +{ + return ds_mrr.dsmrr_next(range_info); +} // end of multi_range_read_next + +ha_rows ha_connect::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq, + void *seq_init_param, + uint n_ranges, uint *bufsz, + uint *flags, Cost_estimate *cost) +{ + /* + This call is here because there is no location where this->table would + already be known. + TODO: consider moving it into some per-query initialization call. + */ + ds_mrr.init(this, table); + + // MMR is implemented for "local" file based tables only + if (!IsFileType(GetRealType(GetTableOptionStruct()))) + *flags|= HA_MRR_USE_DEFAULT_IMPL; + + ha_rows rows= ds_mrr.dsmrr_info_const(keyno, seq, seq_init_param, n_ranges, + bufsz, flags, cost); + xp->g->Mrr= !(*flags & HA_MRR_USE_DEFAULT_IMPL); + return rows; +} // end of multi_range_read_info_const + +ha_rows ha_connect::multi_range_read_info(uint keyno, uint n_ranges, uint keys, + uint key_parts, uint *bufsz, + uint *flags, Cost_estimate *cost) +{ + ds_mrr.init(this, table); + + // MMR is implemented for "local" file based tables only + if (!IsFileType(GetRealType(GetTableOptionStruct()))) + *flags|= HA_MRR_USE_DEFAULT_IMPL; + + ha_rows rows= ds_mrr.dsmrr_info(keyno, n_ranges, keys, key_parts, bufsz, + flags, cost); + xp->g->Mrr= !(*flags & HA_MRR_USE_DEFAULT_IMPL); + return rows; +} // end of multi_range_read_info + + +int ha_connect::multi_range_read_explain_info(uint mrr_mode, char *str, + size_t size) +{ + return ds_mrr.dsmrr_explain_info(mrr_mode, str, size); +} // end of multi_range_read_explain_info + +/* CONNECT MRR implementation ends */ + +#if 0 +// Does this make sens for CONNECT? +Item *ha_connect::idx_cond_push(uint keyno_arg, Item* idx_cond_arg) +{ + pushed_idx_cond_keyno= keyno_arg; + pushed_idx_cond= idx_cond_arg; + in_range_check_pushed_down= TRUE; + if (active_index == pushed_idx_cond_keyno) + mi_set_index_cond_func(file, handler_index_cond_check, this); + return NULL; +} +#endif // 0 + + +struct st_mysql_storage_engine connect_storage_engine= +{ MYSQL_HANDLERTON_INTERFACE_VERSION }; + +/***********************************************************************/ +/* CONNECT global variables definitions. */ +/***********************************************************************/ +#if defined(XMAP) +// Using file mapping for indexes if true +static MYSQL_SYSVAR_BOOL(indx_map, xmap, PLUGIN_VAR_RQCMDARG, + "Using file mapping for indexes", NULL, NULL, 0); +#endif // XMAP + +#if defined(XMSG) +static MYSQL_SYSVAR_STR(errmsg_dir_path, msg_path, +// PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path to the directory where are the message files", +// check_msg_path, update_msg_path, + NULL, NULL, + "../../../../storage/connect/"); // for testing +#endif // XMSG + +#if defined(JAVA_SUPPORT) +static MYSQL_SYSVAR_STR(jvm_path, JvmPath, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC, + "Path to the directory where is the JVM lib", + // check_jvm_path, update_jvm_path, + NULL, NULL, NULL); + +static MYSQL_SYSVAR_STR(class_path, ClassPath, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC, + "Java class path", + // check_class_path, update_class_path, + NULL, NULL, NULL); +#endif // JAVA_SUPPORT + + +static struct st_mysql_sys_var* connect_system_variables[]= { + MYSQL_SYSVAR(xtrace), + MYSQL_SYSVAR(conv_size), + MYSQL_SYSVAR(type_conv), +#if defined(XMAP) + MYSQL_SYSVAR(indx_map), +#endif // XMAP + MYSQL_SYSVAR(work_size), + MYSQL_SYSVAR(use_tempfile), + MYSQL_SYSVAR(exact_info), +#if defined(XMSG) || defined(NEWMSG) + MYSQL_SYSVAR(msg_lang), +#endif // XMSG || NEWMSG +#if defined(XMSG) + MYSQL_SYSVAR(errmsg_dir_path), +#endif // XMSG + MYSQL_SYSVAR(json_null), + MYSQL_SYSVAR(json_all_path), + MYSQL_SYSVAR(default_depth), + MYSQL_SYSVAR(default_prec), + MYSQL_SYSVAR(json_grp_size), +#if defined(JAVA_SUPPORT) + MYSQL_SYSVAR(jvm_path), + MYSQL_SYSVAR(class_path), + MYSQL_SYSVAR(java_wrapper), +#endif // JAVA_SUPPORT +#if defined(JAVA_SUPPORT) || defined(CMGO_SUPPORT) + MYSQL_SYSVAR(enable_mongo), +#endif // JAVA_SUPPORT || CMGO_SUPPORT + MYSQL_SYSVAR(cond_push), +#if defined(BSON_SUPPORT) + MYSQL_SYSVAR(force_bson), +#endif // BSON_SUPPORT + NULL +}; + +maria_declare_plugin(connect) +{ + MYSQL_STORAGE_ENGINE_PLUGIN, + &connect_storage_engine, + "CONNECT", + "Olivier Bertrand", + "Management of External Data (SQL/NOSQL/MED), including Rest query results", + PLUGIN_LICENSE_GPL, + connect_init_func, /* Plugin Init */ + connect_done_func, /* Plugin Deinit */ + 0x0107, /* version number (1.07) */ + NULL, /* status variables */ + connect_system_variables, /* system variables */ + "1.07.0003", /* string version */ + MariaDB_PLUGIN_MATURITY_STABLE /* maturity */ +} +maria_declare_plugin_end; |