/* Copyright (C) 2007-2015 Arjen G Lentz & Antony T Curtis for Open Query Copyright(C)2013-2015AndrewMcDonnell Copyright(C)2014SergeiGolubchik Portionsofthisfilecopyright(C)2000-2006MySQLAB
YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense alongwiththisprogram;ifnot,writetotheFreeSoftware
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
// Uncomment this for extra debug, but expect a performance hit in large queries //#define VERBOSE_DEBUG #ifdef VERBOSE_DEBUG #else #undef DBUG_PRINT #define DBUG_PRINT(x,y) #endif
// Table of varchar latch operations. // In the future this needs to be refactactored to live somewhere else struct oqgraph_latch_op_table { constchar *key; int latch; }; staticconst oqgraph_latch_op_table latch_ops_table[] = {
{ "", oqgraph::NO_SEARCH } , // suggested by Arjen, use empty string instead of no_search
{ "dijkstras", oqgraph::DIJKSTRAS } ,
{ "breadth_first", oqgraph::BREADTH_FIRST } ,
{ "leaves", oqgraph::LEAVES },
{ NULL, -1 }
};
static uint32 findLongestLatch() { int len = 0; for (const oqgraph_latch_op_table* k=latch_ops_table; k && k->key; k++) { int s = strlen(k->key); if (s > len) {
len = s;
}
} return len;
}
constchar *oqlatchToCode(int latch) { for (const oqgraph_latch_op_table* k=latch_ops_table; k && k->key; k++) { if (k->latch == latch) { return k->key;
}
} return"unknown";
}
struct ha_table_option_struct
{ constchar *table_name; constchar *origid; // name of the origin id column constchar *destid; // name of the target id column constchar *weight; // name of the weight column (optional)
};
hton->db_type= DB_TYPE_AUTOASSIGN;
hton->create= oqgraph_create_handler;
hton->flags= HTON_ALTER_NOT_SUPPORTED; // Prevent ALTER, because the core crashes when the user provides a // non-existing backing store field for ORIGID, etc // 'Fixes' bug 1134355 // HTON_NO_FLAGS;
staticint error_code(int res)
{ switch (res)
{ case oqgraph::OK: return0; case oqgraph::NO_MORE_DATA: return HA_ERR_END_OF_FILE; case oqgraph::EDGE_NOT_FOUND: return HA_ERR_KEY_NOT_FOUND; case oqgraph::INVALID_WEIGHT: return HA_ERR_AUTOINC_ERANGE; case oqgraph::DUPLICATE_EDGE: return HA_ERR_FOUND_DUPP_KEY; case oqgraph::CANNOT_ADD_VERTEX: case oqgraph::CANNOT_ADD_EDGE: return HA_ERR_RECORD_FILE_FULL; case oqgraph::MISC_FAIL: default: return HA_ERR_CRASHED_ON_USAGE;
}
}
if (isLatchColumn && ((*field)->type() == MYSQL_TYPE_SHORT))
{
DBUG_PRINT( "oq-debug", ("Allowing integer no more!"));
badColumn = true;
push_warning(current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Integer latch is not supported for new tables.");
} else /* Check Column Type */ if ((*field)->type() != skel[i].coltype) {
badColumn = true;
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d is wrong type.", i);
}
// Make sure latch column is large enough for all possible latch values if (isLatchColumn && isStringLatch) { if ((*field)->char_length() < findLongestLatch()) {
badColumn = true;
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d is too short.", i);
}
}
if (!badColumn) if (skel[i].coltype != MYSQL_TYPE_DOUBLE && (!isLatchColumn || !isStringLatch)) { /* Check Is UNSIGNED */ if ( (!((*field)->flags & UNSIGNED_FLAG ))) {
badColumn = true;
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d must be UNSIGNED.", i);
}
} /* Check THAT NOT NULL isn't set */ if (!badColumn) if ((*field)->flags & NOT_NULL_FLAG) {
badColumn = true;
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d must be NULL.", i);
} /* Check the column name */ if (!badColumn) if (strcmp(skel[i].colname,(*field)->field_name.str)) {
badColumn = true;
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d must be named '%s'.", i, skel[i].colname);
} if (badColumn) {
DBUG_RETURN(-1);
}
}
if (skel[i].colname) {
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Not enough columns.");
DBUG_RETURN(-1);
} if (*field) {
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Too many columns.");
DBUG_RETURN(-1);
}
if (!table_arg->key_info || !table_arg->s->keys) {
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "No valid key specification.");
DBUG_RETURN(-1);
}
DBUG_PRINT( "oq-debug", ("Checking keys."));
KEY *key= table_arg->key_info; for (uint i= 0; i < table_arg->s->keys; ++i, ++key)
{
Field **field= table_arg->field; /* check that the first key part is the latch and it is a hash key */ if (!(field[0] == key->key_part[0].field &&
HA_KEY_ALG_HASH == key->algorithm)) {
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Incorrect keys algorithm on key %d.", i);
DBUG_RETURN(-1);
} if (key->user_defined_key_parts == 3)
{ /* KEY (latch, origid, destid) USING HASH */ /* KEY (latch, destid, origid) USING HASH */ if (!(field[1] == key->key_part[1].field &&
field[2] == key->key_part[2].field) &&
!(field[1] == key->key_part[2].field &&
field[2] == key->key_part[1].field))
{
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Keys parts mismatch on key %d.", i);
DBUG_RETURN(-1);
}
} else {
push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Too many key parts on key %d.", i);
DBUG_RETURN(-1);
}
}
/** *CheckthatthecurrentlyreferencedOQGRAPHtabledefinition,onentrytoopen(),hassaneOQGRAPHoptions. *(Thisdoesnotcheckthebackingstore,buttheOQGRAPHvirtualtableoptions) * *@returntrueifOK,orfalseifanoptionisinvalid.
*/ bool ha_oqgraph::validate_oqgraph_table_options()
{ // Note when called from open(), we should not expect this method to fail except in the case of bugs; the fact that it does is // could be construed as a bug. I think in practice however, this is because CREATE TABLE calls both create() and open(), // and it is possible to do something like ALTER TABLE x DESTID='y' to _change_ the options. // Thus we need to sanity check from open() until and unless we get around to extending ha_oqgraph to properly handle ALTER TABLE, // after which we could change things to call this method from create() and the ALTER TABLE handling code instead. // It may still be sensible to call this from open() anyway, in case someone somewhere upgrades from a broken table definition...
ha_table_option_struct *options = option_struct; // Catch cases where table was not constructed properly // Note - need to return -1 so our error text gets reported if (!options) { // This should only happen if there is a bug elsewhere in the storage engine, because ENGINE itself is an attribute
fprint_error("Invalid OQGRAPH backing store (null attributes)");
} elseif (!options->table_name || !*options->table_name) { // The first condition indicates no DATA_TABLE option, the second if the user specified DATA_TABLE=''
fprint_error("Invalid OQGRAPH backing store description (unspecified or empty data_table attribute)"); // if table_name option is present but doesn't actually exist, we will fail later
} elseif (!options->origid || !*options->origid) { // The first condition indicates no ORIGID option, the second if the user specified ORIGID=''
fprint_error("Invalid OQGRAPH backing store description (unspecified or empty origid attribute)"); // if ORIGID option is present but doesn't actually exist, we will fail later
} elseif (!options->destid || !*options->destid) { // The first condition indicates no DESTID option, the second if the user specified DESTID=''
fprint_error("Invalid OQGRAPH backing store description (unspecified or empty destid attribute)"); // if DESTID option is present but doesn't actually exist, we will fail later
} else { // weight is optional... returntrue;
} // Fault returnfalse;
}
// So, we took a peek inside handler::ha_open() and learned a few things: // * this->table is set by handler::ha_open() before calling open(). // Note that from this we can only assume that MariaDB knows what it is doing and wont call open() other anything else // relying on this-0>table, re-entrantly... // * this->table_share should never be set back to NULL, an assertion checks for this in ha_open() after open() // * this->table_share is initialised in the constructor of handler // * this->table_share is only otherwise changed by this->change_table_ptr()) // We also discovered that an assertion is raised if table->s is not table_share before calling open())
// What I think this code is doing: // * Our OQGRAPH table is `database_blah/name` // * We point p --> /name (or if table happened to be simply `name`, to `name`, don't know if this is possible) // * plen seems to be then set to length of `database_blah/options_data_table_name` // * then we set share->normalized_path.str and share->path.str to `database_blah/options_data_table_name` // * I assume that this verbiage is needed so the memory used by share->path.str is set in the share mem root // * because otherwise one could simply build the string more simply using malloc and pass it instead of "" above constchar* p= strend(name)-1; while (p > name && *p != '\\' && *p != '/')
--p;
size_t tlen= strlen(options->table_name);
size_t plen= (int)(p - name) + tlen + 1;
int open_def_flags = 0;
open_def_flags = GTS_TABLE;
// We want to open the definition for the given backing table // Once can assume this loop exists because sometimes open_table_def() fails for a reason other than not exist // and not 'exist' is valid, because we use ha_create_table_from_engine() to force it to 'exist' // But, ha_create_table_from_engine() is removed in MariaDB 10.0.4 (?) // Looking inside most recent ha_create_table_from_engine(), it also calls open_table_def() so maybe this whole thing is redundant... // Or perhaps it is needed if the backing store is a temporary table or maybe if has no records as yet...? // Lets try without this, and see if all the tests pass... while (open_table_def(thd, share, open_def_flags))
{
open_table_error(share, OPEN_FRM_OPEN_ERROR, ENOENT);
free_table_share(share); if (thd->is_error())
DBUG_RETURN(thd->get_stmt_da()->sql_errno());
DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
}
// We expect fields origid, destid and optionally weight
origid= destid= weight= 0;
for (Field **field= edges->field; *field; ++field)
{ if (strcmp(options->origid, (*field)->field_name.str)) continue; if ((*field)->cmp_type() != INT_RESULT ||
!((*field)->flags & NOT_NULL_FLAG))
{
fprint_error("Column '%s.%s' (origid) is not a not-null integer type",
options->table_name, options->origid);
closefrm(edges);
free_table_share(share);
DBUG_RETURN(-1);
}
origid = *field; break;
}
if (!origid) {
fprint_error("Invalid OQGRAPH backing store ('%s.origid' attribute not set to a valid column of '%s')", p+1, options->table_name);
closefrm(edges);
free_table_share(share);
DBUG_RETURN(-1);
}
for (Field **field= edges->field; *field; ++field)
{ if (strcmp(options->destid, (*field)->field_name.str)) continue; if ((*field)->type() != origid->type() ||
!((*field)->flags & NOT_NULL_FLAG))
{
fprint_error("Column '%s.%s' (destid) is not a not-null integer type or is a different type to origid attribute.",
options->table_name, options->destid);
closefrm(edges);
free_table_share(share);
DBUG_RETURN(-1);
}
destid = *field; break;
}
if (!destid) {
fprint_error("Invalid OQGRAPH backing store ('%s.destid' attribute not set to a valid column of '%s')", p+1, options->table_name);
closefrm(edges);
free_table_share(share);
DBUG_RETURN(-1);
}
// Make sure origid column != destid column if (strcmp( origid->field_name.str, destid->field_name.str)==0) {
fprint_error("Invalid OQGRAPH backing store ('%s.destid' attribute set to same column as origid attribute)", p+1, options->table_name);
closefrm(edges);
free_table_share(share);
DBUG_RETURN(-1);
}
for (Field **field= edges->field; options->weight && *field; ++field)
{ if (strcmp(options->weight, (*field)->field_name.str)) continue; if ((*field)->result_type() != REAL_RESULT ||
!((*field)->flags & NOT_NULL_FLAG))
{
fprint_error("Column '%s.%s' (weight) is not a not-null real type",
options->table_name, options->weight);
closefrm(edges);
free_table_share(share);
DBUG_RETURN(-1);
}
weight = *field; break;
}
if (!weight && options->weight) {
fprint_error("Invalid OQGRAPH backing store ('%s.weight' attribute not set to a valid column of '%s')", p+1, options->table_name);
closefrm(edges);
free_table_share(share);
DBUG_RETURN(-1);
}
if (!(graph_share = oqgraph::create(edges, origid, destid, weight)))
{
fprint_error("Unable to create graph instance.");
closefrm(edges);
free_table_share(share);
DBUG_RETURN(-1);
}
ref_length= oqgraph::sizeof_ref;
int ha_oqgraph::delete_row(const byte * buf)
{ return HA_ERR_TABLE_READONLY;
}
int ha_oqgraph::index_read(byte * buf, const byte * key, uint key_len, enum ha_rkey_function find_flag)
{
DBUG_ASSERT(inited==INDEX); // reset before we have a cursor, so the memory is not junk, avoiding the sefgault in position() when select with order by (bug #1133093)
graph->init_row_ref(ref); return index_read_idx(buf, active_index, key, key_len, find_flag);
}
/** *ThisfunctionparsetheVARCHAR(n)latchspecificationintoanintegeroperationspecificationcompatiblewith *v1-v3oqgraph::search(). * *Ifthestringcontainsanumber,thisisdirectlyconvertedfromadecimalinteger. * *Otherwise,alookuptableisusedtoconvertfromastringconstant. * *Itisanticipatedthatthisfunction(andthisfileandclassoqgraph)willberefactoredtodothisinanicerway. * *FIXME:Forthetimebeing,onlyhandleslatin1characterset. *@returnfalseifparsingfails.
*/ staticint parse_latch_string_to_legacy_int(const String& value, int &latch)
{ // Attempt to parse as exactly an integer first.
// Note: we are strict about not having whitespace, or garbage characters, // so that the query result gets returned properly: // Because of the way the result is built and used in fill_result, // we have to exactly return in the latch column what was in the latch= clause // otherwise the rows get filtered out by the query optimiser.
// For the same reason, we cant simply treat latch='' as NO_SEARCH either.
String latchValue = value; char *eptr; unsignedlongint v = strtoul( latchValue.c_ptr_safe(), &eptr, 10); if (!*eptr) { // we had an unsigned number; remember 0 is valid too ('vertices' aka 'no_search')) if (v < oqgraph::NUM_SEARCH_OP) {
latch = v; returntrue;
} // fall through and test as a string (although it is unlikely we might have an operator starting with a number)
}
if (ptrdiff)
{
field[0]->move_field_offset(ptrdiff);
field[1]->move_field_offset(ptrdiff);
field[2]->move_field_offset(ptrdiff);
}
String latchFieldValue; if (!field[0]->is_null())
{
{
field[0]->val_str(&latchFieldValue, &latchFieldValue); if (!parse_latch_string_to_legacy_int(latchFieldValue, latch)) { // Invalid, so warn & fail
push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_ARGUMENTS, ER(ER_WRONG_ARGUMENTS), "OQGRAPH latch"); if (ptrdiff) /* fixes debug build assert - should be a tidier way to do this */
{
field[0]->move_field_offset(-ptrdiff);
field[1]->move_field_offset(-ptrdiff);
field[2]->move_field_offset(-ptrdiff);
}
dbug_tmp_restore_column_map(&table->read_set, old_map); return error_code(oqgraph::NO_MORE_DATA);
}
}
latchp= &latch;
}
if (!field[1]->is_null())
{
orig_id= (VertexID) field[1]->val_int();
orig_idp= &orig_id;
}
if (!field[2]->is_null())
{
dest_id= (VertexID) field[2]->val_int();
dest_idp= &dest_id;
}
if (ptrdiff)
{
field[0]->move_field_offset(-ptrdiff);
field[1]->move_field_offset(-ptrdiff);
field[2]->move_field_offset(-ptrdiff);
}
dbug_tmp_restore_column_map(&table->read_set, old_map);
// Keep the latch around so we can use it in the query result later - // See fill_record(). // at the moment our best option is to associate it with the graph // so we pass the string now. // In the future we should refactor parse_latch_string_to_legacy_int() // into oqgraph instead. if (latchp)
graph->retainLatchFieldValue(latchFieldValue.c_ptr_safe()); else
graph->retainLatchFieldValue(NULL);
// just each field specifically, no sense iterating if (row.latch_indicator)
{
field[0]->set_notnull(); // Convert the latch back to a varchar32 if (field[0]->type() == MYSQL_TYPE_VARCHAR) {
field[0]->store(row.latchStringValue, row.latchStringValueLen, &my_charset_latin1);
}
}
if (row.orig_indicator)
{
field[1]->set_notnull();
field[1]->store((longlong) row.orig, 0);
}
if (row.dest_indicator)
{
field[2]->set_notnull();
field[2]->store((longlong) row.dest, 0);
}
if (row.weight_indicator)
{
field[3]->set_notnull();
field[3]->store((double) row.weight);
}
if (row.seq_indicator)
{
field[4]->set_notnull();
field[4]->store((longlong) row.seq, 0);
}
if (row.link_indicator)
{
field[5]->set_notnull();
field[5]->store((longlong) row.link, 0);
}
int ha_oqgraph::rnd_init(bool scan)
{
edges->file->info(HA_STATUS_VARIABLE|HA_STATUS_CONST); // Fix for bug 1195735, hang after truncate table - ensure we operate with up to date count
edges->prepare_for_position(); return error_code(graph->random(scan));
}
int ha_oqgraph::delete_all_rows()
{ return HA_ERR_TABLE_READONLY;
}
int ha_oqgraph::external_lock(THD *thd, int lock_type)
{ // This method is also called to _unlock_ (lock_type == F_UNLCK) // Which means we need to release things before we let the underlying backing table lock go... if (lock_type == F_UNLCK) { // If we have an index open on the backing table, we need to close it out here // this means destroying any open cursor first. // Then we can let the unlock go through to the backing table
graph->release_cursor();
}
KEY *key=table->key_info+inx; #ifdef VERBOSE_DEBUG
{
String temp;
key->key_part[0].field->val_str(&temp);
temp.c_ptr_safe();
DBUG_PRINT( "oq-debug", ("thd: 0x%lx; records_in_range ::>> inx=%u", (long) current_thd, inx));
DBUG_PRINT( "oq-debug", ("records_in_range ::>> key0=%s.", temp.c_ptr())); // for some reason when I had ...inx=%u key=%s", inx, temp.c_ptr_safe()) it printed nothing ...
} #endif
if (!min_key || !max_key ||
min_key->length != max_key->length ||
min_key->length < key->key_length - key->key_part[2].store_length ||
min_key->flag != HA_READ_KEY_EXACT ||
max_key->flag != HA_READ_AFTER_KEY)
{ if (min_key && min_key->length == key->key_part[0].store_length && !key->key_part[0].field->is_null()) /* ensure select * from x where latch is null is consistent with no latch */
{ // If latch is not null and equals 0, return # nodes
// For a the following query: // SELECT * FROM graph2 WHERE latch = 'breadth_first' AND origid = 123 AND weight = 1; // key->key_part[0].field->ptr is the value of latch, which is a 1-byte string length followed by the value ('breadth_first') // key->key_part[2].field->ptr is the value of origid (123) // key->key_part[1].field->ptr is the value of destid which is not specified in the query so we ignore it in this case // so given this ordering we seem to be using the second key specified in create table (aka KEY (latch, destid, origid) USING HASH ))
// min_key->key[0] is the 'null' bit and contains 0 in this instance // min_key->key[1..2] seems to be 16-bit string length // min_key->key[3..34] hold the varchar(32) value which is that specified in the query // min_key->key[35] is the null bit of origid // min_key->key[36..43] is the value in the query (123)
// max_key->key[0] is the ;null' bit and contains 0 in this instance // max_key->key[1..2] seems to be 16-bit string length // max_key->key[3..34] hold the varchar(32) value which is that specified in the query // max_key->key[35] is the null bit of origid // max_key->key[36..43] is the value in the query (123)
// But after knowing all that, all we care about is the latch value
// First draft - ignore most of the stuff, but will likely break if query altered
// It turns out there is a better way though, to access the string, // as demonstrated in key_unpack() of sql/key.cc
String latchCode; int latch = -1; if (key->key_part[0].field->type() == MYSQL_TYPE_VARCHAR) {
// what if someone did something dumb, like mismatching the latches?
if (latch != oqgraph::NO_SEARCH) { // Invalid key type... // Don't assert, in case the user used alter table on us return HA_POS_ERROR; // Can only use exact keys
} unsigned N = graph->vertices_count();
DBUG_PRINT( "oq-debug", ("records_in_range ::>> N=%u (vertices)", N)); return N;
} return HA_POS_ERROR; // Can only use exact keys
}
staticconstchar *oqgraph_status_verbose_debug = #ifdef VERBOSE_DEBUG "Verbose Debug is enabled. Performance may be adversely impacted."; #else "Verbose Debug is not enabled."; #endif
staticconstchar *oqgraph_status_latch_compat_mode = "Legacy tables with integer latches are not supported.";
staticstruct st_mysql_show_var oqgraph_status[]=
{
{ "OQGraph_Boost_Version", (char*) &oqgraph_boost_version, SHOW_CHAR_PTR }, /* We thought about reporting the Judy version, but there seems to be no way to get that from code in the first place. */
{ "OQGraph_Verbose_Debug", (char*) &oqgraph_status_verbose_debug, SHOW_CHAR_PTR },
{ "OQGraph_Compat_mode", (char*) &oqgraph_status_latch_compat_mode, SHOW_CHAR_PTR },
{ 0, 0, SHOW_UNDEF }
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.