1 /* Copyright (C) 2007-2015 Arjen G Lentz & Antony T Curtis for Open Query
2    Copyright (C) 2013-2015 Andrew McDonnell
3    Copyright (C) 2014 Sergei Golubchik
4    Portions of this file copyright (C) 2000-2006 MySQL AB
5 
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; version 2 of the License.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
18 
19 /* ======================================================================
20    Open Query Graph Computation Engine, based on a concept by Arjen Lentz
21    v3 implementation by Antony Curtis, Arjen Lentz, Andrew McDonnell
22    For more information, documentation, support, enhancement engineering,
23    see http://openquery.com/graph or contact graph@openquery.com
24    ======================================================================
25 */
26 
27 /*
28    Changelog since 10.0.13
29    -----------------------
30    * Removed compatibility hacks for 5.5.32 and 10.0.4.
31      I expect no issues building oqgraph into Mariadb 5.5.40 but I think the better approach is maintain a separate fork / patches.
32    * Added status variable to report if verbose debug is on
33    * Fixed handling of connection thread changed, the apparent root cause of
34      MDEV-6282, MDEV-6345 and MDEV-6784
35 
36 */
37 
38 #ifdef USE_PRAGMA_IMPLEMENTATION
39 #pragma implementation                          // gcc: Class implementation
40 #endif
41 
42 #include <my_global.h>
43 #define MYSQL_SERVER 1                          // to have THD
44 /* For the moment, include code to deal with integer latches.
45  * I have wrapped it with this #ifdef to make it easier to find and remove in the future.
46  */
47 #define RETAIN_INT_LATCH_COMPATIBILITY          // for the time being, recognise integer latches to simplify upgrade.
48 
49 #include <mysql/plugin.h>
50 #include <mysql_version.h>
51 #include "ha_oqgraph.h"
52 #include "graphcore.h"
53 #include <sql_error.h>
54 #include <sql_class.h>
55 #include <table.h>
56 #include <field.h>
57 #include <key.h>
58 #include <unireg.h>
59 #include <my_dbug.h>
60 
61 // Uncomment this for extra debug, but expect a performance hit in large queries
62 //#define VERBOSE_DEBUG
63 #ifdef VERBOSE_DEBUG
64 #else
65 #undef DBUG_PRINT
66 #define DBUG_PRINT(x,y)
67 #endif
68 
69 #ifdef RETAIN_INT_LATCH_COMPATIBILITY
70 /* In normal operation, no new tables using an integer latch can be created,
71  * but they can still be used if they already exist, to allow for upgrades.
72  *
73  * However to ensure the legacy function is properly tested, we add a
74  * server variable "oggraph_allow_create_integer_latch" which if set to TRUE
75  * allows new engine tables to be created with integer latches.
76  */
77 
78 static my_bool g_allow_create_integer_latch = FALSE;
79 #endif
80 
81 using namespace open_query;
82 
83 // Table of varchar latch operations.
84 // In the future this needs to be refactactored to live somewhere else
85 struct oqgraph_latch_op_table { const char *key; int latch; };
86 static const oqgraph_latch_op_table latch_ops_table[] = {
87   { "", oqgraph::NO_SEARCH } ,  // suggested by Arjen, use empty string instead of no_search
88   { "dijkstras", oqgraph::DIJKSTRAS } ,
89   { "breadth_first", oqgraph::BREADTH_FIRST } ,
90   { "leaves", oqgraph::LEAVES },
91   { NULL, -1 }
92 };
93 
findLongestLatch()94 static uint32 findLongestLatch() {
95   int len = 0;
96   for (const oqgraph_latch_op_table* k=latch_ops_table; k && k->key; k++) {
97     int s = strlen(k->key);
98     if (s > len) {
99       len = s;
100     }
101   }
102   return len;
103 }
104 
oqlatchToCode(int latch)105 const char *oqlatchToCode(int latch) {
106   for (const oqgraph_latch_op_table* k=latch_ops_table; k && k->key; k++) {
107     if (k->latch == latch) {
108       return k->key;
109     }
110   }
111   return "unknown";
112 }
113 
114 struct ha_table_option_struct
115 {
116   const char *table_name;
117   const char *origid; // name of the origin id column
118   const char *destid; // name of the target id column
119   const char *weight; // name of the weight column (optional)
120 };
121 
122 static const ha_create_table_option oqgraph_table_option_list[]=
123 {
124   HA_TOPTION_STRING("data_table", table_name),
125   HA_TOPTION_STRING("origid", origid),
126   HA_TOPTION_STRING("destid", destid),
127   HA_TOPTION_STRING("weight", weight),
128   HA_TOPTION_END
129 };
130 
131 static bool oqgraph_init_done= 0;
132 
oqgraph_create_handler(handlerton * hton,TABLE_SHARE * table,MEM_ROOT * mem_root)133 static handler* oqgraph_create_handler(handlerton *hton, TABLE_SHARE *table,
134                                        MEM_ROOT *mem_root)
135 {
136   DBUG_PRINT( "oq-debug", ("oqgraph_create_handler"));
137   return new (mem_root) ha_oqgraph(hton, table);
138 }
139 
140 #define OQGRAPH_CREATE_TABLE                              \
141 "         CREATE TABLE oq_graph (                        "\
142 "           latch VARCHAR(32) NULL,                      "\
143 "           origid BIGINT UNSIGNED NULL,                 "\
144 "           destid BIGINT UNSIGNED NULL,                 "\
145 "           weight DOUBLE NULL,                          "\
146 "           seq BIGINT UNSIGNED NULL,                    "\
147 "           linkid BIGINT UNSIGNED NULL,                 "\
148 "           KEY (latch, origid, destid) USING HASH,      "\
149 "           KEY (latch, destid, origid) USING HASH       "\
150 "         )                                              "
151 
152 #define append_opt(NAME,VAL)                                    \
153   if (share->option_struct->VAL)                                \
154   {                                                             \
155     sql.append(STRING_WITH_LEN(" " NAME "='"));                  \
156     sql.append_for_single_quote(share->option_struct->VAL);     \
157     sql.append('\'');                                           \
158   }
159 
oqgraph_discover_table_structure(handlerton * hton,THD * thd,TABLE_SHARE * share,HA_CREATE_INFO * info)160 int oqgraph_discover_table_structure(handlerton *hton, THD* thd,
161                                      TABLE_SHARE *share, HA_CREATE_INFO *info)
162 {
163   StringBuffer<1024> sql(system_charset_info);
164   sql.copy(STRING_WITH_LEN(OQGRAPH_CREATE_TABLE), system_charset_info);
165 
166   append_opt("data_table", table_name);
167   append_opt("origid", origid);
168   append_opt("destid", destid);
169   append_opt("weight", weight);
170 
171   return
172     share->init_from_sql_statement_string(thd, true, sql.ptr(), sql.length());
173 }
174 
175 int oqgraph_close_connection(handlerton *hton, THD *thd);
176 
oqgraph_init(void * p)177 static int oqgraph_init(void *p)
178 {
179   handlerton *hton= (handlerton *)p;
180   DBUG_PRINT( "oq-debug", ("oqgraph_init"));
181 
182   hton->db_type= DB_TYPE_AUTOASSIGN;
183   hton->create= oqgraph_create_handler;
184   hton->flags= HTON_ALTER_NOT_SUPPORTED;
185   // Prevent ALTER, because the core crashes when the user provides a
186   // non-existing backing store field for ORIGID, etc
187   // 'Fixes' bug 1134355
188   // HTON_NO_FLAGS;
189 
190   hton->table_options= (ha_create_table_option*)oqgraph_table_option_list;
191 
192   hton->discover_table_structure= oqgraph_discover_table_structure;
193 
194   hton->close_connection = oqgraph_close_connection;
195   hton->drop_table= [](handlerton *, const char*) { return -1; };
196 
197   oqgraph_init_done= TRUE;
198   return 0;
199 }
200 
oqgraph_fini(void *)201 static int oqgraph_fini(void *)
202 {
203   DBUG_PRINT( "oq-debug", ("oqgraph_fini"));
204   oqgraph_init_done= FALSE;
205   return 0;
206 }
207 
error_code(int res)208 static int error_code(int res)
209 {
210   switch (res)
211   {
212   case oqgraph::OK:
213     return 0;
214   case oqgraph::NO_MORE_DATA:
215     return HA_ERR_END_OF_FILE;
216   case oqgraph::EDGE_NOT_FOUND:
217     return HA_ERR_KEY_NOT_FOUND;
218   case oqgraph::INVALID_WEIGHT:
219     return HA_ERR_AUTOINC_ERANGE;
220   case oqgraph::DUPLICATE_EDGE:
221     return HA_ERR_FOUND_DUPP_KEY;
222   case oqgraph::CANNOT_ADD_VERTEX:
223   case oqgraph::CANNOT_ADD_EDGE:
224     return HA_ERR_RECORD_FILE_FULL;
225   case oqgraph::MISC_FAIL:
226   default:
227     return HA_ERR_CRASHED_ON_USAGE;
228   }
229 }
230 
231 /**
232  * Check if table complies with our designated structure
233  *
234  *    ColName    Type      Attributes
235  *    =======    ========  =============
236  *    latch     VARCHAR   NULL
237  *    origid    BIGINT    UNSIGNED NULL
238  *    destid    BIGINT    UNSIGNED NULL
239  *    weight    DOUBLE    NULL
240  *    seq       BIGINT    UNSIGNED NULL
241  *    linkid    BIGINT    UNSIGNED NULL
242  *    =================================
243  *
244 
245   The latch may be a varchar of any length, however if it is too short to
246   hold the longest latch value, table creation is aborted.
247 
248   CREATE TABLE foo (
249     latch   VARCHAR(32)   NULL,
250     origid  BIGINT    UNSIGNED NULL,
251     destid  BIGINT    UNSIGNED NULL,
252     weight  DOUBLE    NULL,
253     seq     BIGINT    UNSIGNED NULL,
254     linkid  BIGINT    UNSIGNED NULL,
255     KEY (latch, origid, destid) USING HASH,
256     KEY (latch, destid, origid) USING HASH
257   ) ENGINE=OQGRAPH
258     DATA_TABLE=bar
259     ORIGID=src_id
260     DESTID=tgt_id
261 
262  Previously latch could be an integer.
263  We no longer allow new integer tables to be created, but we need to support
264  them if in use and this module is upgraded.
265  So when the table is opened we need to see whether latch is a varchar or
266  integer and change behaviour accordingly.
267  Note that if a table was constructed with varchar and an attempt is made to
268  select with latch=(some integer number) then MYSQL will autocast
269  and no data will be returned... so retaining compatibility does not and cannot
270  extend to making old queries work with new style tables.
271 
272   This method is only called on table creation, so here we ensure new tables
273   can only be created with varchar.
274 
275   This does present a small problem with regression testing;
276   so we work around that by using an system variable to allow
277   integer latch tables to be created.
278 
279  */
oqgraph_check_table_structure(TABLE * table_arg)280 int ha_oqgraph::oqgraph_check_table_structure (TABLE *table_arg)
281 {
282   // Changed from static so we can do decent error reporting.
283 
284   int i;
285   struct { const char *colname; int coltype; } skel[] = {
286     { "latch" , MYSQL_TYPE_VARCHAR },
287     { "origid", MYSQL_TYPE_LONGLONG },
288     { "destid", MYSQL_TYPE_LONGLONG },
289     { "weight", MYSQL_TYPE_DOUBLE },
290     { "seq"   , MYSQL_TYPE_LONGLONG },
291     { "linkid", MYSQL_TYPE_LONGLONG },
292   { NULL    , 0}
293   };
294 
295   DBUG_ENTER("oqgraph_check_table_structure");
296 
297   DBUG_PRINT( "oq-debug", ("Checking structure."));
298 
299   Field **field= table_arg->field;
300   for (i= 0; *field && skel[i].colname; i++, field++) {
301     DBUG_PRINT( "oq-debug", ("Column %d: name='%s', expected '%s'; type=%d, expected %d.", i, (*field)->field_name.str, skel[i].colname, (*field)->type(), skel[i].coltype));
302     bool badColumn = false;
303     bool isLatchColumn = strcmp(skel[i].colname, "latch")==0;
304     bool isStringLatch = true;
305 
306 #ifdef RETAIN_INT_LATCH_COMPATIBILITY
307     if (g_allow_create_integer_latch && isLatchColumn && ((*field)->type() == MYSQL_TYPE_SHORT))
308     {
309       DBUG_PRINT( "oq-debug", ("Allowing integer latch anyway!"));
310       isStringLatch = false;
311       /* Make a warning */
312       push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
313             ER_WARN_DEPRECATED_SYNTAX, ER(ER_WARN_DEPRECATED_SYNTAX),
314             "latch SMALLINT UNSIGNED NULL", "'latch VARCHAR(32) NULL'");
315     } else
316 #endif
317     if (isLatchColumn && ((*field)->type() == MYSQL_TYPE_SHORT))
318     {
319       DBUG_PRINT( "oq-debug", ("Allowing integer no more!"));
320       badColumn = true;
321       push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Integer latch is not supported for new tables.", i);
322     } else
323     /* Check Column Type */
324     if ((*field)->type() != skel[i].coltype) {
325       badColumn = true;
326       push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d is wrong type.", i);
327     }
328 
329     // Make sure latch column is large enough for all possible latch values
330     if (isLatchColumn && isStringLatch) {
331       if ((*field)->char_length() < findLongestLatch()) {
332         badColumn = true;
333         push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d is too short.", i);
334       }
335     }
336 
337     if (!badColumn) if (skel[i].coltype != MYSQL_TYPE_DOUBLE && (!isLatchColumn || !isStringLatch)) {
338       /* Check Is UNSIGNED */
339       if ( (!((*field)->flags & UNSIGNED_FLAG ))) {
340         badColumn = true;
341         push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d must be UNSIGNED.", i);
342       }
343     }
344     /* Check THAT  NOT NULL isn't set */
345     if (!badColumn) if ((*field)->flags & NOT_NULL_FLAG) {
346       badColumn = true;
347       push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d must be NULL.", i);
348     }
349     /* Check the column name */
350     if (!badColumn) if (strcmp(skel[i].colname,(*field)->field_name.str)) {
351       badColumn = true;
352       push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Column %d must be named '%s'.", i, skel[i].colname);
353     }
354     if (badColumn) {
355       DBUG_RETURN(-1);
356     }
357   }
358 
359   if (skel[i].colname) {
360     push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Not enough columns.");
361     DBUG_RETURN(-1);
362   }
363   if (*field) {
364     push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Too many columns.");
365     DBUG_RETURN(-1);
366   }
367 
368   if (!table_arg->key_info || !table_arg->s->keys) {
369     push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "No valid key specification.");
370     DBUG_RETURN(-1);
371   }
372 
373   DBUG_PRINT( "oq-debug", ("Checking keys."));
374 
375   KEY *key= table_arg->key_info;
376   for (uint i= 0; i < table_arg->s->keys; ++i, ++key)
377   {
378     Field **field= table_arg->field;
379     /* check that the first key part is the latch and it is a hash key */
380     if (!(field[0] == key->key_part[0].field &&
381           HA_KEY_ALG_HASH == key->algorithm)) {
382       push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Incorrect keys algorithm on key %d.", i);
383       DBUG_RETURN(-1);
384     }
385     if (key->user_defined_key_parts == 3)
386     {
387       /* KEY (latch, origid, destid) USING HASH */
388       /* KEY (latch, destid, origid) USING HASH */
389       if (!(field[1] == key->key_part[1].field &&
390             field[2] == key->key_part[2].field) &&
391           !(field[1] == key->key_part[2].field &&
392             field[2] == key->key_part[1].field))
393       {
394         push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Keys parts mismatch on key %d.", i);
395         DBUG_RETURN(-1);
396       }
397     }
398     else {
399       push_warning_printf( current_thd, Sql_condition::WARN_LEVEL_WARN, HA_WRONG_CREATE_OPTION, "Too many key parts on key %d.", i);
400       DBUG_RETURN(-1);
401     }
402   }
403 
404   DBUG_RETURN(0);
405 }
406 
407 /*****************************************************************************
408 ** OQGRAPH tables
409 *****************************************************************************/
410 
oqgraph_close_connection(handlerton * hton,THD * thd)411 int oqgraph_close_connection(handlerton *hton, THD *thd)
412 {
413   DBUG_PRINT( "oq-debug", ("thd: 0x%lx; oqgraph_close_connection.", (long) thd));
414   // close_thread_tables(thd); // maybe this?
415   return 0;
416 }
417 
418 
ha_oqgraph(handlerton * hton,TABLE_SHARE * table_arg)419 ha_oqgraph::ha_oqgraph(handlerton *hton, TABLE_SHARE *table_arg)
420   : handler(hton, table_arg)
421   , have_table_share(false)
422   , origid(NULL)
423   , destid(NULL)
424   , weight(NULL)
425   , graph_share(0)
426   , graph(0)
427   , error_message("", 0, &my_charset_latin1)
428 {
429 }
430 
~ha_oqgraph()431 ha_oqgraph::~ha_oqgraph()
432 { }
433 
434 static const char *ha_oqgraph_exts[] =
435 {
436   NullS
437 };
438 
bas_ext() const439 const char **ha_oqgraph::bas_ext() const
440 {
441   return ha_oqgraph_exts;
442 }
443 
table_flags() const444 ulonglong ha_oqgraph::table_flags() const
445 {
446   return (HA_NO_BLOBS | HA_NULL_IN_KEY |
447           HA_REC_NOT_IN_SEQ | HA_CAN_INSERT_DELAYED |
448           HA_BINLOG_STMT_CAPABLE | HA_BINLOG_ROW_CAPABLE);
449 }
450 
index_flags(uint inx,uint part,bool all_parts) const451 ulong ha_oqgraph::index_flags(uint inx, uint part, bool all_parts) const
452 {
453   return HA_ONLY_WHOLE_INDEX | HA_KEY_SCAN_NOT_ROR;
454 }
455 
get_error_message(int error,String * buf)456 bool ha_oqgraph::get_error_message(int error, String* buf)
457 {
458   if (error < 0)
459   {
460     buf->append(error_message);
461     buf->c_ptr_safe();
462     error_message.length(0);
463   }
464   return false;
465 }
466 
fprint_error(const char * fmt,...)467 void ha_oqgraph::fprint_error(const char* fmt, ...)
468 {
469   va_list ap;
470   va_start(ap, fmt);
471   error_message.reserve(256);
472   size_t len = error_message.length();
473   len += vsnprintf(&error_message[len], 255, fmt, ap);
474   error_message.length(len);
475   va_end(ap);
476 }
477 
478 /**
479  * Check that the currently referenced OQGRAPH table definition, on entry to open(), has sane OQGRAPH options.
480  * (This does not check the backing store, but the OQGRAPH virtual table options)
481  *
482  * @return true if OK, or false if an option is invalid.
483  */
validate_oqgraph_table_options()484 bool ha_oqgraph::validate_oqgraph_table_options()
485 {
486   // 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
487   // could be construed as a bug. I think in practice however, this is because CREATE TABLE calls both create() and open(),
488   // and it is possible to do something like ALTER TABLE x DESTID='y' to _change_ the options.
489   // Thus we need to sanity check from open() until and unless we get around to extending ha_oqgraph to properly handle ALTER TABLE,
490   // after which we could change things to call this method from create() and the ALTER TABLE handling code instead.
491   // It may still be sensible to call this from open() anyway, in case someone somewhere upgrades from a broken table definition...
492 
493   ha_table_option_struct *options = table->s->option_struct;
494   // Catch cases where table was not constructed properly
495   // Note - need to return -1 so our error text gets reported
496   if (!options) {
497     // This should only happen if there is a bug elsewhere in the storage engine, because ENGINE itself is an attribute
498     fprint_error("Invalid OQGRAPH backing store (null attributes)");
499   }
500   else if (!options->table_name || !*options->table_name) {
501     // The first condition indicates no DATA_TABLE option, the second if the user specified DATA_TABLE=''
502     fprint_error("Invalid OQGRAPH backing store description (unspecified or empty data_table attribute)");
503     // if table_name option is present but doesn't actually exist, we will fail later
504   }
505   else if (!options->origid || !*options->origid) {
506     // The first condition indicates no ORIGID option, the second if the user specified ORIGID=''
507     fprint_error("Invalid OQGRAPH backing store description (unspecified or empty origid attribute)");
508     // if ORIGID option is present but doesn't actually exist, we will fail later
509   }
510   else if (!options->destid || !*options->destid) {
511     // The first condition indicates no DESTID option, the second if the user specified DESTID=''
512     fprint_error("Invalid OQGRAPH backing store description (unspecified or empty destid attribute)");
513     // if DESTID option is present but doesn't actually exist, we will fail later
514   } else {
515     // weight is optional...
516     return true;
517   }
518   // Fault
519   return false;
520 }
521 
522 /**
523  * Open the OQGRAPH engine 'table'.
524  *
525  * An OQGRAPH table is effectively similar to a view over the underlying backing table, attribute 'data_table', but where the
526  * result returned by a query depends on the value of the 'latch' column specified to the query.
527  * Therefore, when mysqld opens us, we need to open the corresponding backing table 'data_table'.
528  *
529  * Conceptually, the backing store could be any kind of object having queryable semantics, including a SQL VIEW.
530  * However, for that to work in practice would require us to hook into the right level of the MYSQL API.
531  * Presently, only objects that can be opened using the internal mechanisms can be used: INNODB, MYISAM, etc.
532  * The intention is to borrow from ha_connect and use the mysql client library to access the backing store.
533  *
534  */
open(const char * name,int mode,uint test_if_locked)535 int ha_oqgraph::open(const char *name, int mode, uint test_if_locked)
536 {
537   DBUG_ENTER("ha_oqgraph::open");
538   DBUG_PRINT( "oq-debug", ("thd: 0x%lx; open(name=%s,mode=%d,test_if_locked=%u)", (long) current_thd, name, mode, test_if_locked));
539 
540   // So, we took a peek inside handler::ha_open() and learned a few things:
541   // * this->table is set by handler::ha_open() before calling open().
542   //   Note that from this we can only assume that MariaDB knows what it is doing and wont call open() other anything else
543   //   relying on this-0>table, re-entrantly...
544   // * this->table_share should never be set back to NULL, an assertion checks for this in ha_open() after open()
545   // * this->table_share is initialised in the constructor of handler
546   // * this->table_share is only otherwise changed by this->change_table_ptr())
547   // We also discovered that an assertion is raised if table->s is not table_share before calling open())
548 
549   DBUG_ASSERT(!have_table_share);
550   DBUG_ASSERT(graph == NULL);
551 
552   // Before doing anything, make sure we have DATA_TABLE, ORIGID and DESTID not empty
553   if (!validate_oqgraph_table_options()) { DBUG_RETURN(-1); }
554 
555   ha_table_option_struct *options= table->s->option_struct;
556 
557 
558   error_message.length(0);
559   origid= destid= weight= 0;
560 
561   // Here we're abusing init_tmp_table_share() which is normally only works for thread-local shares.
562   THD* thd = current_thd;
563   init_tmp_table_share( thd, share, table->s->db.str, table->s->db.length, options->table_name, "");
564   // because of that, we need to reinitialize the memroot (to reset MY_THREAD_SPECIFIC flag)
565   DBUG_ASSERT(share->mem_root.used == NULL); // it's still empty
566   init_sql_alloc(PSI_INSTRUMENT_ME, &share->mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, MYF(0));
567 
568   // What I think this code is doing:
569   // * Our OQGRAPH table is `database_blah/name`
570   // * We point p --> /name (or if table happened to be simply `name`, to `name`, don't know if this is possible)
571   // * plen seems to be then set to length of `database_blah/options_data_table_name`
572   // * then we set share->normalized_path.str and share->path.str to `database_blah/options_data_table_name`
573   // * I assume that this verbiage is needed so  the memory used by share->path.str is set in the share mem root
574   // * because otherwise one could simply build the string more simply using malloc and pass it instead of "" above
575   const char* p= strend(name)-1;
576   while (p > name && *p != '\\' && *p != '/')
577     --p;
578   size_t tlen= strlen(options->table_name);
579   size_t plen= (int)(p - name) + tlen + 1;
580 
581   share->path.str= (char*)alloc_root(&share->mem_root, plen + 1);
582   strmov(strnmov((char*) share->path.str, name, (int)(p - name) + 1),
583          options->table_name);
584   DBUG_ASSERT(strlen(share->path.str) == plen);
585   share->normalized_path.str= share->path.str;
586   share->path.length= share->normalized_path.length= plen;
587 
588   DBUG_PRINT( "oq-debug", ("share:(normalized_path=%s,path.length=%zu)",
589               share->normalized_path.str, share->path.length));
590 
591   int open_def_flags = 0;
592   open_def_flags = GTS_TABLE;
593 
594   // We want to open the definition for the given backing table
595   // Once can assume this loop exists because sometimes open_table_def() fails for a reason other than not exist
596   // and not 'exist' is valid, because we use ha_create_table_from_engine() to force it to 'exist'
597   // But, ha_create_table_from_engine() is removed in MariaDB 10.0.4 (?)
598   // Looking inside most recent ha_create_table_from_engine(), it also calls open_table_def() so maybe this whole thing is redundant...
599   // Or perhaps it is needed if the backing store is a temporary table or maybe if has no records as yet...?
600   // Lets try without this, and see if all the tests pass...
601   while (open_table_def(thd, share, open_def_flags))
602   {
603     open_table_error(share, OPEN_FRM_OPEN_ERROR, ENOENT);
604     free_table_share(share);
605     if (thd->is_error())
606       DBUG_RETURN(thd->get_stmt_da()->sql_errno());
607     DBUG_RETURN(HA_ERR_NO_SUCH_TABLE);
608   }
609 
610 
611   if (int err= share->error)
612   {
613     open_table_error(share, share->error, share->open_errno);
614     free_table_share(share);
615     DBUG_RETURN(err);
616   }
617 
618   if (share->is_view)
619   {
620     free_table_share(share);
621     fprint_error("VIEWs are not supported for an OQGRAPH backing store.");
622     DBUG_RETURN(-1);
623   }
624 
625   if (enum open_frm_error err= open_table_from_share(thd, share,
626                                                      &empty_clex_str,
627                             (uint) (HA_OPEN_KEYFILE | HA_TRY_READ_ONLY),
628                             EXTRA_RECORD,
629                             thd->open_options, edges, FALSE))
630   {
631     open_table_error(share, err, EMFILE); // NOTE - EMFILE is probably bogus, it reports as too many open files (!)
632     free_table_share(share);
633     DBUG_RETURN(-1);
634   }
635 
636 
637   if (!edges->file)
638   {
639     fprint_error("Some error occurred opening table '%s'", options->table_name);
640     free_table_share(share);
641     DBUG_RETURN(-1);
642   }
643 
644   edges->reginfo.lock_type= TL_READ;
645 
646   edges->tablenr= thd->current_tablenr++;
647   edges->status= STATUS_NO_RECORD;
648   edges->file->ft_handler= 0;
649   edges->pos_in_table_list= 0;
650   edges->clear_column_bitmaps();
651   bfill(table->record[0], table->s->null_bytes, 255);
652   bfill(table->record[1], table->s->null_bytes, 255);
653 
654   // We expect fields origid, destid and optionally weight
655   origid= destid= weight= 0;
656 
657   for (Field **field= edges->field; *field; ++field)
658   {
659     if (strcmp(options->origid, (*field)->field_name.str))
660       continue;
661     if ((*field)->cmp_type() != INT_RESULT ||
662         !((*field)->flags & NOT_NULL_FLAG))
663     {
664       fprint_error("Column '%s.%s' (origid) is not a not-null integer type",
665           options->table_name, options->origid);
666       closefrm(edges);
667       free_table_share(share);
668       DBUG_RETURN(-1);
669     }
670     origid = *field;
671     break;
672   }
673 
674   if (!origid) {
675     fprint_error("Invalid OQGRAPH backing store ('%s.origid' attribute not set to a valid column of '%s')", p+1, options->table_name);
676     closefrm(edges);
677     free_table_share(share);
678     DBUG_RETURN(-1);
679   }
680 
681 
682   for (Field **field= edges->field; *field; ++field)
683   {
684     if (strcmp(options->destid, (*field)->field_name.str))
685       continue;
686     if ((*field)->type() != origid->type() ||
687         !((*field)->flags & NOT_NULL_FLAG))
688     {
689       fprint_error("Column '%s.%s' (destid) is not a not-null integer type or is a different type to origid attribute.",
690           options->table_name, options->destid);
691       closefrm(edges);
692       free_table_share(share);
693       DBUG_RETURN(-1);
694     }
695     destid = *field;
696     break;
697   }
698 
699   if (!destid) {
700     fprint_error("Invalid OQGRAPH backing store ('%s.destid' attribute not set to a valid column of '%s')", p+1, options->table_name);
701     closefrm(edges);
702     free_table_share(share);
703     DBUG_RETURN(-1);
704   }
705 
706   // Make sure origid column != destid column
707   if (strcmp( origid->field_name.str, destid->field_name.str)==0) {
708     fprint_error("Invalid OQGRAPH backing store ('%s.destid' attribute set to same column as origid attribute)", p+1, options->table_name);
709     closefrm(edges);
710     free_table_share(share);
711     DBUG_RETURN(-1);
712   }
713 
714   for (Field **field= edges->field; options->weight && *field; ++field)
715   {
716     if (strcmp(options->weight, (*field)->field_name.str))
717       continue;
718     if ((*field)->result_type() != REAL_RESULT ||
719         !((*field)->flags & NOT_NULL_FLAG))
720     {
721       fprint_error("Column '%s.%s' (weight) is not a not-null real type",
722           options->table_name, options->weight);
723       closefrm(edges);
724       free_table_share(share);
725       DBUG_RETURN(-1);
726     }
727     weight = *field;
728     break;
729   }
730 
731   if (!weight && options->weight) {
732     fprint_error("Invalid OQGRAPH backing store ('%s.weight' attribute not set to a valid column of '%s')", p+1, options->table_name);
733     closefrm(edges);
734     free_table_share(share);
735     DBUG_RETURN(-1);
736   }
737 
738   if (!(graph_share = oqgraph::create(edges, origid, destid, weight)))
739   {
740     fprint_error("Unable to create graph instance.");
741     closefrm(edges);
742     free_table_share(share);
743     DBUG_RETURN(-1);
744   }
745   ref_length= oqgraph::sizeof_ref;
746 
747   graph = oqgraph::create(graph_share);
748   have_table_share = true;
749 
750   DBUG_RETURN(0);
751 }
752 
close(void)753 int ha_oqgraph::close(void)
754 {
755   DBUG_PRINT( "oq-debug", ("close()"));
756   if (graph->get_thd() != current_thd) {
757     DBUG_PRINT( "oq-debug", ("index_next_same g->table->in_use: 0x%lx <-- current_thd 0x%lx", (long) graph->get_thd(), (long) current_thd));
758     graph->set_thd(current_thd);
759   }
760   oqgraph::free(graph); graph= 0;
761   oqgraph::free(graph_share); graph_share= 0;
762 
763   if (have_table_share)
764   {
765     if (edges->file)
766       closefrm(edges);
767     free_table_share(share);
768     have_table_share = false;
769   }
770   return 0;
771 }
772 
update_key_stats()773 void ha_oqgraph::update_key_stats()
774 {
775   DBUG_PRINT( "oq-debug", ("update_key_stats()"));
776   for (uint i= 0; i < table->s->keys; i++)
777   {
778     KEY *key=table->key_info+i;
779     if (!key->rec_per_key)
780       continue;
781     if (key->algorithm != HA_KEY_ALG_BTREE)
782     {
783       if (key->flags & HA_NOSAME)
784         key->rec_per_key[key->user_defined_key_parts-1]= 1;
785       else
786       {
787         //unsigned vertices= graph->vertices_count();
788         //unsigned edges= graph->edges_count();
789         //uint no_records= vertices ? 2 * (edges + vertices) / vertices : 2;
790         //if (no_records < 2)
791         uint
792           no_records= 2;
793         key->rec_per_key[key->user_defined_key_parts-1]= no_records;
794       }
795     }
796   }
797   /* At the end of update_key_stats() we can proudly claim they are OK. */
798   //skey_stat_version= share->key_stat_version;
799 }
800 
801 
write_row(const byte * buf)802 int ha_oqgraph::write_row(const byte * buf)
803 {
804   return HA_ERR_TABLE_READONLY;
805 }
806 
update_row(const uchar * old,const uchar * buf)807 int ha_oqgraph::update_row(const uchar * old, const uchar * buf)
808 {
809   return HA_ERR_TABLE_READONLY;
810 }
811 
delete_row(const byte * buf)812 int ha_oqgraph::delete_row(const byte * buf)
813 {
814   return HA_ERR_TABLE_READONLY;
815 }
816 
index_read(byte * buf,const byte * key,uint key_len,enum ha_rkey_function find_flag)817 int ha_oqgraph::index_read(byte * buf, const byte * key, uint key_len, enum ha_rkey_function find_flag)
818 {
819   DBUG_ASSERT(inited==INDEX);
820   // reset before we have a cursor, so the memory is not junk, avoiding the sefgault in position() when select with order by (bug #1133093)
821   graph->init_row_ref(ref);
822   return index_read_idx(buf, active_index, key, key_len, find_flag);
823 }
824 
index_next_same(byte * buf,const byte * key,uint key_len)825 int ha_oqgraph::index_next_same(byte *buf, const byte *key, uint key_len)
826 {
827   if (graph->get_thd() != current_thd) {
828     DBUG_PRINT( "oq-debug", ("index_next_same g->table->in_use: 0x%lx <-- current_thd 0x%lx", (long) graph->get_thd(), (long) current_thd));
829     graph->set_thd(current_thd);
830   }
831   int res;
832   open_query::row row;
833   DBUG_ASSERT(inited==INDEX);
834   if (!(res= graph->fetch_row(row)))
835     res= fill_record(buf, row);
836   return error_code(res);
837 }
838 
839 #define LATCH_WAS CODE 0
840 #define LATCH_WAS_NUMBER 1
841 
842 /**
843  * This function parse the VARCHAR(n) latch specification into an integer operation specification compatible with
844  * v1-v3 oqgraph::search().
845  *
846  * If the string contains a number, this is directly converted from a decimal integer.
847  *
848  * Otherwise, a lookup table is used to convert from a string constant.
849  *
850  * It is anticipated that this function (and this file and class oqgraph) will be refactored to do this in a nicer way.
851  *
852  * FIXME: For the time being, only handles latin1 character set.
853  * @return false if parsing fails.
854  */
parse_latch_string_to_legacy_int(const String & value,int & latch)855 static int parse_latch_string_to_legacy_int(const String& value, int &latch)
856 {
857   // Attempt to parse as exactly an integer first.
858 
859   // Note: we are strict about not having whitespace, or garbage characters,
860   // so that the query result gets returned properly:
861   // Because of the way the result is built and used in fill_result,
862   // we have to exactly return in the latch column what was in the latch= clause
863   // otherwise the rows get filtered out by the query optimiser.
864 
865   // For the same reason, we cant simply treat latch='' as NO_SEARCH either.
866 
867   String latchValue = value;
868   char *eptr;
869   unsigned long int v = strtoul( latchValue.c_ptr_safe(), &eptr, 10);
870   if (!*eptr) {
871     // we had an unsigned number; remember 0 is valid too ('vertices' aka 'no_search'))
872     if (v < oqgraph::NUM_SEARCH_OP) {
873       latch = v;
874       return true;
875     }
876     // fall through  and test as a string (although it is unlikely we might have an operator starting with a number)
877   }
878 
879   const oqgraph_latch_op_table* entry = latch_ops_table;
880   for ( ; entry->key ; entry++) {
881     if (0 == strncmp(entry->key, latchValue.c_ptr_safe(), latchValue.length())) {
882       latch = entry->latch;
883       return true;
884     }
885   }
886   return false;
887 }
888 
index_read_idx(byte * buf,uint index,const byte * key,uint key_len,enum ha_rkey_function find_flag)889 int ha_oqgraph::index_read_idx(byte * buf, uint index, const byte * key,
890                         uint key_len, enum ha_rkey_function find_flag)
891 {
892   if (graph->get_thd() != current_thd) {
893     DBUG_PRINT( "oq-debug", ("index_read_idx g->table->in_use: 0x%lx <-- current_thd 0x%lx", (long) graph->get_thd(), (long) current_thd));
894     graph->set_thd(current_thd);
895   }
896 
897   Field **field= table->field;
898   KEY *key_info= table->key_info + index;
899   int res;
900   VertexID orig_id, dest_id;
901   int latch;
902   VertexID *orig_idp=0, *dest_idp=0;
903   int* latchp=0;
904   open_query::row row;
905 
906   DBUG_PRINT("oq-debug", ("thd: 0x%lx; index_read_idx()", (long) current_thd));
907 
908   bmove_align(buf, table->s->default_values, table->s->reclength);
909   key_restore(buf, (byte*) key, key_info, key_len);
910 
911   MY_BITMAP *old_map= dbug_tmp_use_all_columns(table, &table->read_set);
912   my_ptrdiff_t ptrdiff= buf - table->record[0];
913 
914   if (ptrdiff)
915   {
916     field[0]->move_field_offset(ptrdiff);
917     field[1]->move_field_offset(ptrdiff);
918     field[2]->move_field_offset(ptrdiff);
919   }
920 
921   String latchFieldValue;
922   if (!field[0]->is_null())
923   {
924 #ifdef RETAIN_INT_LATCH_COMPATIBILITY
925     if (field[0]->type() == MYSQL_TYPE_SHORT) {
926       latch= (int) field[0]->val_int();
927     } else
928 #endif
929     {
930       field[0]->val_str(&latchFieldValue, &latchFieldValue);
931       if (!parse_latch_string_to_legacy_int(latchFieldValue, latch)) {
932         // Invalid, so warn & fail
933         push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, ER_WRONG_ARGUMENTS, ER(ER_WRONG_ARGUMENTS), "OQGRAPH latch");
934         if (ptrdiff) /* fixes debug build assert - should be a tidier way to do this */
935         {
936           field[0]->move_field_offset(-ptrdiff);
937           field[1]->move_field_offset(-ptrdiff);
938           field[2]->move_field_offset(-ptrdiff);
939         }
940         dbug_tmp_restore_column_map(&table->read_set, old_map);
941         return error_code(oqgraph::NO_MORE_DATA);
942       }
943     }
944     latchp= &latch;
945   }
946 
947   if (!field[1]->is_null())
948   {
949     orig_id= (VertexID) field[1]->val_int();
950     orig_idp= &orig_id;
951   }
952 
953   if (!field[2]->is_null())
954   {
955     dest_id= (VertexID) field[2]->val_int();
956     dest_idp= &dest_id;
957   }
958 
959   if (ptrdiff)
960   {
961     field[0]->move_field_offset(-ptrdiff);
962     field[1]->move_field_offset(-ptrdiff);
963     field[2]->move_field_offset(-ptrdiff);
964   }
965   dbug_tmp_restore_column_map(&table->read_set, old_map);
966 
967   // Keep the latch around so we can use it in the query result later -
968   // See fill_record().
969   // at the moment our best option is to associate it with the graph
970   // so we pass the string now.
971   // In the future we should refactor parse_latch_string_to_legacy_int()
972   // into oqgraph instead.
973   if (latchp)
974     graph->retainLatchFieldValue(latchFieldValue.c_ptr_safe());
975   else
976     graph->retainLatchFieldValue(NULL);
977 
978 
979   DBUG_PRINT( "oq-debug", ("index_read_idx ::>> search(latch:%s,%ld,%ld)",
980           oqlatchToCode(latch), orig_idp?(long)*orig_idp:-1, dest_idp?(long)*dest_idp:-1));
981 
982   res= graph->search(latchp, orig_idp, dest_idp);
983 
984   DBUG_PRINT( "oq-debug", ("search() = %d", res));
985 
986   if (!res && !(res= graph->fetch_row(row))) {
987     res= fill_record(buf, row);
988   }
989   return error_code(res);
990 }
991 
fill_record(byte * record,const open_query::row & row)992 int ha_oqgraph::fill_record(byte *record, const open_query::row &row)
993 {
994   Field **field= table->field;
995 
996   bmove_align(record, table->s->default_values, table->s->reclength);
997 
998   MY_BITMAP *old_map= dbug_tmp_use_all_columns(table, &table->write_set);
999   my_ptrdiff_t ptrdiff= record - table->record[0];
1000 
1001   if (ptrdiff)
1002   {
1003     field[0]->move_field_offset(ptrdiff);
1004     field[1]->move_field_offset(ptrdiff);
1005     field[2]->move_field_offset(ptrdiff);
1006     field[3]->move_field_offset(ptrdiff);
1007     field[4]->move_field_offset(ptrdiff);
1008     field[5]->move_field_offset(ptrdiff);
1009   }
1010 
1011   DBUG_PRINT( "oq-debug", ("fill_record() ::>> %s,%ld,%ld,%lf,%ld,%ld",
1012           row.latch_indicator ? oqlatchToCode((int)row.latch) : "-",
1013           row.orig_indicator ? (long)row.orig : -1,
1014           row.dest_indicator ? (long)row.dest : -1,
1015           row.weight_indicator ? (double)row.weight : -1,
1016           row.seq_indicator ? (long)row.seq : -1,
1017           row.link_indicator ? (long)row.link : -1));
1018 
1019   // just each field specifically, no sense iterating
1020   if (row.latch_indicator)
1021   {
1022     field[0]->set_notnull();
1023     // Convert the latch back to a varchar32
1024     if (field[0]->type() == MYSQL_TYPE_VARCHAR) {
1025       field[0]->store(row.latchStringValue, row.latchStringValueLen, &my_charset_latin1);
1026     }
1027 #ifdef RETAIN_INT_LATCH_COMPATIBILITY
1028     else if (field[0]->type() == MYSQL_TYPE_SHORT) {
1029       field[0]->store((longlong) row.latch, 0);
1030     }
1031 #endif
1032 
1033   }
1034 
1035   if (row.orig_indicator)
1036   {
1037     field[1]->set_notnull();
1038     field[1]->store((longlong) row.orig, 0);
1039   }
1040 
1041   if (row.dest_indicator)
1042   {
1043     field[2]->set_notnull();
1044     field[2]->store((longlong) row.dest, 0);
1045   }
1046 
1047   if (row.weight_indicator)
1048   {
1049     field[3]->set_notnull();
1050     field[3]->store((double) row.weight);
1051   }
1052 
1053   if (row.seq_indicator)
1054   {
1055     field[4]->set_notnull();
1056     field[4]->store((longlong) row.seq, 0);
1057   }
1058 
1059   if (row.link_indicator)
1060   {
1061     field[5]->set_notnull();
1062     field[5]->store((longlong) row.link, 0);
1063   }
1064 
1065   if (ptrdiff)
1066   {
1067     field[0]->move_field_offset(-ptrdiff);
1068     field[1]->move_field_offset(-ptrdiff);
1069     field[2]->move_field_offset(-ptrdiff);
1070     field[3]->move_field_offset(-ptrdiff);
1071     field[4]->move_field_offset(-ptrdiff);
1072     field[5]->move_field_offset(-ptrdiff);
1073   }
1074   dbug_tmp_restore_column_map(&table->write_set, old_map);
1075 
1076   return 0;
1077 }
1078 
rnd_init(bool scan)1079 int ha_oqgraph::rnd_init(bool scan)
1080 {
1081   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
1082   edges->prepare_for_position();
1083   return error_code(graph->random(scan));
1084 }
1085 
rnd_next(byte * buf)1086 int ha_oqgraph::rnd_next(byte *buf)
1087 {
1088   if (graph->get_thd() != current_thd) {
1089     DBUG_PRINT( "oq-debug", ("rnd_next g->table->in_use: 0x%lx <-- current_thd 0x%lx", (long) graph->get_thd(), (long) current_thd));
1090     graph->set_thd(current_thd);
1091   }
1092   int res;
1093   open_query::row row = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
1094 
1095   if (!(res= graph->fetch_row(row)))
1096     res= fill_record(buf, row);
1097   return error_code(res);
1098 }
1099 
rnd_pos(byte * buf,byte * pos)1100 int ha_oqgraph::rnd_pos(byte * buf, byte *pos)
1101 {
1102   if (graph->get_thd() != current_thd) {
1103     DBUG_PRINT( "oq-debug", ("rnd_pos g->table->in_use: 0x%lx <-- current_thd 0x%lx", (long) graph->get_thd(), (long) current_thd));
1104     graph->set_thd(current_thd);
1105   }
1106   int res;
1107   open_query::row row;
1108   if (!(res= graph->fetch_row(row, pos)))
1109     res= fill_record(buf, row);
1110   return error_code(res);
1111 }
1112 
position(const byte * record)1113 void ha_oqgraph::position(const byte *record)
1114 {
1115   graph->row_ref((void*) ref); // Ref is aligned
1116 }
1117 
cmp_ref(const byte * ref1,const byte * ref2)1118 int ha_oqgraph::cmp_ref(const byte *ref1, const byte *ref2)
1119 {
1120   return memcmp(ref1, ref2, oqgraph::sizeof_ref);
1121 }
1122 
info(uint flag)1123 int ha_oqgraph::info(uint flag)
1124 {
1125   stats.records = graph->edges_count();
1126 
1127   /*
1128     If info() is called for the first time after open(), we will still
1129     have to update the key statistics. Hoping that a table lock is now
1130     in place.
1131   */
1132 //  if (key_stat_version != share->key_stat_version)
1133   //  update_key_stats();
1134   return 0;
1135 }
1136 
extra(enum ha_extra_function operation)1137 int ha_oqgraph::extra(enum ha_extra_function operation)
1138 {
1139   if (graph->get_thd() != ha_thd()) {
1140     DBUG_PRINT( "oq-debug", ("rnd_pos g->table->in_use: 0x%lx <-- current_thd 0x%lx", (long) graph->get_thd(), (long) current_thd));
1141     graph->set_thd(current_thd);
1142   }
1143   return edges->file->extra(operation);
1144 }
1145 
delete_all_rows()1146 int ha_oqgraph::delete_all_rows()
1147 {
1148   return HA_ERR_TABLE_READONLY;
1149 }
1150 
external_lock(THD * thd,int lock_type)1151 int ha_oqgraph::external_lock(THD *thd, int lock_type)
1152 {
1153   // This method is also called to _unlock_ (lock_type == F_UNLCK)
1154   // Which means we need to release things before we let the underlying backing table lock go...
1155   if (lock_type == F_UNLCK) {
1156     // If we have an index open on the backing table, we need to close it out here
1157     // this means destroying any open cursor first.
1158     // Then we can let the unlock go through to the backing table
1159     graph->release_cursor();
1160   }
1161 
1162   return edges->file->ha_external_lock(thd, lock_type);
1163 }
1164 
1165 
store_lock(THD * thd,THR_LOCK_DATA ** to,enum thr_lock_type lock_type)1166 THR_LOCK_DATA **ha_oqgraph::store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type)
1167 {
1168   return edges->file->store_lock(thd, to, lock_type);
1169 }
1170 
1171 /*
1172   We have to ignore ENOENT entries as the HEAP table is created on open and
1173   not when doing a CREATE on the table.
1174 */
1175 
delete_table(const char *)1176 int ha_oqgraph::delete_table(const char *)
1177 {
1178   DBUG_PRINT( "oq-debug", ("delete_table()"));
1179   return 0;
1180 }
1181 
rename_table(const char *,const char *)1182 int ha_oqgraph::rename_table(const char *, const char *)
1183 {
1184   DBUG_PRINT( "oq-debug", ("rename_table()"));
1185   return 0;
1186 }
1187 
1188 
records_in_range(uint inx,const key_range * min_key,const key_range * max_key,page_range * pages)1189 ha_rows ha_oqgraph::records_in_range(uint inx,
1190                                      const key_range *min_key,
1191                                      const key_range *max_key,
1192                                      page_range *pages)
1193 {
1194   if (graph->get_thd() != current_thd) {
1195     DBUG_PRINT( "oq-debug", ("g->table->in_use: 0x%lx <-- current_thd 0x%lx", (long) graph->get_thd(), (long) current_thd));
1196     graph->set_thd(current_thd);
1197   }
1198 
1199   KEY *key=table->key_info+inx;
1200 #ifdef VERBOSE_DEBUG
1201   {
1202     String temp;
1203     key->key_part[0].field->val_str(&temp);
1204     temp.c_ptr_safe();
1205     DBUG_PRINT( "oq-debug", ("thd: 0x%lx; records_in_range ::>> inx=%u", (long) current_thd, inx));
1206     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 ...
1207   }
1208 #endif
1209 
1210   if (!min_key || !max_key ||
1211       min_key->length != max_key->length ||
1212       min_key->length < key->key_length - key->key_part[2].store_length ||
1213       min_key->flag != HA_READ_KEY_EXACT ||
1214       max_key->flag != HA_READ_AFTER_KEY)
1215   {
1216     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 */
1217     {
1218       // If latch is not null and equals 0, return # nodes
1219 
1220       // How to decode the key,  For VARCHAR(32), from empirical observation using the debugger
1221       // and information gleaned from:
1222       //   http://grokbase.com/t/mysql/internals/095h6ch1q7/parsing-key-information
1223       //   http://dev.mysql.com/doc/internals/en/support-for-indexing.html#parsing-key-information
1224       //   comments in opt_range.cc
1225       // POSSIBLY ONLY VALID FOR INNODB!
1226 
1227       // For a the following query:
1228       //     SELECT * FROM graph2 WHERE latch = 'breadth_first' AND origid = 123 AND weight = 1;
1229       // key->key_part[0].field->ptr  is the value of latch, which is a 1-byte string length followed by the value ('breadth_first')
1230       // key->key_part[2].field->ptr  is the value of origid (123)
1231       // 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
1232       // so given this ordering we seem to be using the second key specified in create table (aka KEY (latch, destid, origid) USING HASH ))
1233 
1234       // min_key->key[0] is the 'null' bit and contains 0 in this instance
1235       // min_key->key[1..2] seems to be 16-bit string length
1236       // min_key->key[3..34] hold the varchar(32) value which is that specified in the query
1237       // min_key->key[35] is the null bit of origid
1238       // min_key->key[36..43] is the value in the query (123)
1239 
1240       // max_key->key[0] is the ;null' bit and contains 0 in this instance
1241       // max_key->key[1..2] seems to be 16-bit string length
1242       // max_key->key[3..34] hold the varchar(32) value which is that specified in the query
1243       // max_key->key[35] is the null bit of origid
1244       // max_key->key[36..43] is the value in the query (123)
1245 
1246       // But after knowing all that, all we care about is the latch value
1247 
1248       // First draft - ignore most of the stuff, but will likely break if query altered
1249 
1250       // It turns out there is a better way though, to access the string,
1251       // as demonstrated in key_unpack() of sql/key.cc
1252       String latchCode;
1253       int latch = -1;
1254       if (key->key_part[0].field->type() == MYSQL_TYPE_VARCHAR) {
1255 
1256         key->key_part[0].field->val_str(&latchCode);
1257 
1258         parse_latch_string_to_legacy_int( latchCode, latch);
1259       }
1260 
1261       // what if someone did something dumb, like mismatching the latches?
1262 
1263 #ifdef RETAIN_INT_LATCH_COMPATIBILITY
1264       else if (key->key_part[0].field->type() == MYSQL_TYPE_SHORT) {
1265         // If not null, and zero ...
1266         // Note, the following code relies on the fact that the three bytes
1267         // at beginning of min_key just happen to be the null indicator and the
1268         // 16-bit value of the latch ...
1269         // this will fall through if the user alter-tabled to not null
1270         if (key->key_part[0].null_bit && !min_key->key[0] &&
1271           !min_key->key[1] && !min_key->key[2]) {
1272           latch = oqgraph::NO_SEARCH;
1273         }
1274       }
1275 #endif
1276       if (latch != oqgraph::NO_SEARCH) {
1277         // Invalid key type...
1278         // Don't assert, in case the user used alter table on us
1279         return HA_POS_ERROR;    // Can only use exact keys
1280       }
1281       unsigned N = graph->vertices_count();
1282       DBUG_PRINT( "oq-debug", ("records_in_range ::>> N=%u (vertices)", N));
1283       return N;
1284     }
1285     return HA_POS_ERROR;        // Can only use exact keys
1286   }
1287 
1288   if (stats.records <= 1) {
1289     DBUG_PRINT( "oq-debug", ("records_in_range ::>> N=%u (stats)", (unsigned)stats.records));
1290     return stats.records;
1291   }
1292 
1293   /* Assert that info() did run. We need current statistics here. */
1294   //DBUG_ASSERT(key_stat_version == share->key_stat_version);
1295   //ha_rows result= key->rec_per_key[key->user_defined_key_parts-1];
1296   ha_rows result= 10;
1297   DBUG_PRINT( "oq-debug", ("records_in_range ::>> N=%u", (unsigned)result));
1298 
1299   return result;
1300 }
1301 
1302 
create(const char * name,TABLE * table_arg,HA_CREATE_INFO * create_info)1303 int ha_oqgraph::create(const char *name, TABLE *table_arg, HA_CREATE_INFO *create_info)
1304 {
1305   DBUG_ENTER("ha_oqgraph::create");
1306   DBUG_PRINT( "oq-debug", ("create(name=%s)", name));
1307 
1308   if (oqgraph_check_table_structure(table_arg)) {
1309     DBUG_RETURN(HA_WRONG_CREATE_OPTION);
1310   }
1311 
1312   DBUG_RETURN(0);
1313 }
1314 
1315 
update_create_info(HA_CREATE_INFO * create_info)1316 void ha_oqgraph::update_create_info(HA_CREATE_INFO *create_info)
1317 {
1318   table->file->info(HA_STATUS_AUTO);
1319 }
1320 
1321 // --------------------
1322 // Handler description.
1323 // --------------------
1324 
1325 
1326 static const char oqgraph_description[]=
1327   "Open Query Graph Computation Engine "
1328   "(http://openquery.com/graph)";
1329 
1330 struct st_mysql_storage_engine oqgraph_storage_engine=
1331 { MYSQL_HANDLERTON_INTERFACE_VERSION };
1332 
1333 extern "C" const char* const oqgraph_boost_version;
1334 
1335 static const char *oqgraph_status_verbose_debug =
1336 #ifdef VERBOSE_DEBUG
1337   "Verbose Debug is enabled. Performance may be adversely impacted.";
1338 #else
1339   "Verbose Debug is not enabled.";
1340 #endif
1341 
1342 static const char *oqgraph_status_latch_compat_mode =
1343 #ifdef RETAIN_INT_LATCH_COMPATIBILITY
1344   "Legacy tables with integer latches are supported.";
1345 #else
1346   "Legacy tables with integer latches are not supported.";
1347 #endif
1348 
1349 static struct st_mysql_show_var oqgraph_status[]=
1350 {
1351   { "OQGraph_Boost_Version", (char*) &oqgraph_boost_version, SHOW_CHAR_PTR },
1352   /* We thought about reporting the Judy version, but there seems to be no way to get that from code in the first place. */
1353   { "OQGraph_Verbose_Debug", (char*) &oqgraph_status_verbose_debug, SHOW_CHAR_PTR },
1354   { "OQGraph_Compat_mode",   (char*) &oqgraph_status_latch_compat_mode, SHOW_CHAR_PTR },
1355   { 0, 0, SHOW_UNDEF }
1356 };
1357 
1358 #ifdef RETAIN_INT_LATCH_COMPATIBILITY
1359 static MYSQL_SYSVAR_BOOL( allow_create_integer_latch, g_allow_create_integer_latch, PLUGIN_VAR_RQCMDARG,
1360                         "Allow creation of integer latches so the upgrade logic can be tested. Not for normal use.",
1361                         NULL, NULL, FALSE);
1362 #endif
1363 
1364 static struct st_mysql_sys_var* oqgraph_sysvars[]= {
1365 #ifdef RETAIN_INT_LATCH_COMPATIBILITY
1366   MYSQL_SYSVAR(allow_create_integer_latch),
1367 #endif
1368   0
1369 };
1370 
maria_declare_plugin(oqgraph)1371 maria_declare_plugin(oqgraph)
1372 {
1373   MYSQL_STORAGE_ENGINE_PLUGIN,
1374   &oqgraph_storage_engine,
1375   "OQGRAPH",
1376   "Arjen Lentz & Antony T Curtis, Open Query, and Andrew McDonnell",
1377   oqgraph_description,
1378   PLUGIN_LICENSE_GPL,
1379   oqgraph_init,                  /* Plugin Init                  */
1380   oqgraph_fini,                  /* Plugin Deinit                */
1381   0x0300,                        /* Version: 3s.0                */
1382   oqgraph_status,                /* status variables             */
1383   oqgraph_sysvars,               /* system variables             */
1384   "3.0",
1385   MariaDB_PLUGIN_MATURITY_GAMMA
1386 }
1387 maria_declare_plugin_end;
1388