1 /***********************************************************************
2 
3 Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License, version 2.0,
7 as published by the Free Software Foundation.
8 
9 This program is also distributed with certain software (including
10 but not limited to OpenSSL) that is licensed under separate terms,
11 as designated in a particular file or component or in included license
12 documentation.  The authors of MySQL hereby grant you an additional
13 permission to link the program and your derivative works with the
14 separately licensed software that they have included with MySQL.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 GNU General Public License, version 2.0, for more details.
20 
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
24 
25 ***********************************************************************/
26 
27 /**************************************************/ /**
28  @file
29  InnoDB Memcached configurations
30 
31  Created 04/12/2011 Jimmy Yang
32  *******************************************************/
33 
34 #include <errno.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 
39 #include "my_compiler.h"
40 
41 #include "innodb_api.h"
42 #include "innodb_cb_api.h"
43 #include "innodb_config.h"
44 #include "innodb_utility.h"
45 
46 /** Configure options enum IDs, their "names" and their default value */
47 option_t config_option_names[] = {
48     {OPTION_ID_COL_SEP, COLUMN_SEPARATOR, {"|", 1}},
49     {OPTION_ID_TBL_MAP_SEP, TABLE_MAP_SEPARATOR, {".", 1}}};
50 
51 /**********************************************************************/ /**
52  Makes a NUL-terminated copy of a nonterminated string.
53  @return own: a copy of the string, must be deallocated by caller */
my_strdupl(const char * str,int len)54 static char *my_strdupl(
55     /*=======*/
56     const char *str, /*!< in: string to be copied */
57     int len)         /*!< in: length of str, in bytes */
58 {
59   char *s = (char *)malloc(len + 1);
60 
61   if (!s) {
62     return (NULL);
63   }
64 
65   s[len] = 0;
66 
67   return ((char *)memcpy(s, str, len));
68 }
69 
70 /**********************************************************************/ /**
71  This function frees meta info structures */
innodb_config_free(meta_cfg_info_t * item)72 void innodb_config_free(
73     /*===============*/
74     meta_cfg_info_t *item) /*!< in: meta info structure */
75 {
76   int i;
77 
78   for (i = 0; i < CONTAINER_NUM_COLS; i++) {
79     if (item->col_info[i].col_name) {
80       free(item->col_info[i].col_name);
81       item->col_info[i].col_name = NULL;
82     }
83   }
84 
85   if (item->index_info.idx_name) {
86     free(item->index_info.idx_name);
87     item->index_info.idx_name = NULL;
88   }
89 
90   if (item->extra_col_info) {
91     for (i = 0; i < item->n_extra_col; i++) {
92       free(item->extra_col_info[i].col_name);
93       item->extra_col_info[i].col_name = NULL;
94     }
95 
96     free(item->extra_col_info);
97     item->extra_col_info = NULL;
98   }
99 }
100 
101 /**********************************************************************/ /**
102  This function parses possible multiple column name separated by ",", ";"
103  or " " in the input "str" for the memcached "value" field.
104  @return true if everything works out fine */
innodb_config_parse_value_col(meta_cfg_info_t * item,char * str,int len)105 static bool innodb_config_parse_value_col(
106     /*==========================*/
107     meta_cfg_info_t *item, /*!< in: meta info structure */
108     char *str,             /*!< in: column name(s) string */
109     int len)               /*!< in: length of above string */
110 {
111   static const char *sep = " ;,|\n";
112   char *last;
113   char *column_str;
114   int num_cols = 0;
115   char *my_str = my_strdupl(str, len);
116 
117   /* Find out how many column names in the string */
118   for (column_str = strtok_r(my_str, sep, &last); column_str;
119        column_str = strtok_r(NULL, sep, &last)) {
120     num_cols++;
121   }
122 
123   free(my_str);
124 
125   my_str = str;
126 
127   if (num_cols > 1) {
128     int i = 0;
129     item->extra_col_info =
130         (meta_column_t *)malloc(num_cols * sizeof(*item->extra_col_info));
131 
132     if (!item->extra_col_info) {
133       return (false);
134     }
135 
136     for (column_str = strtok_r(my_str, sep, &last); column_str;
137          column_str = strtok_r(NULL, sep, &last)) {
138       item->extra_col_info[i].col_name_len = strlen(column_str);
139       item->extra_col_info[i].col_name =
140           my_strdupl(column_str, item->extra_col_info[i].col_name_len);
141       item->extra_col_info[i].field_id = -1;
142       i++;
143     }
144 
145     item->n_extra_col = num_cols;
146   } else {
147     item->extra_col_info = NULL;
148     item->n_extra_col = 0;
149   }
150 
151   return (true);
152 }
153 
154 /**********************************************************************/ /**
155  This function opens the cache_policy configuration table, and find the
156  table and column info that used for memcached data
157  @return true if everything works out fine */
innodb_read_cache_policy(meta_cfg_info_t * item,void * thd)158 static bool innodb_read_cache_policy(
159     /*=====================*/
160     meta_cfg_info_t *item, /*!< in: meta info structure */
161     void *thd)             /*!< in/out: MySQL THD */
162 {
163   ib_trx_t ib_trx;
164   ib_crsr_t crsr = NULL;
165   ib_crsr_t idx_crsr = NULL;
166   ib_tpl_t tpl = NULL;
167   ib_err_t err = DB_SUCCESS;
168   int n_cols MY_ATTRIBUTE((unused));
169   int i;
170   ib_ulint_t data_len;
171   ib_col_meta_t col_meta;
172 
173   ib_trx = ib_cb_trx_begin(IB_TRX_READ_COMMITTED, true, false, thd);
174 
175   err = innodb_api_begin(NULL, MCI_CFG_DB_NAME, MCI_CFG_CACHE_POLICIES, NULL,
176                          ib_trx, &crsr, &idx_crsr, IB_LOCK_S);
177 
178   if (err != DB_SUCCESS) {
179     fprintf(stderr,
180             " InnoDB_Memcached: Cannot open config table"
181             "'%s' in database '%s'. Error %d\n",
182             MCI_CFG_CACHE_POLICIES, MCI_CFG_DB_NAME, err);
183     err = DB_ERROR;
184     goto func_exit;
185   }
186 
187   tpl = innodb_cb_read_tuple_create(crsr);
188 
189   /* Currently, we support one table per memcached setup.
190   We could extend that limit later */
191   err = innodb_cb_cursor_first(crsr);
192 
193   if (err != DB_SUCCESS) {
194     fprintf(stderr,
195             " InnoDB_Memcached: failed to locate entry in"
196             " config table '%s' in database '%s' \n",
197             MCI_CFG_CACHE_POLICIES, MCI_CFG_DB_NAME);
198     err = DB_ERROR;
199     goto func_exit;
200   }
201 
202   err = ib_cb_cursor_read_row(crsr, tpl, NULL, 0, NULL, NULL, NULL);
203 
204   n_cols = innodb_cb_tuple_get_n_cols(tpl);
205 
206   assert(n_cols >= CACHE_POLICY_NUM_COLS);
207 
208   for (i = 0; i < CACHE_POLICY_NUM_COLS; ++i) {
209     char opt_name;
210     meta_cache_opt_t opt_val;
211 
212     /* Skip cache policy name for now, We could have
213     different cache policy stored, and switch dynamically */
214     if (i == CACHE_POLICY_NAME) {
215       continue;
216     }
217 
218     data_len = innodb_cb_col_get_meta(tpl, i, &col_meta);
219 
220     if (data_len == IB_SQL_NULL) {
221       opt_val = META_CACHE_OPT_INNODB;
222     } else {
223       opt_name = *(char *)innodb_cb_col_get_value(tpl, i);
224 
225       opt_val = (meta_cache_opt_t)opt_name;
226     }
227 
228     if (opt_val >= META_CACHE_NUM_OPT || opt_val < META_CACHE_OPT_INNODB) {
229       fprintf(stderr,
230               " InnoDB_Memcached: Invalid Cache"
231               " Policy %d. Reset to innodb_only\n",
232               (int)opt_val);
233       opt_val = META_CACHE_OPT_INNODB;
234     }
235 
236     switch (i) {
237       case CACHE_POLICY_GET:
238         item->get_option = opt_val;
239         break;
240       case CACHE_POLICY_SET:
241         item->set_option = opt_val;
242         break;
243       case CACHE_POLICY_DEL:
244         item->del_option = opt_val;
245         break;
246       case CACHE_POLICY_FLUSH:
247         item->flush_option = opt_val;
248         break;
249       default:
250         assert(0);
251     }
252   }
253 
254 func_exit:
255 
256   innodb_cb_cursor_close(crsr);
257 
258   if (tpl) {
259     innodb_cb_tuple_delete(tpl);
260   }
261 
262   innodb_cb_trx_commit(ib_trx);
263   ib_cb_trx_release(ib_trx);
264 
265   return (err == DB_SUCCESS || err == DB_END_OF_INDEX);
266 }
267 
268 /**********************************************************************/ /**
269  This function opens the config_options configuration table, and find the
270  table and column info that used for memcached data
271  @return true if everything works out fine */
innodb_read_config_option(meta_cfg_info_t * item,void * thd)272 static bool innodb_read_config_option(
273     /*======================*/
274     meta_cfg_info_t *item, /*!< in: meta info structure */
275     void *thd)             /*!< in/out: MySQL THD */
276 {
277   ib_trx_t ib_trx;
278   ib_crsr_t crsr = NULL;
279   ib_crsr_t idx_crsr = NULL;
280   ib_tpl_t tpl = NULL;
281   ib_err_t err = DB_SUCCESS;
282   int n_cols MY_ATTRIBUTE((unused));
283   int i;
284   ib_ulint_t data_len;
285   ib_col_meta_t col_meta;
286   int current_option = -1;
287 
288   ib_trx = ib_cb_trx_begin(IB_TRX_READ_COMMITTED, true, false, thd);
289   err = innodb_api_begin(NULL, MCI_CFG_DB_NAME, MCI_CFG_CONFIG_OPTIONS, NULL,
290                          ib_trx, &crsr, &idx_crsr, IB_LOCK_S);
291 
292   if (err != DB_SUCCESS) {
293     fprintf(stderr,
294             " InnoDB_Memcached: Cannot open config table"
295             "'%s' in database '%s'\n",
296             MCI_CFG_CONFIG_OPTIONS, MCI_CFG_DB_NAME);
297     err = DB_ERROR;
298     goto func_exit;
299   }
300 
301   tpl = innodb_cb_read_tuple_create(crsr);
302 
303   err = innodb_cb_cursor_first(crsr);
304 
305   if (err != DB_SUCCESS) {
306     fprintf(stderr,
307             " InnoDB_Memcached: failed to locate entry in"
308             " config table '%s' in database '%s' \n",
309             MCI_CFG_CONFIG_OPTIONS, MCI_CFG_DB_NAME);
310     err = DB_ERROR;
311     goto func_exit;
312   }
313 
314   do {
315     err = ib_cb_cursor_read_row(crsr, tpl, NULL, 0, NULL, NULL, NULL);
316 
317     if (err != DB_SUCCESS) {
318       fprintf(stderr,
319               " InnoDB_Memcached: failed to read"
320               " row from config table '%s' in"
321               " database '%s' \n",
322               MCI_CFG_CONFIG_OPTIONS, MCI_CFG_DB_NAME);
323       err = DB_ERROR;
324       goto func_exit;
325     }
326 
327     n_cols = innodb_cb_tuple_get_n_cols(tpl);
328 
329     assert(n_cols >= CONFIG_OPT_NUM_COLS);
330 
331     for (i = 0; i < CONFIG_OPT_NUM_COLS; ++i) {
332       char *key;
333 
334       data_len = innodb_cb_col_get_meta(tpl, i, &col_meta);
335 
336       assert(data_len != IB_SQL_NULL);
337 
338       if (i == CONFIG_OPT_KEY) {
339         int j;
340         key = (char *)innodb_cb_col_get_value(tpl, i);
341         current_option = -1;
342 
343         for (j = 0; j < OPTION_ID_NUM_OPTIONS; j++) {
344           /* Currently, we only support one
345           configure option, that is the string
346           "separator" */
347           if (strcmp(key, config_option_names[j].name) == 0) {
348             current_option = config_option_names[j].id;
349             break;
350           }
351         }
352       }
353 
354       if (i == CONFIG_OPT_VALUE && current_option >= 0) {
355         int max_len;
356 
357         /* The maximum length for delimiter is
358         MAX_DELIMITER_LEN */
359         max_len = (data_len > MAX_DELIMITER_LEN) ? MAX_DELIMITER_LEN : data_len;
360 
361         memcpy(item->options[current_option].value,
362                innodb_cb_col_get_value(tpl, i), max_len);
363 
364         item->options[current_option].value[max_len] = 0;
365 
366         item->options[current_option].value_len = max_len;
367       }
368     }
369 
370     err = ib_cb_cursor_next(crsr);
371 
372   } while (err == DB_SUCCESS);
373 
374 func_exit:
375 
376   innodb_cb_cursor_close(crsr);
377 
378   if (tpl) {
379     innodb_cb_tuple_delete(tpl);
380   }
381 
382   innodb_cb_trx_commit(ib_trx);
383   ib_cb_trx_release(ib_trx);
384 
385   return (err == DB_SUCCESS || err == DB_END_OF_INDEX);
386 }
387 
388 /**********************************************************************/ /**
389  This function opens the "containers" configuration table, and find the
390  table and column info that used for memcached data, and instantiates
391  meta_cfg_info_t structure for such metadata.
392  @return instantiated configure info item if everything works out fine,
393  callers are responsible to free the memory returned by this function */
innodb_config_add_item(ib_tpl_t tpl,hash_table_t * eng_meta_hash,void * thd)394 static meta_cfg_info_t *innodb_config_add_item(
395     /*===================*/
396     ib_tpl_t tpl,                /*!< in: container row we are fetching
397                                  row from */
398     hash_table_t *eng_meta_hash, /*!< in/out: hash table to insert
399                                  the row */
400     void *thd)                   /*!< in/out: MySQL THD */
401 {
402   ib_err_t err = DB_SUCCESS;
403   int n_cols;
404   int i;
405   ib_ulint_t data_len;
406   meta_cfg_info_t *item = NULL;
407   ib_col_meta_t col_meta;
408   int fold;
409 
410   n_cols = innodb_cb_tuple_get_n_cols(tpl);
411 
412   if (n_cols < CONTAINER_NUM_COLS) {
413     fprintf(stderr,
414             " InnoDB_Memcached: config table '%s' in"
415             " database '%s' has only %d column(s),"
416             " server is expecting %d columns\n",
417             MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME, n_cols,
418             CONTAINER_NUM_COLS);
419     err = DB_ERROR;
420     goto func_exit;
421   }
422 
423   item = (meta_cfg_info_t *)malloc(sizeof(*item));
424 
425   memset(item, 0, sizeof(*item));
426 
427   /* Get the column mappings (column for each memcached data */
428   for (i = 0; i < CONTAINER_NUM_COLS; ++i) {
429     data_len = innodb_cb_col_get_meta(tpl, i, &col_meta);
430 
431     if (data_len == IB_SQL_NULL) {
432       fprintf(stderr,
433               " InnoDB_Memcached: column %d in"
434               " the entry for config table '%s' in"
435               " database '%s' has an invalid"
436               " NULL value\n",
437               i, MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME);
438 
439       err = DB_ERROR;
440       goto func_exit;
441     }
442 
443     item->col_info[i].col_name_len = data_len;
444 
445     item->col_info[i].col_name =
446         my_strdupl((char *)innodb_cb_col_get_value(tpl, i), data_len);
447 
448     item->col_info[i].field_id = -1;
449 
450     if (i == CONTAINER_VALUE) {
451       innodb_config_parse_value_col(item, item->col_info[i].col_name, data_len);
452     }
453   }
454 
455   /* Last column is about the unique index name on key column */
456   data_len = innodb_cb_col_get_meta(tpl, i, &col_meta);
457 
458   if (data_len == IB_SQL_NULL) {
459     fprintf(stderr,
460             " InnoDB_Memcached: There must be a unique"
461             " index on memcached table's key column\n");
462     err = DB_ERROR;
463     goto func_exit;
464   }
465 
466   item->index_info.idx_name =
467       my_strdupl((char *)innodb_cb_col_get_value(tpl, i), data_len);
468 
469   if (!innodb_verify(item, thd)) {
470     err = DB_ERROR;
471     goto func_exit;
472   }
473 
474   fold = ut_fold_string(item->col_info[0].col_name);
475   HASH_INSERT(meta_cfg_info_t, name_hash, eng_meta_hash, fold, item);
476 
477 func_exit:
478   if (err != DB_SUCCESS && item) {
479     free(item);
480     item = NULL;
481   }
482 
483   return (item);
484 }
485 
486 /**********************************************************************/ /**
487  This function opens the "containers" table, reads in all rows and
488  instantiates the metadata hash table.
489  @return the default configuration setting (whose mapping name is "default") */
innodb_config_meta_hash_init(hash_table_t * meta_hash,void * thd)490 meta_cfg_info_t *innodb_config_meta_hash_init(
491     /*=========================*/
492     hash_table_t *meta_hash, /*!< in/out: InnoDB Memcached engine */
493     void *thd)               /*!< in/out: MySQL THD */
494 {
495   ib_trx_t ib_trx;
496   ib_crsr_t crsr = NULL;
497   ib_crsr_t idx_crsr = NULL;
498   ib_tpl_t tpl = NULL;
499   ib_err_t err = DB_SUCCESS;
500   meta_cfg_info_t *default_item = NULL;
501 
502   ib_trx = ib_cb_trx_begin(IB_TRX_READ_COMMITTED, true, false, thd);
503   err = innodb_api_begin(NULL, MCI_CFG_DB_NAME, MCI_CFG_CONTAINER_TABLE, NULL,
504                          ib_trx, &crsr, &idx_crsr, IB_LOCK_S);
505 
506   if (err != DB_SUCCESS) {
507     fprintf(stderr,
508             " InnoDB_Memcached: Please create config table"
509             "'%s' in database '%s' by running"
510             " 'innodb_memcached_config.sql. error %s'\n",
511             MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME, ib_cb_ut_strerr(err));
512     err = DB_ERROR;
513     goto func_exit;
514   }
515 
516   tpl = innodb_cb_read_tuple_create(crsr);
517 
518   /* If name field is NULL, just read the first row */
519   err = innodb_cb_cursor_first(crsr);
520 
521   while (err == DB_SUCCESS) {
522     meta_cfg_info_t *item;
523 
524     err = ib_cb_cursor_read_row(crsr, tpl, NULL, 0, NULL, NULL, NULL);
525 
526     if (err != DB_SUCCESS) {
527       fprintf(stderr,
528               " InnoDB_Memcached: failed to read row"
529               " from config table '%s' in database"
530               " '%s' \n",
531               MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME);
532       err = DB_ERROR;
533       goto func_exit;
534     }
535 
536     item = innodb_config_add_item(tpl, meta_hash, thd);
537 
538     /* First initialize default setting to be the first row
539     of the table */
540     /* If there are any setting whose name is "default",
541     then set default_item to point to this setting, otherwise
542     point it to the first row of the table */
543     if (default_item == NULL ||
544         (item && strcmp(item->col_info[0].col_name, "default") == 0)) {
545       default_item = item;
546     }
547 
548     err = ib_cb_cursor_next(crsr);
549   }
550 
551   if (err == DB_END_OF_INDEX) {
552     err = DB_SUCCESS;
553   }
554 
555   if (err != DB_SUCCESS) {
556     fprintf(stderr,
557             " InnoDB_Memcached: failed to locate entry in"
558             " config table '%s' in database '%s' \n",
559             MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME);
560     err = DB_ERROR;
561   }
562 
563 func_exit:
564 
565   innodb_cb_cursor_close(crsr);
566 
567   if (tpl) {
568     innodb_cb_tuple_delete(tpl);
569   }
570 
571   innodb_cb_trx_commit(ib_trx);
572   ib_cb_trx_release(ib_trx);
573 
574   return (default_item);
575 }
576 
577 /**********************************************************************/ /**
578  This function opens the "containers" configuration table, and find the
579  table and column info that used for memcached data
580  @return true if everything works out fine */
innodb_config_container(const char * name,size_t name_len,hash_table_t * meta_hash,void * thd)581 static meta_cfg_info_t *innodb_config_container(
582     /*====================*/
583     const char *name,        /*!< in: option name to look for */
584     size_t name_len,         /*!< in: option name length */
585     hash_table_t *meta_hash, /*!< in: engine hash table */
586     void *thd)               /*!< in/out: MySQL THD */
587 {
588   ib_trx_t ib_trx;
589   ib_crsr_t crsr = NULL;
590   ib_crsr_t idx_crsr = NULL;
591   ib_tpl_t tpl = NULL;
592   ib_err_t err = DB_SUCCESS;
593   int n_cols;
594   int i;
595   ib_ulint_t data_len;
596   ib_col_meta_t col_meta;
597   ib_tpl_t read_tpl = NULL;
598   meta_cfg_info_t *item = NULL;
599 
600   if (name != NULL) {
601     ib_ulint_t fold;
602 
603     assert(meta_hash);
604 
605     fold = ut_fold_string(name);
606     HASH_SEARCH(name_hash, meta_hash, fold, meta_cfg_info_t *, item,
607                 (name_len == item->col_info[0].col_name_len &&
608                  strcmp(name, item->col_info[0].col_name) == 0));
609 
610     if (item) {
611       return (item);
612     }
613   }
614 
615   ib_trx = ib_cb_trx_begin(IB_TRX_READ_COMMITTED, true, false, thd);
616   err = innodb_api_begin(NULL, MCI_CFG_DB_NAME, MCI_CFG_CONTAINER_TABLE, NULL,
617                          ib_trx, &crsr, &idx_crsr, IB_LOCK_S);
618 
619   if (err != DB_SUCCESS) {
620     fprintf(stderr,
621             " InnoDB_Memcached: Please create config table"
622             "'%s' in database '%s' by running"
623             " 'innodb_memcached_config.sql. error %d'\n",
624             MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME, err);
625     err = DB_ERROR;
626     goto func_exit;
627   }
628 
629   if (!name) {
630     tpl = innodb_cb_read_tuple_create(crsr);
631 
632     /* If name field is NULL, just read the first row */
633     err = innodb_cb_cursor_first(crsr);
634   } else {
635     /* User supplied a config option name, find it */
636     tpl = ib_cb_sec_search_tuple_create(crsr);
637 
638     err = ib_cb_col_set_value(tpl, 0, name, name_len, true);
639 
640     ib_cb_cursor_set_match_mode(crsr, IB_EXACT_MATCH);
641     err = ib_cb_cursor_moveto(crsr, tpl, IB_CUR_GE, 0);
642   }
643 
644   if (err != DB_SUCCESS) {
645     fprintf(stderr,
646             " InnoDB_Memcached: failed to locate entry in"
647             " config table '%s' in database '%s' \n",
648             MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME);
649     err = DB_ERROR;
650     goto func_exit;
651   }
652 
653   if (!name) {
654     read_tpl = tpl;
655     err = ib_cb_cursor_read_row(crsr, tpl, NULL, 0, NULL, NULL, NULL);
656   } else {
657     read_tpl = ib_cb_clust_read_tuple_create(crsr);
658 
659     err = ib_cb_cursor_read_row(crsr, read_tpl, NULL, 0, NULL, NULL, NULL);
660   }
661 
662   if (err != DB_SUCCESS) {
663     fprintf(stderr,
664             " InnoDB_Memcached: failed to read row from"
665             " config table '%s' in database '%s' \n",
666             MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME);
667     err = DB_ERROR;
668     goto func_exit;
669   }
670 
671   n_cols = innodb_cb_tuple_get_n_cols(read_tpl);
672 
673   if (n_cols < CONTAINER_NUM_COLS) {
674     fprintf(stderr,
675             " InnoDB_Memcached: config table '%s' in"
676             " database '%s' has only %d column(s),"
677             " server is expecting %d columns\n",
678             MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME, n_cols,
679             CONTAINER_NUM_COLS);
680     err = DB_ERROR;
681     goto func_exit;
682   }
683 
684   item = (meta_cfg_info_t *)malloc(sizeof(*item));
685   memset(item, 0, sizeof(*item));
686 
687   /* Get the column mappings (column for each memcached data */
688   for (i = 0; i < CONTAINER_NUM_COLS; ++i) {
689     data_len = innodb_cb_col_get_meta(read_tpl, i, &col_meta);
690 
691     if (data_len == IB_SQL_NULL) {
692       fprintf(stderr,
693               " InnoDB_Memcached: column %d in"
694               " the entry for config table '%s' in"
695               " database '%s' has an invalid"
696               " NULL value\n",
697               i, MCI_CFG_CONTAINER_TABLE, MCI_CFG_DB_NAME);
698 
699       err = DB_ERROR;
700       goto func_exit;
701     }
702 
703     item->col_info[i].col_name_len = data_len;
704 
705     item->col_info[i].col_name =
706         my_strdupl((char *)innodb_cb_col_get_value(read_tpl, i), data_len);
707 
708     item->col_info[i].field_id = -1;
709 
710     if (i == CONTAINER_VALUE) {
711       innodb_config_parse_value_col(item, item->col_info[i].col_name, data_len);
712     }
713   }
714 
715   /* Last column is about the unique index name on key column */
716   data_len = innodb_cb_col_get_meta(read_tpl, i, &col_meta);
717 
718   if (data_len == IB_SQL_NULL) {
719     fprintf(stderr,
720             " InnoDB_Memcached: There must be a unique"
721             " index on memcached table's key column\n");
722     err = DB_ERROR;
723     goto func_exit;
724   }
725 
726   item->index_info.idx_name =
727       my_strdupl((char *)innodb_cb_col_get_value(read_tpl, i), data_len);
728 
729   if (!innodb_verify(item, thd)) {
730     err = DB_ERROR;
731   }
732 
733 func_exit:
734 
735   innodb_cb_cursor_close(crsr);
736 
737   if (tpl) {
738     innodb_cb_tuple_delete(tpl);
739   }
740 
741   innodb_cb_trx_commit(ib_trx);
742   ib_cb_trx_release(ib_trx);
743 
744   if (err != DB_SUCCESS) {
745     free(item);
746     item = NULL;
747   } else {
748     ib_ulint_t fold;
749 
750     fold = ut_fold_string(item->col_info[0].col_name);
751     HASH_INSERT(meta_cfg_info_t, name_hash, meta_hash, fold, item);
752   }
753 
754   return (item);
755 }
756 
757 /**********************************************************************/ /**
758  This function verifies "value" column(s) specified by configure table are of
759  the correct type
760  @return DB_SUCCESS if everything is verified */
innodb_config_value_col_verify(const char * name,meta_cfg_info_t * meta_info,ib_col_meta_t * col_meta,int col_id,meta_column_t * col_verify)761 static ib_err_t innodb_config_value_col_verify(
762     /*===========================*/
763     const char *name,           /*!< in: column name */
764     meta_cfg_info_t *meta_info, /*!< in: meta info structure */
765     ib_col_meta_t *col_meta,    /*!< in: column metadata */
766     int col_id,                 /*!< in: column ID */
767     meta_column_t *col_verify)  /*!< in: verify structure */
768 {
769   ib_err_t err = DB_NOT_FOUND;
770   char table_name[MAX_TABLE_NAME_LEN + MAX_DATABASE_NAME_LEN];
771   char *dbname;
772   char *tname;
773 
774   /* Get table name. */
775   dbname = meta_info->col_info[CONTAINER_DB].col_name;
776   tname = meta_info->col_info[CONTAINER_TABLE].col_name;
777 #ifdef __WIN__
778   sprintf(table_name, "%s\%s", dbname, tname);
779 #else
780   snprintf(table_name, sizeof(table_name), "%s/%s", dbname, tname);
781 #endif
782 
783   if (!meta_info->n_extra_col) {
784     meta_column_t *cinfo = meta_info->col_info;
785 
786     /* "value" column must be of CHAR, VARCHAR or BLOB type */
787     if (strcmp(name, cinfo[CONTAINER_VALUE].col_name) == 0) {
788       if (col_meta->type != IB_VARCHAR && col_meta->type != IB_CHAR &&
789           col_meta->type != IB_BLOB && col_meta->type != IB_CHAR_ANYCHARSET &&
790           col_meta->type != IB_VARCHAR_ANYCHARSET && col_meta->type != IB_INT) {
791         fprintf(stderr,
792                 " InnoDB_Memcached: the value"
793                 " column %s in table %s"
794                 " should be INTEGER, CHAR or"
795                 " VARCHAR.\n",
796                 name, table_name);
797         err = DB_DATA_MISMATCH;
798       }
799 
800       cinfo[CONTAINER_VALUE].field_id = col_id;
801       cinfo[CONTAINER_VALUE].col_meta = *col_meta;
802       err = DB_SUCCESS;
803     }
804   } else {
805     int i;
806 
807     for (i = 0; i < meta_info->n_extra_col; i++) {
808       if (strcmp(name, meta_info->extra_col_info[i].col_name) == 0) {
809         if (col_meta->type != IB_VARCHAR && col_meta->type != IB_CHAR &&
810             col_meta->type != IB_BLOB && col_meta->type != IB_CHAR_ANYCHARSET &&
811             col_meta->type != IB_VARCHAR_ANYCHARSET &&
812             col_meta->type != IB_INT) {
813           fprintf(stderr,
814                   " InnoDB_Memcached: the value"
815                   " column %s in table %s"
816                   " should be INTEGER, CHAR or"
817                   " VARCHAR.\n",
818                   name, table_name);
819           err = DB_DATA_MISMATCH;
820           break;
821         }
822 
823         meta_info->extra_col_info[i].field_id = col_id;
824         meta_info->extra_col_info[i].col_meta = *col_meta;
825 
826         meta_info->col_info[CONTAINER_VALUE].field_id = col_id;
827         meta_info->col_info[CONTAINER_VALUE].col_meta = *col_meta;
828 
829         if (col_verify) {
830           col_verify[i].field_id = col_id;
831         }
832 
833         err = DB_SUCCESS;
834       }
835     }
836   }
837 
838   return (err);
839 }
840 
841 /**********************************************************************/ /**
842  This function verifies the table configuration information on an opened
843  table, and fills in columns used for memcached functionalities (cas, exp etc.)
844  @return true if everything works out fine */
innodb_verify_low(meta_cfg_info_t * info,ib_crsr_t crsr,bool runtime)845 ib_err_t innodb_verify_low(
846     /*==============*/
847     meta_cfg_info_t *info, /*!< in/out: meta info structure */
848     ib_crsr_t crsr,        /*!< in: crsr */
849     bool runtime)          /*!< in: verify at the runtime */
850 {
851   ib_crsr_t idx_crsr = NULL;
852   ib_tpl_t tpl = NULL;
853   ib_col_meta_t col_meta;
854   int n_cols;
855   int i;
856   bool is_key_col = false;
857   bool is_value_col = false;
858   bool is_flag_col = false;
859   bool is_cas_col = false;
860   bool is_exp_col = false;
861   int index_type;
862   ib_id_u64_t index_id;
863   ib_err_t err = DB_SUCCESS;
864   const char *name;
865   meta_column_t *cinfo = info->col_info;
866   meta_column_t *col_verify = NULL;
867   char table_name[MAX_TABLE_NAME_LEN + MAX_DATABASE_NAME_LEN];
868   char *dbname;
869   char *tname;
870 
871   tpl = innodb_cb_read_tuple_create(crsr);
872 
873   if (runtime && info->n_extra_col) {
874     col_verify =
875         (meta_column_t *)malloc(info->n_extra_col * sizeof(meta_column_t));
876 
877     if (!col_verify) {
878       return (DB_ERROR);
879     }
880 
881     for (i = 0; i < info->n_extra_col; i++) {
882       col_verify[i].field_id = -1;
883     }
884   }
885 
886   /* Get table name. */
887   dbname = info->col_info[CONTAINER_DB].col_name;
888   tname = info->col_info[CONTAINER_TABLE].col_name;
889 #ifdef __WIN__
890   sprintf(table_name, "%s\%s", dbname, tname);
891 #else
892   snprintf(table_name, sizeof(table_name), "%s/%s", dbname, tname);
893 #endif
894 
895   n_cols = innodb_cb_tuple_get_n_cols(tpl);
896 
897   /* Verify each mapped column */
898   for (i = 0; i < n_cols; i++) {
899     ib_err_t result = DB_SUCCESS;
900 
901     name = innodb_cb_col_get_name(crsr, i);
902     innodb_cb_col_get_meta(tpl, i, &col_meta);
903 
904     result =
905         innodb_config_value_col_verify(name, info, &col_meta, i, col_verify);
906 
907     if (result == DB_SUCCESS) {
908       is_value_col = true;
909 
910       if (strcmp(name, cinfo[CONTAINER_KEY].col_name)) {
911         continue;
912       }
913     } else if (result == DB_DATA_MISMATCH) {
914       err = DB_DATA_MISMATCH;
915       goto func_exit;
916     }
917 
918     if (strcmp(name, cinfo[CONTAINER_KEY].col_name) == 0) {
919       /* Key column must be CHAR or VARCHAR type */
920       if (col_meta.type != IB_VARCHAR && col_meta.type != IB_CHAR &&
921           col_meta.type != IB_VARCHAR_ANYCHARSET &&
922           col_meta.type != IB_CHAR_ANYCHARSET && col_meta.type != IB_INT) {
923         fprintf(stderr,
924                 " InnoDB_Memcached: the key"
925                 " column %s in table %s should"
926                 " be INTEGER, CHAR or VARCHAR.\n",
927                 name, table_name);
928         err = DB_DATA_MISMATCH;
929         goto func_exit;
930       }
931       cinfo[CONTAINER_KEY].field_id = i;
932       cinfo[CONTAINER_KEY].col_meta = col_meta;
933       is_key_col = true;
934     } else if (strcmp(name, cinfo[CONTAINER_FLAG].col_name) == 0) {
935       /* Flag column must be integer type */
936       if (col_meta.type != IB_INT) {
937         fprintf(stderr,
938                 " InnoDB_Memcached: the flag"
939                 " column %s in table %s should"
940                 " be INTEGER.\n",
941                 name, table_name);
942         err = DB_DATA_MISMATCH;
943         goto func_exit;
944       }
945       cinfo[CONTAINER_FLAG].field_id = i;
946       cinfo[CONTAINER_FLAG].col_meta = col_meta;
947       info->flag_enabled = true;
948       is_flag_col = true;
949     } else if (strcmp(name, cinfo[CONTAINER_CAS].col_name) == 0) {
950       /* CAS column must be integer type */
951       if (col_meta.type != IB_INT) {
952         fprintf(stderr,
953                 " InnoDB_Memcached: the cas"
954                 " column %s in table %s should"
955                 " be INTEGER.\n",
956                 name, table_name);
957         err = DB_DATA_MISMATCH;
958         goto func_exit;
959       }
960       cinfo[CONTAINER_CAS].field_id = i;
961       cinfo[CONTAINER_CAS].col_meta = col_meta;
962       info->cas_enabled = true;
963       is_cas_col = true;
964     } else if (strcmp(name, cinfo[CONTAINER_EXP].col_name) == 0) {
965       /* EXP column must be integer type */
966       if (col_meta.type != IB_INT) {
967         fprintf(stderr,
968                 " InnoDB_Memcached: the expire"
969                 " column %s in table %s should"
970                 " be INTEGER.\n",
971                 name, table_name);
972         err = DB_DATA_MISMATCH;
973         goto func_exit;
974       }
975       cinfo[CONTAINER_EXP].field_id = i;
976       cinfo[CONTAINER_EXP].col_meta = col_meta;
977       info->exp_enabled = true;
978       is_exp_col = true;
979     }
980   }
981 
982   /* Key column and Value column must present */
983   if (!is_key_col || !is_value_col) {
984     fprintf(stderr,
985             " InnoDB_Memcached: failed to locate key"
986             " column or value column in table"
987             " as specified by config table \n");
988 
989     err = DB_ERROR;
990     goto func_exit;
991   }
992 
993   if (info->n_extra_col) {
994     meta_column_t *col_check;
995 
996     col_check = (runtime && col_verify) ? col_verify : info->extra_col_info;
997 
998     for (i = 0; i < info->n_extra_col; i++) {
999       if (col_check[i].field_id < 0) {
1000         fprintf(stderr,
1001                 " InnoDB_Memcached: failed to"
1002                 " locate value column %s"
1003                 " as specified by config"
1004                 " table \n",
1005                 info->extra_col_info[i].col_name);
1006         err = DB_ERROR;
1007         goto func_exit;
1008       }
1009     }
1010   }
1011 
1012   if (info->flag_enabled && !is_flag_col) {
1013     fprintf(stderr,
1014             " InnoDB_Memcached: failed to locate flag"
1015             " column as specified by config table \n");
1016     err = DB_ERROR;
1017     goto func_exit;
1018   }
1019 
1020   if (info->cas_enabled && !is_cas_col) {
1021     fprintf(stderr,
1022             " InnoDB_Memcached: failed to locate cas"
1023             " column as specified by config table \n");
1024     err = DB_ERROR;
1025     goto func_exit;
1026   }
1027 
1028   if (info->exp_enabled && !is_exp_col) {
1029     fprintf(stderr,
1030             " InnoDB_Memcached: failed to locate exp"
1031             " column as specified by config table \n");
1032     err = DB_ERROR;
1033     goto func_exit;
1034   }
1035 
1036   /* Test the specified index */
1037   innodb_cb_cursor_open_index_using_name(crsr, info->index_info.idx_name,
1038                                          &idx_crsr, &index_type, &index_id);
1039 
1040   if (index_type & IB_CLUSTERED) {
1041     info->index_info.srch_use_idx = META_USE_CLUSTER;
1042   } else if (!idx_crsr || !(index_type & IB_UNIQUE)) {
1043     fprintf(stderr,
1044             " InnoDB_Memcached: Index on key column"
1045             " must be a Unique index\n");
1046     info->index_info.srch_use_idx = META_USE_NO_INDEX;
1047     err = DB_ERROR;
1048   } else {
1049     info->index_info.idx_id = index_id;
1050     info->index_info.srch_use_idx = META_USE_SECONDARY;
1051   }
1052 
1053   if (idx_crsr) {
1054     /* We use the same function even if index_type & IB_CLUSTERED,
1055     because the variant for _clust_ is not exposed via API, and it turns
1056     out that we don't really need it, as long as the clustered index
1057     has one column. */
1058     ib_tpl_t idx_tpl = ib_cb_sec_search_tuple_create(idx_crsr);
1059 
1060     n_cols = ib_cb_tuple_get_n_user_cols(idx_tpl);
1061 
1062     name = ib_cb_get_idx_field_name(idx_crsr, 0);
1063 
1064     if (n_cols > 1) {
1065       fprintf(stderr,
1066               " InnoDB_Memcached: The unique_idx_name_on_key (%s)"
1067               " must be on key column (%s) only but it is on %d columns\n",
1068               info->index_info.idx_name, cinfo[CONTAINER_KEY].col_name, n_cols);
1069       info->index_info.srch_use_idx = META_USE_NO_INDEX;
1070       err = DB_ERROR;
1071     }
1072     if (strcmp(name, cinfo[CONTAINER_KEY].col_name)) {
1073       fprintf(stderr,
1074               " InnoDB_Memcached: The unique_idx_name_on_key (%s)"
1075               " must be on key column (%s) but it is on (%s)\n",
1076               info->index_info.idx_name, cinfo[CONTAINER_KEY].col_name, name);
1077       info->index_info.srch_use_idx = META_USE_NO_INDEX;
1078       err = DB_ERROR;
1079     }
1080 
1081     innodb_cb_tuple_delete(idx_tpl);
1082     innodb_cb_cursor_close(idx_crsr);
1083   }
1084 func_exit:
1085 
1086   if (runtime && col_verify) {
1087     free(col_verify);
1088   }
1089 
1090   if (tpl) {
1091     innodb_cb_tuple_delete(tpl);
1092   }
1093 
1094   return (err);
1095 }
1096 
1097 /** This function verifies the table configuration information, and fills
1098 in columns used for memcached functionalities (cas, exp etc.)
1099 @param[in]	info	meta info structure
1100 @param[in,out]	thd	MySQL THD
1101 @return true if everything works out fine */
innodb_verify(meta_cfg_info_t * info,void * thd)1102 bool innodb_verify(
1103     /*==========*/
1104     meta_cfg_info_t *info, void *thd) {
1105   ib_crsr_t crsr = NULL;
1106   char table_name[MAX_TABLE_NAME_LEN + MAX_DATABASE_NAME_LEN];
1107   char *dbname;
1108   char *name;
1109   ib_err_t err = DB_SUCCESS;
1110   ib_trx_t ib_trx = ib_cb_trx_begin(IB_TRX_READ_COMMITTED, false, false, thd);
1111 
1112   dbname = info->col_info[CONTAINER_DB].col_name;
1113   name = info->col_info[CONTAINER_TABLE].col_name;
1114   info->flag_enabled = false;
1115   info->cas_enabled = false;
1116   info->exp_enabled = false;
1117 
1118 #ifdef _WIN32
1119   sprintf(table_name, "%s\%s", dbname, name);
1120 #else
1121   snprintf(table_name, sizeof(table_name), "%s/%s", dbname, name);
1122 #endif
1123 
1124   err = innodb_cb_open_table(table_name, ib_trx, &crsr);
1125 
1126   /* Mapped InnoDB table must be able to open */
1127   if (err != DB_SUCCESS) {
1128     fprintf(stderr,
1129             " InnoDB_Memcached: failed to open table"
1130             " '%s' \n",
1131             table_name);
1132     err = DB_ERROR;
1133     goto func_exit;
1134   }
1135 
1136   if (ib_cb_is_virtual_table(crsr)) {
1137     fprintf(stderr,
1138             " InnoDB_Memcached: table '%s' cannot"
1139             " be mapped since it contains virtual"
1140             " columns. \n",
1141             table_name);
1142     err = DB_ERROR;
1143     goto func_exit;
1144   }
1145 
1146   err = innodb_verify_low(info, crsr, false);
1147 func_exit:
1148   innodb_cb_cursor_close(crsr);
1149 
1150   innodb_cb_trx_commit(ib_trx);
1151   ib_cb_trx_release(ib_trx);
1152 
1153   return (err == DB_SUCCESS);
1154 }
1155 
1156 /**********************************************************************/ /**
1157  If the hash table (meta_hash) is NULL, then initialise the hash table with
1158  data in the configure tables. And return the "default" item.  If there is
1159  no setting named "default" then use the first row in the table. This is
1160  currently only used at the engine initialization time.
1161  If the hash table (meta_hash) is created, then look for the meta-data based on
1162  specified configuration name parameter. If such metadata does not exist in
1163  the hash table, then add such metadata into hash table.
1164  @return meta_cfg_info_t* structure if configure option found, otherwise NULL */
innodb_config(const char * name,size_t name_len,hash_table_t ** meta_hash)1165 meta_cfg_info_t *innodb_config(
1166     /*==========*/
1167     const char *name,         /*!< in: config option name */
1168     size_t name_len,          /*!< in: name length */
1169     hash_table_t **meta_hash) /*!< in/out: engine hash
1170                               table. If NULL, it will be
1171                               created and initialized */
1172 {
1173   meta_cfg_info_t *item;
1174   bool success;
1175   void *thd = handler_create_thd(false);
1176 
1177   if (*meta_hash == NULL) {
1178     *meta_hash = hash_create(100);
1179   }
1180 
1181   if (!name) {
1182     item = innodb_config_meta_hash_init(*meta_hash, thd);
1183   } else {
1184     ib_ulint_t fold;
1185 
1186     fold = ut_fold_string(name);
1187     HASH_SEARCH(name_hash, *meta_hash, fold, meta_cfg_info_t *, item,
1188                 (name_len == item->col_info[0].col_name_len &&
1189                  strcmp(name, item->col_info[0].col_name) == 0));
1190 
1191     if (item) {
1192       handler_close_thd(thd);
1193       return (item);
1194     }
1195 
1196     item = innodb_config_container(name, name_len, *meta_hash, thd);
1197   }
1198 
1199   if (!item) {
1200     handler_close_thd(thd);
1201     return (NULL);
1202   }
1203 
1204   /* Following two configure operations are optional, and can be
1205   failed */
1206   success = innodb_read_cache_policy(item, thd);
1207 
1208   if (!success) {
1209     handler_close_thd(thd);
1210     return (NULL);
1211   }
1212 
1213   success = innodb_read_config_option(item, thd);
1214 
1215   handler_close_thd(thd);
1216 
1217   if (!success) {
1218     return (NULL);
1219   }
1220 
1221   return (item);
1222 }
1223