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