mysql5/mysql-5.7.27/sql/ha_ndbinfo.cc

883 lines
23 KiB
C++

/*
Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
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 St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ha_ndbcluster_glue.h"
#include "ha_ndbinfo.h"
#include "ndb_tdc.h"
#include "../storage/ndb/src/ndbapi/NdbInfo.hpp"
static MYSQL_THDVAR_UINT(
max_rows, /* name */
PLUGIN_VAR_RQCMDARG,
"Specify max number of rows to fetch per roundtrip to cluster",
NULL, /* check func. */
NULL, /* update func. */
10, /* default */
1, /* min */
256, /* max */
0 /* block */
);
static MYSQL_THDVAR_UINT(
max_bytes, /* name */
PLUGIN_VAR_RQCMDARG,
"Specify approx. max number of bytes to fetch per roundtrip to cluster",
NULL, /* check func. */
NULL, /* update func. */
0, /* default */
0, /* min */
65535, /* max */
0 /* block */
);
static MYSQL_THDVAR_BOOL(
show_hidden, /* name */
PLUGIN_VAR_RQCMDARG,
"Control if tables should be visible or not",
NULL, /* check func. */
NULL, /* update func. */
FALSE /* default */
);
static char* opt_ndbinfo_dbname = (char*)"ndbinfo";
static MYSQL_SYSVAR_STR(
database, /* name */
opt_ndbinfo_dbname, /* var */
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
"Name of the database used by ndbinfo",
NULL, /* check func. */
NULL, /* update func. */
NULL /* default */
);
static char* opt_ndbinfo_table_prefix = (char*)"ndb$";
static MYSQL_SYSVAR_STR(
table_prefix, /* name */
opt_ndbinfo_table_prefix, /* var */
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
"Prefix to use for all virtual tables loaded from NDB",
NULL, /* check func. */
NULL, /* update func. */
NULL /* default */
);
static Uint32 opt_ndbinfo_version = NDB_VERSION_D;
static MYSQL_SYSVAR_UINT(
version, /* name */
opt_ndbinfo_version, /* var */
PLUGIN_VAR_NOCMDOPT | PLUGIN_VAR_READONLY,
"Compile version for ndbinfo",
NULL, /* check func. */
NULL, /* update func. */
0, /* default */
0, /* min */
0, /* max */
0 /* block */
);
static my_bool opt_ndbinfo_offline;
static
void
offline_update(THD* thd, struct st_mysql_sys_var* var,
void* var_ptr, const void* save)
{
DBUG_ENTER("offline_update");
const my_bool new_offline =
(*(static_cast<const my_bool*>(save)) != 0);
if (new_offline == opt_ndbinfo_offline)
{
// No change
DBUG_VOID_RETURN;
}
// Set offline mode, any tables opened from here on will
// be opened in the new mode
opt_ndbinfo_offline = new_offline;
// Close any open tables which may be in the old mode
(void)ndb_tdc_close_cached_tables();
DBUG_VOID_RETURN;
}
static MYSQL_SYSVAR_BOOL(
offline, /* name */
opt_ndbinfo_offline, /* var */
PLUGIN_VAR_NOCMDOPT,
"Set ndbinfo in offline mode, tables and views can "
"be opened even if they don't exist or have different "
"definition in NDB. No rows will be returned.",
NULL, /* check func. */
offline_update, /* update func. */
0 /* default */
);
static NdbInfo* g_ndbinfo;
extern Ndb_cluster_connection* g_ndb_cluster_connection;
static bool
ndbcluster_is_disabled(void)
{
/*
ndbinfo uses the same connection as ndbcluster
to avoid using up another nodeid, this also means that
if ndbcluster is not enabled, ndbinfo won't start
*/
if (g_ndb_cluster_connection)
return false;
assert(g_ndbinfo == NULL);
return true;
}
static handler*
create_handler(handlerton *hton, TABLE_SHARE *table, MEM_ROOT *mem_root)
{
return new (mem_root) ha_ndbinfo(hton, table);
}
struct ha_ndbinfo_impl
{
const NdbInfo::Table* m_table;
NdbInfoScanOperation* m_scan_op;
Vector<const NdbInfoRecAttr *> m_columns;
bool m_first_use;
// Indicates if table has been opened in offline mode
// can only be reset by closing the table
bool m_offline;
ha_ndbinfo_impl() :
m_table(NULL),
m_scan_op(NULL),
m_first_use(true),
m_offline(false)
{
}
};
ha_ndbinfo::ha_ndbinfo(handlerton *hton, TABLE_SHARE *table_arg)
: handler(hton, table_arg), m_impl(*new ha_ndbinfo_impl)
{
}
ha_ndbinfo::~ha_ndbinfo()
{
delete &m_impl;
}
enum ndbinfo_error_codes {
ERR_INCOMPAT_TABLE_DEF = 40001
};
static
struct error_message {
int error;
const char* message;
} error_messages[] = {
{ ERR_INCOMPAT_TABLE_DEF, "Incompatible table definitions" },
{ HA_ERR_NO_CONNECTION, "Connection to NDB failed" },
{ 0, 0 }
};
static
const char* find_error_message(int error)
{
struct error_message* err = error_messages;
while (err->error && err->message)
{
if (err->error == error)
{
assert(err->message);
return err->message;
}
err++;
}
return NULL;
}
static int err2mysql(int error)
{
DBUG_ENTER("err2mysql");
DBUG_PRINT("enter", ("error: %d", error));
assert(error != 0);
switch(error)
{
case NdbInfo::ERR_ClusterFailure:
DBUG_RETURN(HA_ERR_NO_CONNECTION);
break;
case NdbInfo::ERR_OutOfMemory:
DBUG_RETURN(HA_ERR_OUT_OF_MEM);
break;
default:
break;
}
push_warning_printf(current_thd, Sql_condition::SL_WARNING,
ER_GET_ERRNO, ER(ER_GET_ERRNO), error);
DBUG_RETURN(HA_ERR_INTERNAL_ERROR);
}
bool ha_ndbinfo::get_error_message(int error, String *buf)
{
DBUG_ENTER("ha_ndbinfo::get_error_message");
DBUG_PRINT("enter", ("error: %d", error));
const char* message = find_error_message(error);
if (!message)
DBUG_RETURN(false);
buf->set(message, (uint32)strlen(message), &my_charset_bin);
DBUG_PRINT("exit", ("message: %s", buf->ptr()));
DBUG_RETURN(false);
}
static void
generate_sql(const NdbInfo::Table* ndb_tab, BaseString& sql)
{
sql.appfmt("'CREATE TABLE `%s`.`%s%s` (",
opt_ndbinfo_dbname, opt_ndbinfo_table_prefix, ndb_tab->getName());
const char* separator = "";
for (unsigned i = 0; i < ndb_tab->columns(); i++)
{
const NdbInfo::Column* col = ndb_tab->getColumn(i);
sql.appfmt("%s", separator);
separator = ", ";
sql.appfmt("`%s` ", col->m_name.c_str());
switch(col->m_type)
{
case NdbInfo::Column::Number:
sql.appfmt("INT UNSIGNED");
break;
case NdbInfo::Column::Number64:
sql.appfmt("BIGINT UNSIGNED");
break;
case NdbInfo::Column::String:
sql.appfmt("VARCHAR(512)");
break;
default:
sql.appfmt("UNKNOWN");
assert(false);
break;
}
}
sql.appfmt(") ENGINE=NDBINFO'");
}
/*
Push a warning with explanation of the problem as well as the
proper SQL so the user can regenerate the table definition
*/
static void
warn_incompatible(const NdbInfo::Table* ndb_tab, bool fatal,
const char* format, ...)
{
BaseString msg;
DBUG_ENTER("warn_incompatible");
DBUG_PRINT("enter",("table_name: %s, fatal: %d", ndb_tab->getName(), fatal));
DBUG_ASSERT(format != NULL);
va_list args;
char explanation[128];
va_start(args,format);
my_vsnprintf(explanation, sizeof(explanation), format, args);
va_end(args);
msg.assfmt("Table '%s%s' is defined differently in NDB, %s. The "
"SQL to regenerate is: ",
opt_ndbinfo_table_prefix, ndb_tab->getName(), explanation);
generate_sql(ndb_tab, msg);
const Sql_condition::enum_severity_level level =
(fatal ? Sql_condition::SL_WARNING : Sql_condition::SL_NOTE);
push_warning(current_thd, level, ERR_INCOMPAT_TABLE_DEF, msg.c_str());
DBUG_VOID_RETURN;
}
int ha_ndbinfo::create(const char *name, TABLE *form,
HA_CREATE_INFO *create_info)
{
DBUG_ENTER("ha_ndbinfo::create");
DBUG_PRINT("enter", ("name: %s", name));
DBUG_RETURN(0);
}
bool ha_ndbinfo::is_open(void) const
{
return m_impl.m_table != NULL;
}
bool ha_ndbinfo::is_offline(void) const
{
return m_impl.m_offline;
}
int ha_ndbinfo::open(const char *name, int mode, uint test_if_locked)
{
DBUG_ENTER("ha_ndbinfo::open");
DBUG_PRINT("enter", ("name: %s, mode: %d", name, mode));
assert(is_closed());
assert(!is_offline()); // Closed table can not be offline
if (mode == O_RDWR)
{
if (table->db_stat & HA_TRY_READ_ONLY)
{
DBUG_PRINT("info", ("Telling server to use readonly mode"));
DBUG_RETURN(EROFS); // Read only fs
}
// Find any commands that does not allow open readonly
DBUG_ASSERT(false);
}
if (opt_ndbinfo_offline ||
ndbcluster_is_disabled())
{
// Mark table as being offline and allow it to be opened
m_impl.m_offline = true;
DBUG_RETURN(0);
}
int err = g_ndbinfo->openTable(name, &m_impl.m_table);
if (err)
{
assert(m_impl.m_table == 0);
if (err == NdbInfo::ERR_NoSuchTable)
DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
DBUG_RETURN(err2mysql(err));
}
/*
Check table def. to detect incompatible differences which should
return an error. Differences which only generate a warning
is checked on first use
*/
DBUG_PRINT("info", ("Comparing MySQL's table def against NDB"));
const NdbInfo::Table* ndb_tab = m_impl.m_table;
for (uint i = 0; i < table->s->fields; i++)
{
const Field* field = table->field[i];
// Check if field is NULLable
if (const_cast<Field*>(field)->real_maybe_null() == false)
{
// Only NULLable fields supported
warn_incompatible(ndb_tab, true,
"column '%s' is NOT NULL",
field->field_name);
delete m_impl.m_table; m_impl.m_table= 0;
DBUG_RETURN(ERR_INCOMPAT_TABLE_DEF);
}
// Check if column exist in NDB
const NdbInfo::Column* col = ndb_tab->getColumn(field->field_name);
if (!col)
{
// The column didn't exist
continue;
}
// Check compatible field and column type
bool compatible = false;
switch(col->m_type)
{
case NdbInfo::Column::Number:
if (field->type() == MYSQL_TYPE_LONG)
compatible = true;
break;
case NdbInfo::Column::Number64:
if (field->type() == MYSQL_TYPE_LONGLONG)
compatible = true;
break;
case NdbInfo::Column::String:
if (field->type() == MYSQL_TYPE_VARCHAR)
compatible = true;
break;
default:
assert(false);
break;
}
if (!compatible)
{
// The column type is not compatible
warn_incompatible(ndb_tab, true,
"column '%s' is not compatible",
field->field_name);
delete m_impl.m_table; m_impl.m_table= 0;
DBUG_RETURN(ERR_INCOMPAT_TABLE_DEF);
}
}
/* Increase "ref_length" to allow a whole row to be stored in "ref" */
ref_length = 0;
for (uint i = 0; i < table->s->fields; i++)
ref_length += table->field[i]->pack_length();
DBUG_PRINT("info", ("ref_length: %u", ref_length));
DBUG_RETURN(0);
}
int ha_ndbinfo::close(void)
{
DBUG_ENTER("ha_ndbinfo::close");
if (is_offline())
DBUG_RETURN(0);
assert(is_open());
if (m_impl.m_table)
{
g_ndbinfo->closeTable(m_impl.m_table);
m_impl.m_table = NULL;
}
DBUG_RETURN(0);
}
int ha_ndbinfo::rnd_init(bool scan)
{
DBUG_ENTER("ha_ndbinfo::rnd_init");
DBUG_PRINT("info", ("scan: %d", scan));
if (is_offline())
{
push_warning(current_thd, Sql_condition::SL_NOTE, 1,
"'NDBINFO' has been started in offline mode "
"since the 'NDBCLUSTER' engine is disabled "
"or @@global.ndbinfo_offline is turned on "
"- no rows can be returned");
DBUG_RETURN(0);
}
assert(is_open());
if (m_impl.m_scan_op)
{
/*
It should be impossible to come here with an already open
scan, assumption is that rnd_end() would be called to indicate
that the previous scan should be closed or perhaps like it says
in decsription of rnd_init() that it "may be called two times". Once
to open the cursor and once to position te cursor at first row.
Unfortunately the assumption and description of rnd_init() is not
correct. The rnd_init function is used on an open scan to reposition
it back to first row. For ha_ndbinfo this means closing
the scan and letting it be reopened.
*/
assert(scan); // "only makes sense if scan=1" (from rnd_init() description)
DBUG_PRINT("info", ("Closing scan to position it back to first row"));
// Release the scan operation
g_ndbinfo->releaseScanOperation(m_impl.m_scan_op);
m_impl.m_scan_op = NULL;
// Release pointers to the columns
m_impl.m_columns.clear();
}
assert(m_impl.m_scan_op == NULL); // No scan already ongoing
if (m_impl.m_first_use)
{
m_impl.m_first_use = false;
/*
Check table def. and generate warnings for incompatibilites
which is allowed but should generate a warning.
(Done this late due to different code paths in MySQL Server for
prepared statement protocol, where warnings from 'handler::open'
are lost).
*/
uint fields_found_in_ndb = 0;
const NdbInfo::Table* ndb_tab = m_impl.m_table;
for (uint i = 0; i < table->s->fields; i++)
{
const Field* field = table->field[i];
const NdbInfo::Column* col = ndb_tab->getColumn(field->field_name);
if (!col)
{
// The column didn't exist
warn_incompatible(ndb_tab, true,
"column '%s' does not exist",
field->field_name);
continue;
}
fields_found_in_ndb++;
}
if (fields_found_in_ndb < ndb_tab->columns())
{
// There are more columns available in NDB
warn_incompatible(ndb_tab, false,
"there are more columns available");
}
}
if (!scan)
{
// Just an init to read using 'rnd_pos'
DBUG_PRINT("info", ("not scan"));
DBUG_RETURN(0);
}
THD* thd = current_thd;
int err;
NdbInfoScanOperation* scan_op = NULL;
if ((err = g_ndbinfo->createScanOperation(m_impl.m_table,
&scan_op,
THDVAR(thd, max_rows),
THDVAR(thd, max_bytes))) != 0)
DBUG_RETURN(err2mysql(err));
if ((err = scan_op->readTuples()) != 0)
{
// Release the scan operation
g_ndbinfo->releaseScanOperation(scan_op);
DBUG_RETURN(err2mysql(err));
}
/* Read all columns specified in read_set */
for (uint i = 0; i < table->s->fields; i++)
{
Field *field = table->field[i];
if (bitmap_is_set(table->read_set, i))
m_impl.m_columns.push_back(scan_op->getValue(field->field_name));
else
m_impl.m_columns.push_back(NULL);
}
if ((err = scan_op->execute()) != 0)
{
// Release pointers to the columns
m_impl.m_columns.clear();
// Release the scan operation
g_ndbinfo->releaseScanOperation(scan_op);
DBUG_RETURN(err2mysql(err));
}
m_impl.m_scan_op = scan_op;
DBUG_RETURN(0);
}
int ha_ndbinfo::rnd_end()
{
DBUG_ENTER("ha_ndbinfo::rnd_end");
if (is_offline())
DBUG_RETURN(0);
assert(is_open());
if (m_impl.m_scan_op)
{
g_ndbinfo->releaseScanOperation(m_impl.m_scan_op);
m_impl.m_scan_op = NULL;
}
m_impl.m_columns.clear();
DBUG_RETURN(0);
}
int ha_ndbinfo::rnd_next(uchar *buf)
{
int err;
DBUG_ENTER("ha_ndbinfo::rnd_next");
if (is_offline())
DBUG_RETURN(HA_ERR_END_OF_FILE);
assert(is_open());
if (!m_impl.m_scan_op)
{
/*
It should be impossible to come here without a scan operation.
But apparently it's not safe to assume that rnd_next() isn't
called even though rnd_init() returned an error. Thus double check
that the scan operation exists and bail out in case it doesn't.
*/
DBUG_RETURN(HA_ERR_INTERNAL_ERROR);
}
if ((err = m_impl.m_scan_op->nextResult()) == 0)
DBUG_RETURN(HA_ERR_END_OF_FILE);
if (err != 1)
DBUG_RETURN(err2mysql(err));
unpack_record(buf);
DBUG_RETURN(0);
}
int ha_ndbinfo::rnd_pos(uchar *buf, uchar *pos)
{
DBUG_ENTER("ha_ndbinfo::rnd_pos");
assert(is_open());
assert(m_impl.m_scan_op == NULL); // No scan started
/* Copy the saved row into "buf" and set all fields to not null */
memcpy(buf, pos, ref_length);
for (uint i = 0; i < table->s->fields; i++)
table->field[i]->set_notnull();
DBUG_RETURN(0);
}
void ha_ndbinfo::position(const uchar *record)
{
DBUG_ENTER("ha_ndbinfo::position");
assert(is_open());
assert(m_impl.m_scan_op);
/* Save away the whole row in "ref" */
memcpy(ref, record, ref_length);
DBUG_VOID_RETURN;
}
int ha_ndbinfo::info(uint flag)
{
DBUG_ENTER("ha_ndbinfo::info");
DBUG_PRINT("enter", ("flag: %d", flag));
DBUG_RETURN(0);
}
void
ha_ndbinfo::unpack_record(uchar *dst_row)
{
DBUG_ENTER("ha_ndbinfo::unpack_record");
my_ptrdiff_t dst_offset = dst_row - table->record[0];
for (uint i = 0; i < table->s->fields; i++)
{
Field *field = table->field[i];
const NdbInfoRecAttr* record = m_impl.m_columns[i];
if (record && !record->isNULL())
{
field->set_notnull();
field->move_field_offset(dst_offset);
switch (field->type()) {
case (MYSQL_TYPE_VARCHAR):
{
DBUG_PRINT("info", ("str: %s", record->c_str()));
Field_varstring* vfield = (Field_varstring *) field;
/* Field_bit in DBUG requires the bit set in write_set for store(). */
my_bitmap_map *old_map =
dbug_tmp_use_all_columns(table, table->write_set);
(void)vfield->store(record->c_str(),
MIN(record->length(), field->field_length)-1,
field->charset());
dbug_tmp_restore_column_map(table->write_set, old_map);
break;
}
case (MYSQL_TYPE_LONG):
{
memcpy(field->ptr, record->ptr(), sizeof(Uint32));
break;
}
case (MYSQL_TYPE_LONGLONG):
{
memcpy(field->ptr, record->ptr(), sizeof(Uint64));
break;
}
default:
sql_print_error("Found unexpected field type %u", field->type());
break;
}
field->move_field_offset(-dst_offset);
}
else
{
field->set_null();
}
}
DBUG_VOID_RETURN;
}
static int
ndbinfo_find_files(handlerton *hton, THD *thd,
const char *db, const char *path,
const char *wild, bool dir, List<LEX_STRING> *files)
{
DBUG_ENTER("ndbinfo_find_files");
DBUG_PRINT("enter", ("db: '%s', dir: %d, path: '%s'", db, dir, path));
const bool show_hidden = THDVAR(thd, show_hidden);
if(show_hidden)
DBUG_RETURN(0); // Don't filter out anything
if (dir)
{
if (!ndbcluster_is_disabled())
DBUG_RETURN(0);
// Hide our database when ndbcluster is disabled
LEX_STRING *dir_name;
List_iterator<LEX_STRING> it(*files);
while ((dir_name=it++))
{
if (strcmp(dir_name->str, opt_ndbinfo_dbname))
continue;
DBUG_PRINT("info", ("Hiding own databse '%s'", dir_name->str));
it.remove();
}
DBUG_RETURN(0);
}
DBUG_ASSERT(db);
if (strcmp(db, opt_ndbinfo_dbname))
DBUG_RETURN(0); // Only hide files in "our" db
/* Hide all files that start with "our" prefix */
LEX_STRING *file_name;
List_iterator<LEX_STRING> it(*files);
while ((file_name=it++))
{
if (is_prefix(file_name->str, opt_ndbinfo_table_prefix))
{
DBUG_PRINT("info", ("Hiding '%s'", file_name->str));
it.remove();
}
}
DBUG_RETURN(0);
}
handlerton* ndbinfo_hton;
static
int
ndbinfo_init(void *plugin)
{
DBUG_ENTER("ndbinfo_init");
handlerton *hton = (handlerton *) plugin;
hton->create = create_handler;
hton->flags =
HTON_TEMPORARY_NOT_SUPPORTED |
HTON_ALTER_NOT_SUPPORTED;
hton->find_files = ndbinfo_find_files;
ndbinfo_hton = hton;
if (ndbcluster_is_disabled())
{
// Starting in limited mode since ndbcluster is disabled
DBUG_RETURN(0);
}
char prefix[FN_REFLEN];
build_table_filename(prefix, sizeof(prefix) - 1,
opt_ndbinfo_dbname, opt_ndbinfo_table_prefix, "", 0);
DBUG_PRINT("info", ("prefix: '%s'", prefix));
assert(g_ndb_cluster_connection);
g_ndbinfo = new NdbInfo(g_ndb_cluster_connection, prefix,
opt_ndbinfo_dbname, opt_ndbinfo_table_prefix);
if (!g_ndbinfo)
{
sql_print_error("Failed to create NdbInfo");
DBUG_RETURN(1);
}
if (!g_ndbinfo->init())
{
sql_print_error("Failed to init NdbInfo");
delete g_ndbinfo;
g_ndbinfo = NULL;
DBUG_RETURN(1);
}
DBUG_RETURN(0);
}
static
int
ndbinfo_deinit(void *plugin)
{
DBUG_ENTER("ndbinfo_deinit");
if (g_ndbinfo)
{
delete g_ndbinfo;
g_ndbinfo = NULL;
}
DBUG_RETURN(0);
}
struct st_mysql_sys_var* ndbinfo_system_variables[]= {
MYSQL_SYSVAR(max_rows),
MYSQL_SYSVAR(max_bytes),
MYSQL_SYSVAR(show_hidden),
MYSQL_SYSVAR(database),
MYSQL_SYSVAR(table_prefix),
MYSQL_SYSVAR(version),
MYSQL_SYSVAR(offline),
NULL
};
struct st_mysql_storage_engine ndbinfo_storage_engine=
{
MYSQL_HANDLERTON_INTERFACE_VERSION
};
struct st_mysql_plugin ndbinfo_plugin =
{
MYSQL_STORAGE_ENGINE_PLUGIN,
&ndbinfo_storage_engine,
"ndbinfo",
"Sun Microsystems Inc.",
"MySQL Cluster system information storage engine",
PLUGIN_LICENSE_GPL,
ndbinfo_init, /* plugin init */
ndbinfo_deinit, /* plugin deinit */
0x0001, /* plugin version */
NULL, /* status variables */
ndbinfo_system_variables, /* system variables */
NULL, /* config options */
0
};
template class Vector<const NdbInfoRecAttr*>;