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 Engine code
30 
31  Extracted and modified from NDB memcached project
32  04/12/2011 Jimmy Yang
33  *******************************************************/
34 
35 // Work around a bug in the memcached C++ headers with GCC 4.x.
36 #ifndef bool
37 #define bool bool
38 #endif
39 
40 #include <arpa/inet.h>
41 #include <assert.h>
42 #include <memcached/config_parser.h>
43 #include <memcached/util.h>
44 #include <pthread.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <unistd.h>
48 #include "default_engine.h"
49 
50 #include "hash_item_util.h"
51 #include "innodb_api.h"
52 #include "innodb_cb_api.h"
53 #include "innodb_engine.h"
54 #include "innodb_engine_private.h"
55 #include "my_compiler.h"
56 #include "my_thread.h"
57 
58 /** Define also present in daemon/memcached.h */
59 #define KEY_MAX_LENGTH 250
60 
61 #ifdef UNIV_MEMCACHED_SDI
62 static const char SDI_PREFIX[] = "sdi_";
63 static const char SDI_CREATE_PREFIX[] = "sdi_create_";
64 static const char SDI_DROP_PREFIX[] = "sdi_drop_";
65 static const char SDI_LIST_PREFIX[] = "sdi_list_";
66 #endif /* UNIV_MEMCACHED_SDI */
67 
68 /** Tells whether memcached plugin is being shutdown */
69 static bool memcached_shutdown = false;
70 
71 /** Tells whether the background thread is exited */
72 static bool bk_thd_exited = true;
73 
74 /** The SDI buffer length for storing list of SDI keys. Example output
75 looks like "1:2|2:2|3:4|..". So SDI list of key retrieval has this limit of
76 characters from memcached plugin. This is sufficent for testing. */
77 const uint32_t SDI_LIST_BUF_MAX_LEN MY_ATTRIBUTE((unused)) = 10000;
78 
79 /** Tells whether all connections need to release MDL locks */
80 bool release_mdl_lock = false;
81 
82 /** InnoDB Memcached engine configuration info */
83 typedef struct eng_config_info {
84   char *option_string;               /*!< memcached config option
85                                      string */
86   void *cb_ptr;                      /*!< call back function ptr */
87   unsigned int eng_read_batch_size;  /*!< read batch size */
88   unsigned int eng_write_batch_size; /*!< write batch size */
89   bool eng_enable_binlog;            /*!< whether binlog is
90                                      enabled specifically for
91                                      this memcached engine */
92 } eng_config_info_t;
93 
94 extern option_t config_option_names[];
95 
96 /** Check the input key name implies a table mapping switch. The name
97 would start with "@@", and in the format of "@@new_table_mapping.key"
98 or simply "@@new_table_mapping" */
99 
100 /**********************************************************************/ /**
101  Unlock a table and commit the transaction
102  return 0 if fail to commit the transaction */
103 extern int handler_unlock_table(
104     /*=================*/
105     void *my_thd,      /*!< in: thread */
106     void *my_table,    /*!< in: Table metadata */
107     int my_lock_mode); /*!< in: lock mode */
108 
109 /*******************************************************************/ /**
110  Get InnoDB Memcached engine handle
111  @return InnoDB Memcached engine handle */
innodb_handle(ENGINE_HANDLE * handle)112 static inline struct innodb_engine *innodb_handle(
113     /*==========*/
114     ENGINE_HANDLE *handle) /*!< in: Generic engine handle */
115 {
116   return ((struct innodb_engine *)handle);
117 }
118 
119 /*******************************************************************/ /**
120  Cleanup idle connections if "clear_all" is false, and clean up all
121  connections if "clear_all" is true. */
122 static void innodb_conn_clean_data(
123     /*===================*/
124     innodb_conn_data_t *conn_data, bool has_lock, bool free_all);
125 
126 /*******************************************************************/ /**
127  Destroy and Free InnoDB Memcached engine */
128 static void innodb_destroy(
129     /*===========*/
130     ENGINE_HANDLE *handle, /*!< in: Destroy the engine instance */
131     bool force);           /*!< in: Force to destroy */
132 
133 /*******************************************************************/ /**
134  Support memcached "INCR" and "DECR" command, add or subtract a "delta"
135  value from an integer key value
136  @return ENGINE_SUCCESS if successfully, otherwise error code */
137 static ENGINE_ERROR_CODE innodb_arithmetic(
138     /*==============*/
139     ENGINE_HANDLE *handle,    /*!< in: Engine Handle */
140     const void *cookie,       /*!< in: connection cookie */
141     const void *key,          /*!< in: key for the value to add */
142     const int nkey,           /*!< in: key length */
143     const bool increment,     /*!< in: whether to increment
144                               or decrement */
145     const bool create,        /*!< in: whether to create the key
146                               value pair if can't find */
147     const uint64_t delta,     /*!< in: value to add/substract */
148     const uint64_t initial,   /*!< in: initial */
149     const rel_time_t exptime, /*!< in: expiration time */
150     uint64_t *cas,            /*!< out: new cas value */
151     uint64_t *result,         /*!< out: result out */
152     uint16_t vbucket);        /*!< in: bucket, used by default
153                               engine only */
154 
155 /**
156  Callback function used by Memcached's process_command() function
157  to get the result key/value information
158 @param[in]   handle     Engine Handle
159 @param[in]   cookie     connection cookie
160 @param[in]   item       item in question
161 @param[out]  item_info  item info got
162 @return true if info fetched */
163 static bool innodb_get_item_info(ENGINE_HANDLE *handle, const void *cookie,
164                                  const item *item, item_info *item_info);
165 
166 /*******************************************************************/ /**
167  Get default Memcached engine handle
168  @return default Memcached engine handle */
default_handle(struct innodb_engine * eng)169 static inline struct default_engine *default_handle(
170     /*===========*/
171     struct innodb_engine *eng) {
172   return ((struct default_engine *)eng->default_engine);
173 }
174 
175 #ifdef UNIV_MEMCACHED_SDI
176 /** Remove SDI entry from tablespace
177 @param[in,out]	innodb_eng	innodb engine structure
178 @param[in,out]	conn_data	innodb connection data
179 @param[in,out]	err_ret		error code
180 @param[in]	key		memcached key
181 @param[in]	nkey		memcached key length
182 @return true if key is SDI key else false */
183 static bool innodb_sdi_remove(struct innodb_engine *innodb_eng,
184                               innodb_conn_data_t *conn_data,
185                               ENGINE_ERROR_CODE *err_ret, const void *key,
186                               const size_t nkey);
187 
188 /** Retrieve SDI for a given SDI key from tablespace
189 @param[in,out]	conn_data	innodb connection data
190 @param[in,out]	err_ret		error code
191 @param[in]	key		memcached key
192 @param[in]	nkey		memcached key length
193 @param[in,out]	item		memcached item to fill
194 @return true if key is SDI key else false */
195 static bool innodb_sdi_get(innodb_conn_data_t *conn_data,
196                            ENGINE_ERROR_CODE *err_ret, const void *key,
197                            const size_t nkey, item ***item);
198 
199 /** Store SDI entry into a tablespace
200 @param[in,out]	innodb_eng	innodb engine structure
201 @param[in,out]	conn_data	innodb connection data
202 @param[in,out]	err_ret		error code
203 @param[in]	value		memcached value
204 @param[in]	val_len		memcached value length
205 @param[in]	nkey		memcached key length
206 @return true if key is SDI key else false */
207 static bool innodb_sdi_store(struct innodb_engine *innodb_eng,
208                              innodb_conn_data_t *conn_data,
209                              ENGINE_ERROR_CODE *err_ret, char *value,
210                              uint32_t val_len, const size_t nkey);
211 #endif /* UNIV_MEMCACHED_SDI */
212 
213 /****** Gateway to the default_engine's create_instance() function */
214 extern "C" ENGINE_ERROR_CODE create_my_default_instance(
215     /*=======================*/
216     uint64_t, GET_SERVER_API, ENGINE_HANDLE **);
217 
218 /*********** FUNCTIONS IMPLEMENTING THE PUBLISHED API BEGIN HERE ********/
219 
220 /*******************************************************************/ /**
221  Create InnoDB Memcached Engine.
222  @return ENGINE_SUCCESS if successful, otherwise, error code */
223 ENGINE_ERROR_CODE
create_instance(uint64_t interface,GET_SERVER_API get_server_api,ENGINE_HANDLE ** handle)224 create_instance(
225     /*============*/
226     uint64_t interface,            /*!< in: protocol version,
227                                    currently always 1 */
228     GET_SERVER_API get_server_api, /*!< in: Callback the engines
229                                    may call to get the public
230                                    server interface */
231     ENGINE_HANDLE **handle)        /*!< out: Engine handle */
232 {
233   ENGINE_ERROR_CODE err_ret;
234   struct innodb_engine *innodb_eng;
235 
236   SERVER_HANDLE_V1 *api = get_server_api();
237 
238   if (interface != 1 || api == NULL) {
239     return (ENGINE_ENOTSUP);
240   }
241 
242   innodb_eng = (innodb_engine *)malloc(sizeof(struct innodb_engine));
243 
244   if (innodb_eng == NULL) {
245     return (ENGINE_ENOMEM);
246   }
247 
248   memset(innodb_eng, 0, sizeof(*innodb_eng));
249   innodb_eng->engine.interface.interface = 1;
250   innodb_eng->engine.get_info = innodb_get_info;
251   innodb_eng->engine.initialize = innodb_initialize;
252   innodb_eng->engine.destroy = innodb_destroy;
253   innodb_eng->engine.allocate = innodb_allocate;
254   innodb_eng->engine.remove = innodb_remove;
255   innodb_eng->engine.release = innodb_release;
256   innodb_eng->engine.clean_engine = innodb_clean_engine;
257   innodb_eng->engine.get = innodb_get;
258   innodb_eng->engine.get_stats = innodb_get_stats;
259   innodb_eng->engine.reset_stats = innodb_reset_stats;
260   innodb_eng->engine.store = innodb_store;
261   innodb_eng->engine.arithmetic = innodb_arithmetic;
262   innodb_eng->engine.flush = innodb_flush;
263   innodb_eng->engine.unknown_command = innodb_unknown_command;
264   innodb_eng->engine.item_set_cas = item_set_cas;
265   innodb_eng->engine.get_item_info = innodb_get_item_info;
266   innodb_eng->engine.get_stats_struct = NULL;
267   innodb_eng->engine.errinfo = NULL;
268   innodb_eng->engine.bind = innodb_bind;
269 
270   innodb_eng->server = *api;
271   innodb_eng->get_server_api = get_server_api;
272 
273   /* configuration, with default values*/
274   innodb_eng->info.info.description = "InnoDB Memcache " VERSION;
275   innodb_eng->info.info.num_features = 3;
276   innodb_eng->info.info.features[0].feature = ENGINE_FEATURE_CAS;
277   innodb_eng->info.info.features[1].feature = ENGINE_FEATURE_PERSISTENT_STORAGE;
278   innodb_eng->info.info.features[2].feature = ENGINE_FEATURE_LRU;
279 
280   /* Now call create_instace() for the default engine */
281   err_ret = create_my_default_instance(interface, get_server_api,
282                                        &(innodb_eng->default_engine));
283 
284   if (err_ret != ENGINE_SUCCESS) {
285     free(innodb_eng);
286     return (err_ret);
287   }
288 
289   innodb_eng->clean_stale_conn = false;
290   innodb_eng->initialized = true;
291 
292   *handle = (ENGINE_HANDLE *)&innodb_eng->engine;
293 
294   return (ENGINE_SUCCESS);
295 }
innodb_close_cursors(innodb_conn_data_t * conn_data)296 static void innodb_close_cursors(innodb_conn_data_t *conn_data) {
297   innodb_cb_cursor_close(conn_data->idx_read_crsr);
298   innodb_cb_cursor_close(conn_data->idx_crsr);
299   innodb_cb_cursor_close(conn_data->read_crsr);
300   innodb_cb_cursor_close(conn_data->crsr);
301 }
innodb_commit_and_release_crsr_trx(innodb_conn_data_t * conn_data)302 static void innodb_commit_and_release_crsr_trx(innodb_conn_data_t *conn_data) {
303   assert(!conn_data->mysql_tbl);
304   innodb_close_cursors(conn_data);
305   innodb_cb_trx_commit(conn_data->crsr_trx);
306   auto err MY_ATTRIBUTE((unused)) = ib_cb_trx_release(conn_data->crsr_trx);
307   assert(err == DB_SUCCESS);
308   conn_data->crsr_trx = nullptr;
309 }
310 
311 void innodb_close_mysql_table(innodb_conn_data_t *conn_data);
312 
313 /*******************************************************************/ /**
314  background thread to commit trx.
315  @return dummy parameter */
innodb_bk_thread(void * arg)316 static void *innodb_bk_thread(
317     /*=============*/
318     void *arg) {
319   ENGINE_HANDLE *handle;
320   struct innodb_engine *innodb_eng;
321 
322   bk_thd_exited = false;
323 
324   handle = (ENGINE_HANDLE *)(arg);
325   innodb_eng = innodb_handle(handle);
326 
327   my_thread_init();
328 
329   /* While we commit transactions on behalf of the other
330   threads, we will "pretend" to be each connection. */
331   void *thd = handler_create_thd(innodb_eng->enable_binlog);
332 
333   while (!memcached_shutdown) {
334     release_mdl_lock = handler_check_global_read_lock_active();
335 
336     /* Do the cleanup every innodb_eng->bk_commit_interval
337     seconds. We also check if the plugin is being shutdown
338     every second */
339     for (uint i = 0; i < innodb_eng->bk_commit_interval; i++) {
340       sleep(1);
341 
342       /* If memcached is being shutdown, break */
343       if (memcached_shutdown) {
344         break;
345       }
346     }
347 
348     LOCK_CONN_IF_NOT_LOCKED(false, innodb_eng);
349 
350     /* Set the clean_stale_conn to prevent force clean in
351     innodb_conn_clean. */
352     innodb_eng->clean_stale_conn = true;
353     innodb_conn_data_t *next_conn_data = nullptr;
354 
355     for (innodb_conn_data_t *conn_data =
356              UT_LIST_GET_FIRST(innodb_eng->conn_data);
357          conn_data; conn_data = next_conn_data) {
358       next_conn_data = UT_LIST_GET_NEXT(conn_list, conn_data);
359       if (conn_data->is_waiting_for_mdl) {
360         continue;
361       }
362 
363       if (LOCK_CURRENT_CONN_TRYLOCK(conn_data) != 0) {
364         continue;
365       }
366 
367       if (conn_data->is_stale) {
368         assert(!conn_data->in_use);
369         UT_LIST_REMOVE(conn_list, innodb_eng->conn_data, conn_data);
370         innodb_conn_clean_data(conn_data, true, true);
371       } else if (!conn_data->in_use) {
372         if (conn_data->thd) {
373           handler_thd_attach(conn_data->thd, NULL);
374         }
375         innodb_reset_conn(conn_data, true, true, innodb_eng->enable_binlog);
376         innodb_close_mysql_table(conn_data);
377         innodb_conn_clean_data(conn_data, true, false);
378       }
379 
380       UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(false, conn_data);
381     }
382 
383     /* Set the clean_stale_conn back. */
384     innodb_eng->clean_stale_conn = false;
385     UNLOCK_CONN_IF_NOT_LOCKED(false, innodb_eng);
386   }
387 
388   bk_thd_exited = true;
389 
390   /* Change to its original state before close the MySQL THD */
391   handler_thd_attach(thd, NULL);
392   handler_close_thd(thd);
393 
394   my_thread_end();
395   pthread_detach(pthread_self());
396   pthread_exit(NULL);
397 
398   return ((void *)0);
399 }
400 
401 /*******************************************************************/ /**
402  Get engine info.
403  @return engine info */
innodb_get_info(ENGINE_HANDLE * handle)404 static const engine_info *innodb_get_info(
405     /*============*/
406     ENGINE_HANDLE *handle) /*!< in: Engine handle */
407 {
408   return (&innodb_handle(handle)->info.info);
409 }
410 
411 /*******************************************************************/ /**
412  Initialize InnoDB Memcached Engine.
413  @return ENGINE_SUCCESS if successful */
innodb_initialize(ENGINE_HANDLE * handle,const char * config_str)414 static ENGINE_ERROR_CODE innodb_initialize(
415     /*==============*/
416     ENGINE_HANDLE *handle,  /*!< in/out: InnoDB memcached
417                             engine */
418     const char *config_str) /*!< in: configure string */
419 {
420   /* In case you want to make sure that your MTR is robust in case
421   of longer plugin initialization, consider adding
422      sleep(10);
423   here - I've found lots of errors this way, hopefuly all are fixed.*/
424 
425   ENGINE_ERROR_CODE return_status = ENGINE_SUCCESS;
426   struct innodb_engine *innodb_eng = innodb_handle(handle);
427   struct default_engine *def_eng = default_handle(innodb_eng);
428   eng_config_info_t *my_eng_config;
429   pthread_attr_t attr;
430 
431   my_eng_config = (eng_config_info_t *)config_str;
432 
433   /* If no call back function registered (InnoDB engine failed to load),
434   load InnoDB Memcached engine should fail too */
435   if (!my_eng_config->cb_ptr) {
436     return (ENGINE_TMPFAIL);
437   }
438 
439   /* Register the call back function */
440   register_innodb_cb((void *)my_eng_config->cb_ptr);
441 
442   innodb_eng->read_batch_size =
443       (my_eng_config->eng_read_batch_size ? my_eng_config->eng_read_batch_size
444                                           : CONN_NUM_READ_COMMIT);
445 
446   innodb_eng->write_batch_size =
447       (my_eng_config->eng_write_batch_size ? my_eng_config->eng_write_batch_size
448                                            : CONN_NUM_WRITE_COMMIT);
449 
450   innodb_eng->enable_binlog = my_eng_config->eng_enable_binlog;
451 
452   innodb_eng->cfg_status = innodb_cb_get_cfg();
453 
454   /* If binlog is not enabled by InnoDB memcached plugin, let's
455   check whether innodb_direct_access_enable_binlog is turned on */
456   if (!innodb_eng->enable_binlog) {
457     innodb_eng->enable_binlog = innodb_eng->cfg_status & IB_CFG_BINLOG_ENABLED;
458   }
459 
460   innodb_eng->enable_mdl = innodb_eng->cfg_status & IB_CFG_MDL_ENABLED;
461   innodb_eng->trx_level = ib_cb_cfg_trx_level();
462   innodb_eng->bk_commit_interval = ib_cb_cfg_bk_commit_interval();
463 
464   UT_LIST_INIT(innodb_eng->conn_data);
465   pthread_mutex_init(&innodb_eng->conn_mutex, NULL);
466   pthread_mutex_init(&innodb_eng->cas_mutex, NULL);
467 
468   /* Fetch InnoDB specific settings */
469   innodb_eng->meta_info = innodb_config(NULL, 0, &innodb_eng->meta_hash);
470 
471   if (!innodb_eng->meta_info) {
472     return (ENGINE_TMPFAIL);
473   }
474 
475   if (innodb_eng->default_engine) {
476     return_status = def_eng->engine.initialize(innodb_eng->default_engine,
477                                                my_eng_config->option_string);
478   }
479 
480   memcached_shutdown = false;
481   pthread_attr_init(&attr);
482   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
483   pthread_create(&innodb_eng->bk_thd_for_commit, &attr, innodb_bk_thread,
484                  handle);
485 
486   return (return_status);
487 }
488 
489 extern void handler_close_thd(void *);
490 
491 /*******************************************************************/ /**
492  Close table using handler functions.
493  @param conn_data	cursor information of connection */
innodb_close_mysql_table(innodb_conn_data_t * conn_data)494 void innodb_close_mysql_table(
495     /*=====================*/
496     innodb_conn_data_t *conn_data) /*!< in: connection
497                                             cursor*/
498 {
499   if (conn_data->mysql_tbl) {
500     assert(conn_data->thd);
501     handler_unlock_table(conn_data->thd, conn_data->mysql_tbl, HDL_READ);
502     conn_data->mysql_tbl = NULL;
503   }
504 }
505 
506 #define NUM_MAX_MEM_SLOT 1024
507 
508 /*******************************************************************/ /**
509  Cleanup idle connections if "clear_all" is false, and clean up all
510  connections if "clear_all" is true. */
innodb_conn_clean_data(innodb_conn_data_t * conn_data,bool has_lock,bool free_all)511 static void innodb_conn_clean_data(
512     /*===================*/
513     innodb_conn_data_t *conn_data, bool has_lock, bool free_all) {
514   mem_buf_t *mem_buf;
515 
516   if (!conn_data) {
517     return;
518   }
519 
520   LOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
521 
522   innodb_close_cursors(conn_data);
523 
524   if (conn_data->crsr_trx) {
525     ib_err_t err MY_ATTRIBUTE((unused));
526     innodb_cb_trx_commit(conn_data->crsr_trx);
527     err = ib_cb_trx_release(conn_data->crsr_trx);
528     assert(err == DB_SUCCESS);
529     conn_data->crsr_trx = NULL;
530   }
531 
532   innodb_close_mysql_table(conn_data);
533 
534   if (conn_data->tpl) {
535     ib_cb_tuple_delete(conn_data->tpl);
536     conn_data->tpl = NULL;
537   }
538 
539   if (conn_data->idx_tpl) {
540     ib_cb_tuple_delete(conn_data->idx_tpl);
541     conn_data->idx_tpl = NULL;
542   }
543 
544   if (conn_data->read_tpl) {
545     ib_cb_tuple_delete(conn_data->read_tpl);
546     conn_data->read_tpl = NULL;
547   }
548 
549   if (conn_data->sel_tpl) {
550     ib_cb_tuple_delete(conn_data->sel_tpl);
551     conn_data->sel_tpl = NULL;
552   }
553 
554   UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
555 
556   if (free_all) {
557     if (conn_data->thd) {
558       handler_close_thd(conn_data->thd);
559       conn_data->thd = NULL;
560     }
561 
562     conn_data->is_stale = false;
563 
564     if (conn_data->result) {
565       free(conn_data->result);
566       conn_data->result = NULL;
567     }
568 
569     if (conn_data->row_buf) {
570       for (int i = 0; i < NUM_MAX_MEM_SLOT; i++) {
571         if (conn_data->row_buf[i]) {
572           free(conn_data->row_buf[i]);
573           conn_data->row_buf[i] = NULL;
574         }
575       }
576 
577       free(conn_data->row_buf);
578       conn_data->row_buf = NULL;
579       conn_data->row_buf_slot = 0;
580     }
581 #ifdef UNIV_MEMCACHED_SDI
582     free(conn_data->sdi_buf);
583     conn_data->sdi_buf = NULL;
584 #endif /* UNIV_MEMCACHED_SDI */
585 
586     if (conn_data->cmd_buf) {
587       free(conn_data->cmd_buf);
588       conn_data->cmd_buf = NULL;
589       conn_data->cmd_buf_len = 0;
590     }
591 
592     if (conn_data->mul_col_buf) {
593       free(conn_data->mul_col_buf);
594       conn_data->mul_col_buf = NULL;
595       conn_data->mul_col_buf_len = 0;
596     }
597 
598     mem_buf = UT_LIST_GET_FIRST(conn_data->mul_used_buf);
599 
600     while (mem_buf) {
601       UT_LIST_REMOVE(mem_list, conn_data->mul_used_buf, mem_buf);
602       free(mem_buf->mem);
603       mem_buf = UT_LIST_GET_FIRST(conn_data->mul_used_buf);
604     }
605 
606     pthread_mutex_destroy(&conn_data->curr_conn_mutex);
607     free(conn_data);
608   }
609 }
610 
611 /*******************************************************************/ /**
612  Cleanup idle connections if "clear_all" is false, and clean up all
613  connections if "clear_all" is true.
614  @return number of connection cleaned */
innodb_conn_clean(innodb_engine_t * engine,bool clear_all,bool has_lock)615 static int innodb_conn_clean(
616     /*==============*/
617     innodb_engine_t *engine, /*!< in/out: InnoDB memcached
618                              engine */
619     bool clear_all,          /*!< in: Clear all connection */
620     bool has_lock)           /*!< in: Has engine mutext */
621 {
622   innodb_conn_data_t *conn_data;
623   innodb_conn_data_t *next_conn_data;
624   int num_freed = 0;
625   void *thd = NULL;
626 
627   if (clear_all) {
628     my_thread_init();
629     thd = handler_create_thd(engine->enable_binlog);
630   }
631 
632   LOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
633 
634   conn_data = UT_LIST_GET_FIRST(engine->conn_data);
635 
636   while (conn_data) {
637     void *cookie = conn_data->conn_cookie;
638 
639     next_conn_data = UT_LIST_GET_NEXT(conn_list, conn_data);
640 
641     if (!clear_all && !conn_data->in_use) {
642       innodb_conn_data_t *check_data;
643       check_data =
644           (innodb_conn_data_t *)engine->server.cookie->get_engine_specific(
645               cookie);
646 
647       /* The check data is the original conn_data stored
648       in connection "cookie", it can be set to NULL if
649       connection closed, or to a new conn_data if it is
650       closed and reopened. So verify and see if our
651       current conn_data is stale */
652       if (!check_data || check_data != conn_data) {
653         assert(conn_data->is_stale);
654       }
655     }
656 
657     /* If current conn is stale or clear_all is true,
658     clean up it.*/
659     if (conn_data->is_stale) {
660       /* If bk thread is doing the same thing, stop
661       the loop to avoid confliction.*/
662       if (engine->clean_stale_conn) break;
663 
664       UT_LIST_REMOVE(conn_list, engine->conn_data, conn_data);
665       innodb_conn_clean_data(conn_data, false, true);
666       num_freed++;
667     } else {
668       if (clear_all) {
669         UT_LIST_REMOVE(conn_list, engine->conn_data, conn_data);
670 
671         if (thd && conn_data->thd) {
672           handler_thd_attach(conn_data->thd, NULL);
673         }
674 
675         innodb_reset_conn(conn_data, false, true, engine->enable_binlog);
676         if (conn_data->thd) {
677           handler_thd_attach(conn_data->thd, NULL);
678         }
679         innodb_conn_clean_data(conn_data, false, true);
680 
681         engine->server.cookie->store_engine_specific(cookie, NULL);
682         num_freed++;
683       }
684     }
685 
686     conn_data = next_conn_data;
687   }
688 
689   assert(!clear_all || engine->conn_data.count == 0);
690 
691   UNLOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
692 
693   if (thd) {
694     handler_thd_attach(thd, NULL);
695     handler_close_thd(thd);
696     my_thread_end();
697   }
698 
699   return (num_freed);
700 }
701 
702 /*******************************************************************/ /**
703  Destroy and Free InnoDB Memcached engine */
innodb_destroy(ENGINE_HANDLE * handle,bool force)704 static void innodb_destroy(
705     /*===========*/
706     ENGINE_HANDLE *handle, /*!< in: Destroy the engine instance */
707     bool force)            /*!< in: Force to destroy */
708 {
709   struct innodb_engine *innodb_eng = innodb_handle(handle);
710   struct default_engine *def_eng = default_handle(innodb_eng);
711 
712   memcached_shutdown = true;
713 
714   /* Wait for the background thread to exit */
715   while (!bk_thd_exited) {
716     sleep(1);
717   }
718 
719   innodb_conn_clean(innodb_eng, true, false);
720 
721   if (innodb_eng->meta_hash) {
722     HASH_CLEANUP(innodb_eng->meta_hash, meta_cfg_info_t *);
723   }
724 
725   pthread_mutex_destroy(&innodb_eng->conn_mutex);
726   pthread_mutex_destroy(&innodb_eng->cas_mutex);
727 
728   if (innodb_eng->default_engine) {
729     def_eng->engine.destroy(innodb_eng->default_engine, force);
730   }
731 
732   free(innodb_eng);
733 }
734 
735 /** Defines for connection initialization to indicate if we will
736 do a read or write operation, or in the case of CONN_MODE_NONE, just get
737 the connection's conn_data structure */
738 enum conn_mode { CONN_MODE_READ, CONN_MODE_WRITE, CONN_MODE_NONE };
739 
740 /*******************************************************************/ /**
741  Opens mysql table if enable_binlog or enable_mdl is set
742  @param conn_data	connection cursor data
743  @param conn_option	read or write operation
744  @param engine		Innodb memcached engine
745  @returns DB_SUCCESS on success and DB_ERROR on failure */
innodb_open_mysql_table(innodb_conn_data_t * conn_data,int conn_option,innodb_engine_t * engine)746 ib_err_t innodb_open_mysql_table(
747     /*====================*/
748     innodb_conn_data_t *conn_data, /*!< in/out:Connection cursor data */
749     int conn_option,               /*!< in: Read or write operation */
750     innodb_engine_t *engine)       /*!< in: InnoDB memcached engine */
751 {
752   meta_cfg_info_t *meta_info;
753   meta_info = conn_data->conn_meta;
754   conn_data->is_waiting_for_mdl = true;
755 
756   /* Close the table before opening it again */
757   innodb_close_mysql_table(conn_data);
758 
759   if (conn_option == CONN_MODE_READ) {
760     conn_data->is_waiting_for_mdl = false;
761     return (DB_SUCCESS);
762   }
763 
764   if (!conn_data->thd) {
765     conn_data->thd = handler_create_thd(engine->enable_binlog);
766     if (!conn_data->thd) {
767       return (DB_ERROR);
768     }
769   }
770 
771   if (!conn_data->mysql_tbl) {
772     conn_data->mysql_tbl = handler_open_table(
773         conn_data->thd, meta_info->col_info[CONTAINER_DB].col_name,
774         meta_info->col_info[CONTAINER_TABLE].col_name, HDL_WRITE);
775   }
776   conn_data->is_waiting_for_mdl = false;
777 
778   if (!conn_data->mysql_tbl) {
779     return (DB_LOCK_WAIT);
780   }
781 
782   return (DB_SUCCESS);
783 }
784 
785 /*******************************************************************/ /**
786  Cleanup connections
787  @return number of connection cleaned */
788 /* Initialize a connection's cursor and transactions
789 @return the connection's conn_data structure */
innodb_conn_init(innodb_engine_t * engine,const void * cookie,int conn_option,ib_lck_mode_t lock_mode,bool has_lock,meta_cfg_info_t * new_meta_info)790 static innodb_conn_data_t *innodb_conn_init(
791     /*=============*/
792     innodb_engine_t *engine,        /*!< in/out: InnoDB memcached
793                                     engine */
794     const void *cookie,             /*!< in: This connection's
795                                     cookie */
796     int conn_option,                /*!< in: whether it is
797                                     for read or write operation*/
798     ib_lck_mode_t lock_mode,        /*!< in: Table lock mode */
799     bool has_lock,                  /*!< in: Has engine mutex */
800     meta_cfg_info_t *new_meta_info) /*!< in: meta info for
801                                     table to open or NULL */
802 {
803   innodb_conn_data_t *conn_data;
804   meta_cfg_info_t *meta_info;
805   meta_index_t *meta_index;
806   ib_err_t err = DB_SUCCESS;
807   ib_crsr_t crsr = nullptr;
808   ib_crsr_t read_crsr = nullptr;
809   ib_crsr_t idx_crsr = nullptr;
810   bool trx_updated = false;
811 
812   /* Get this connection's conn_data */
813   conn_data =
814       (innodb_conn_data_t *)engine->server.cookie->get_engine_specific(cookie);
815 
816   assert(!conn_data || !conn_data->in_use || conn_data->range ||
817          conn_data->multi_get);
818 
819   if (!conn_data) {
820     LOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
821     conn_data =
822         (innodb_conn_data_t *)engine->server.cookie->get_engine_specific(
823             cookie);
824 
825     if (conn_data) {
826       UNLOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
827       goto have_conn;
828     }
829 
830     if (UT_LIST_GET_LEN(engine->conn_data) > 2048) {
831       /* Some of conn_data can be stale, recycle them */
832       innodb_conn_clean(engine, false, true);
833     }
834 
835     conn_data = (innodb_conn_data_t *)malloc(sizeof(*conn_data));
836 
837     if (!conn_data) {
838       UNLOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
839       return (NULL);
840     }
841 
842     memset(conn_data, 0, sizeof(*conn_data));
843     conn_data->result = malloc(sizeof(mci_item_t));
844     if (!conn_data->result) {
845       UNLOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
846       free(conn_data);
847       conn_data = NULL;
848       return (NULL);
849     }
850     conn_data->conn_meta = new_meta_info ? new_meta_info : engine->meta_info;
851 
852     /* FIX_ME: to make this dynamic extensible */
853     conn_data->row_buf = (void **)malloc(NUM_MAX_MEM_SLOT * sizeof(void *));
854     if (conn_data->row_buf == NULL) {
855       UNLOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
856       free(conn_data->result);
857       free(conn_data);
858       conn_data = NULL;
859       return (NULL);
860     }
861     memset(conn_data->row_buf, 0, NUM_MAX_MEM_SLOT * sizeof(void *));
862 
863     conn_data->row_buf[0] = (void *)malloc(REC_BUF_SLOT_SIZE);
864 
865     if (conn_data->row_buf[0] == NULL) {
866       UNLOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
867       free(conn_data->row_buf);
868       free(conn_data->result);
869       free(conn_data);
870       conn_data = NULL;
871       return (NULL);
872     }
873 
874     conn_data->row_buf_slot = 0;
875 
876     conn_data->cmd_buf = malloc(1024);
877     if (!conn_data->cmd_buf) {
878       UNLOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
879       free(conn_data->row_buf[0]);
880       free(conn_data->row_buf);
881       free(conn_data->result);
882       free(conn_data);
883       conn_data = NULL;
884       return (NULL);
885     }
886     conn_data->cmd_buf_len = 1024;
887 
888     conn_data->thd = handler_create_thd(engine->enable_binlog);
889 #ifdef UNIV_MEMCACHED_SDI
890     conn_data->sdi_buf = NULL;
891 #endif /* UNIV_MEMCACHED_SDI */
892 
893     conn_data->conn_cookie = (void *)cookie;
894 
895     /* Add connection to the list after all memory allocations */
896     UT_LIST_ADD_LAST(conn_list, engine->conn_data, conn_data);
897     engine->server.cookie->store_engine_specific(cookie, conn_data);
898 
899     pthread_mutex_init(&conn_data->curr_conn_mutex, NULL);
900     UT_LIST_INIT(conn_data->mul_used_buf);
901     UNLOCK_CONN_IF_NOT_LOCKED(has_lock, engine);
902   }
903 have_conn:
904   if (memcached_shutdown) {
905     return (NULL);
906   }
907 
908   meta_info = conn_data->conn_meta;
909   meta_index = &meta_info->index_info;
910 
911   assert(engine->conn_data.count > 0);
912 
913   assert(conn_data->thd);
914   handler_thd_attach(conn_data->thd, nullptr);
915 
916   if (conn_option == CONN_MODE_NONE) {
917     return (conn_data);
918   }
919 
920   /* If this is a range search or multi-key search, we do not
921   need to reset search cursor, continue with the one being used */
922   if (conn_data->range || conn_data->multi_get) {
923     return (conn_data);
924   }
925 
926   LOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
927 
928   /* This special case added to facilitate unlocking
929      of MDL lock during FLUSH TABLE WITH READ LOCK */
930   if (engine && release_mdl_lock &&
931       (engine->enable_binlog || engine->enable_mdl)) {
932     if (DB_SUCCESS != innodb_open_mysql_table(conn_data, conn_option, engine)) {
933       UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
934       return NULL;
935     }
936   }
937 
938   conn_data->in_use = true;
939 
940   crsr = conn_data->crsr;
941   read_crsr = conn_data->read_crsr;
942 
943   if (lock_mode == IB_LOCK_TABLE_X) {
944     if (!conn_data->crsr_trx) {
945       conn_data->crsr_trx =
946           ib_cb_trx_begin(engine->trx_level, true, false, conn_data->thd);
947     } else {
948       /* Write cursor transaction exists.
949          Reuse this transaction.*/
950       if (ib_cb_trx_read_only(conn_data->crsr_trx)) {
951         innodb_cb_trx_commit(conn_data->crsr_trx);
952       }
953 
954       err = ib_cb_trx_start(conn_data->crsr_trx, engine->trx_level, true, false,
955                             conn_data->thd);
956       assert(err == DB_SUCCESS);
957     }
958 
959     err = innodb_api_begin(engine, meta_info->col_info[CONTAINER_DB].col_name,
960                            meta_info->col_info[CONTAINER_TABLE].col_name,
961                            conn_data, conn_data->crsr_trx, &conn_data->crsr,
962                            &conn_data->idx_crsr, lock_mode);
963 
964     if (err != DB_SUCCESS) {
965       innodb_commit_and_release_crsr_trx(conn_data);
966       conn_data->in_use = false;
967       UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
968       return (NULL);
969     }
970 
971     UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
972     return (conn_data);
973   }
974 
975   /* Write operation */
976   if (conn_option == CONN_MODE_WRITE) {
977     if (!crsr) {
978       if (!conn_data->crsr_trx) {
979         conn_data->crsr_trx =
980             ib_cb_trx_begin(engine->trx_level, true, false, conn_data->thd);
981         trx_updated = true;
982       } else {
983         if (ib_cb_trx_read_only(conn_data->crsr_trx)) {
984           innodb_cb_trx_commit(conn_data->crsr_trx);
985         }
986 
987         ib_cb_trx_start(conn_data->crsr_trx, engine->trx_level, true, false,
988                         conn_data->thd);
989       }
990 
991       err = innodb_api_begin(engine, meta_info->col_info[CONTAINER_DB].col_name,
992                              meta_info->col_info[CONTAINER_TABLE].col_name,
993                              conn_data, conn_data->crsr_trx, &conn_data->crsr,
994                              &conn_data->idx_crsr, lock_mode);
995 
996       if (err != DB_SUCCESS) {
997         innodb_commit_and_release_crsr_trx(conn_data);
998         conn_data->in_use = false;
999 
1000         UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
1001         return (NULL);
1002       }
1003 
1004     } else if (!conn_data->crsr_trx) {
1005       /* There exists a cursor, just need update
1006       with a new transaction */
1007       conn_data->crsr_trx =
1008           ib_cb_trx_begin(engine->trx_level, true, false, conn_data->thd);
1009 
1010       innodb_cb_cursor_new_trx(crsr, conn_data->crsr_trx);
1011       trx_updated = true;
1012 
1013       err = innodb_cb_cursor_lock(engine, crsr, lock_mode);
1014 
1015       if (err != DB_SUCCESS) {
1016         innodb_commit_and_release_crsr_trx(conn_data);
1017         conn_data->in_use = false;
1018         UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
1019         return (NULL);
1020       }
1021 
1022       if (meta_index->srch_use_idx == META_USE_SECONDARY) {
1023         idx_crsr = conn_data->idx_crsr;
1024         innodb_cb_cursor_new_trx(idx_crsr, conn_data->crsr_trx);
1025         innodb_cb_cursor_lock(engine, idx_crsr, lock_mode);
1026       }
1027     } else {
1028       if (ib_cb_trx_read_only(conn_data->crsr_trx)) {
1029         innodb_cb_trx_commit(conn_data->crsr_trx);
1030       }
1031 
1032       ib_cb_trx_start(conn_data->crsr_trx, engine->trx_level, true, false,
1033                       conn_data->thd);
1034       ib_cb_cursor_stmt_begin(crsr);
1035       err = innodb_cb_cursor_lock(engine, crsr, lock_mode);
1036 
1037       if (err != DB_SUCCESS) {
1038         innodb_commit_and_release_crsr_trx(conn_data);
1039         conn_data->in_use = false;
1040         UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
1041         return (NULL);
1042       }
1043     }
1044 
1045     if (trx_updated) {
1046       if (conn_data->read_crsr) {
1047         innodb_cb_cursor_new_trx(conn_data->read_crsr, conn_data->crsr_trx);
1048       }
1049 
1050       if (conn_data->idx_read_crsr) {
1051         innodb_cb_cursor_new_trx(conn_data->idx_read_crsr, conn_data->crsr_trx);
1052       }
1053     }
1054   } else {
1055     /* This is a read operation, but depending on isolation level, it might
1056     require an InnoDB shared table intention lock, and/or a shared record lock.
1057     We need to tell in advance if our trx is "read_write" and "autocommit".
1058     These concepts are mapped through API and InnoDB's trx_start_low() in a
1059     complicated way. In particular the auto_commit maps to trx->api_auto_commit
1060     and if trx->api_auto_commit is true then trx_start_low does not set
1061     trx->will_lock (it stays 0). When we later request a lock, we violate
1062       !trx_is_autocommit_non_locking((t))
1063     assert in lock sys. So, if we plan to take locks, we should not set
1064     auto_commit, as InnoDB would interpret it as "autocommit non locking".*/
1065     const bool will_lock = lock_mode != IB_LOCK_NONE;
1066     const bool auto_commit = (!will_lock && engine->read_batch_size == 1 &&
1067                               !(engine->cfg_status & IB_CFG_DISABLE_ROWLOCK));
1068     assert(conn_option == CONN_MODE_READ);
1069 
1070     if (!read_crsr) {
1071       if (!conn_data->crsr_trx) {
1072         /* This is read operation, start a trx
1073         with "read_write" parameter set to false */
1074         conn_data->crsr_trx = ib_cb_trx_begin(engine->trx_level, false,
1075                                               auto_commit, conn_data->thd);
1076         trx_updated = true;
1077       } else {
1078         ib_cb_trx_start(conn_data->crsr_trx, engine->trx_level, false,
1079                         auto_commit, conn_data->thd);
1080       }
1081 
1082       err = innodb_api_begin(engine, meta_info->col_info[CONTAINER_DB].col_name,
1083                              meta_info->col_info[CONTAINER_TABLE].col_name,
1084                              conn_data, conn_data->crsr_trx,
1085                              &conn_data->read_crsr, &conn_data->idx_read_crsr,
1086                              lock_mode);
1087 
1088       if (err != DB_SUCCESS) {
1089         innodb_commit_and_release_crsr_trx(conn_data);
1090         conn_data->in_use = false;
1091         UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
1092 
1093         return (NULL);
1094       }
1095 
1096     } else if (!conn_data->crsr_trx) {
1097       /* This is read operation, start a trx
1098       with "read_write" parameter set to false */
1099       conn_data->crsr_trx = ib_cb_trx_begin(engine->trx_level, false,
1100                                             auto_commit, conn_data->thd);
1101 
1102       trx_updated = true;
1103 
1104       innodb_cb_cursor_new_trx(conn_data->read_crsr, conn_data->crsr_trx);
1105 
1106       if (conn_data->crsr) {
1107         innodb_cb_cursor_new_trx(conn_data->crsr, conn_data->crsr_trx);
1108       }
1109 
1110       err = innodb_cb_cursor_lock(engine, conn_data->read_crsr, lock_mode);
1111 
1112       if (err != DB_SUCCESS) {
1113         innodb_commit_and_release_crsr_trx(conn_data);
1114         conn_data->in_use = false;
1115         UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
1116 
1117         return (NULL);
1118       }
1119 
1120       if (meta_index->srch_use_idx == META_USE_SECONDARY) {
1121         ib_crsr_t idx_crsr = conn_data->idx_read_crsr;
1122 
1123         innodb_cb_cursor_new_trx(idx_crsr, conn_data->crsr_trx);
1124         innodb_cb_cursor_lock(engine, idx_crsr, lock_mode);
1125       }
1126     } else {
1127       /* This is read operation, start a trx
1128       with "read_write" parameter set to false */
1129       ib_cb_trx_start(conn_data->crsr_trx, engine->trx_level, false,
1130                       auto_commit, conn_data->thd);
1131 
1132       ib_cb_cursor_stmt_begin(conn_data->read_crsr);
1133 
1134       err = innodb_cb_cursor_lock(engine, conn_data->read_crsr, lock_mode);
1135 
1136       if (err != DB_SUCCESS) {
1137         innodb_commit_and_release_crsr_trx(conn_data);
1138         conn_data->in_use = false;
1139         UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
1140 
1141         return (NULL);
1142       }
1143 
1144       if (meta_index->srch_use_idx == META_USE_SECONDARY) {
1145         ib_crsr_t idx_crsr = conn_data->idx_read_crsr;
1146         ib_cb_cursor_stmt_begin(idx_crsr);
1147         innodb_cb_cursor_lock(engine, idx_crsr, lock_mode);
1148       }
1149     }
1150 
1151     if (trx_updated) {
1152       if (conn_data->crsr) {
1153         innodb_cb_cursor_new_trx(conn_data->crsr, conn_data->crsr_trx);
1154       }
1155 
1156       if (conn_data->idx_crsr) {
1157         innodb_cb_cursor_new_trx(conn_data->idx_crsr, conn_data->crsr_trx);
1158       }
1159     }
1160   }
1161 
1162   UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(has_lock, conn_data);
1163 
1164   return (conn_data);
1165 }
1166 
1167 /*** allocate ***/
1168 
1169 /*******************************************************************/ /**
1170  Allocate gets a struct item from the slab allocator, and fills in
1171  everything but the value.  It seems like we can just pass this on to
1172  the default engine; we'll intercept it later in store(). */
innodb_allocate(ENGINE_HANDLE * handle,const void * cookie,item ** item,const void * key,const size_t nkey,const size_t nbytes,const int flags,const rel_time_t exptime)1173 static ENGINE_ERROR_CODE innodb_allocate(
1174     /*============*/
1175     ENGINE_HANDLE *handle,    /*!< in: Engine handle */
1176     const void *cookie,       /*!< in: connection cookie */
1177     item **item,              /*!< out: item to allocate */
1178     const void *key,          /*!< in: key */
1179     const size_t nkey,        /*!< in: key length */
1180     const size_t nbytes,      /*!< in: estimated value length */
1181     const int flags,          /*!< in: flag */
1182     const rel_time_t exptime) /*!< in: expiration time */
1183 {
1184   size_t len;
1185   struct innodb_engine *innodb_eng = innodb_handle(handle);
1186   struct default_engine *def_eng = default_handle(innodb_eng);
1187   innodb_conn_data_t *conn_data;
1188   hash_item *it = NULL;
1189   meta_cfg_info_t *meta_info = innodb_eng->meta_info;
1190 
1191   conn_data =
1192       (innodb_conn_data_t *)innodb_eng->server.cookie->get_engine_specific(
1193           cookie);
1194 
1195   if (!conn_data) {
1196     conn_data = innodb_conn_init(innodb_eng, cookie, CONN_MODE_WRITE, IB_LOCK_X,
1197                                  false, NULL);
1198     if (!conn_data) {
1199       return (ENGINE_TMPFAIL);
1200     }
1201   }
1202 
1203   /* If system configured to use Memcached default engine (instead
1204   of InnoDB engine), continue to use Memcached's default memory
1205   allocation */
1206   if (meta_info->set_option == META_CACHE_OPT_DEFAULT ||
1207       meta_info->set_option == META_CACHE_OPT_MIX) {
1208     conn_data->use_default_mem = true;
1209     conn_data->in_use = false;
1210     return (def_eng->engine.allocate(innodb_eng->default_engine, cookie, item,
1211                                      key, nkey, nbytes, flags, exptime));
1212   }
1213 
1214   conn_data->use_default_mem = false;
1215   len = sizeof(*it) + nkey + nbytes + sizeof(uint64_t);
1216   if (len > conn_data->cmd_buf_len) {
1217     free(conn_data->cmd_buf);
1218     conn_data->cmd_buf = malloc(len);
1219     conn_data->cmd_buf_len = len;
1220   }
1221 
1222   it = (hash_item *)conn_data->cmd_buf;
1223 
1224   it->next = it->prev = it->h_next = 0;
1225   it->refcount = 1;
1226   it->iflag = def_eng->config.use_cas ? ITEM_WITH_CAS : 0;
1227   it->nkey = nkey;
1228   it->nbytes = nbytes;
1229   it->flags = flags;
1230   it->slabs_clsid = 1;
1231   /* item_get_key() is a memcached code, here we cast away const return */
1232   memcpy((void *)item_get_key(it), key, nkey);
1233   it->exptime = exptime;
1234 
1235   *item = it;
1236   conn_data->in_use = false;
1237 
1238   return (ENGINE_SUCCESS);
1239 }
1240 
1241 #ifdef UNIV_MEMCACHED_SDI
1242 #define check_key_name_for_sdi(key, nkey, pattern) \
1243   check_key_name_for_sdi_pattern(key, nkey, pattern, (sizeof pattern) - 1)
1244 
1245 /** Checks memcached key for SDI prefix patterns(sdi_, sdi_create_,
1246 sdi_list_). If the prefix exists, then operation is for SDI table
1247 @param[in]	key		Memcached Key
1248 @param[in]	nkey		Length of Memcached Key
1249 @param[in]	pattern		SDI patterns (sdi_, sdi_create_, sdi_list_)
1250 @param[in]	pattern_len	SDI pattern len
1251 @return true if it has prefix, else false */
check_key_name_for_sdi_pattern(const void * key,const size_t nkey,const char * pattern,const size_t pattern_len)1252 static bool check_key_name_for_sdi_pattern(const void *key, const size_t nkey,
1253                                            const char *pattern,
1254                                            const size_t pattern_len) {
1255   return (nkey >= pattern_len &&
1256           strncmp((const char *)key, pattern, pattern_len) == 0);
1257 }
1258 #endif /* UNIV_MEMCACHED_SDI */
1259 
1260 /*******************************************************************/ /**
1261  Cleanup connections
1262  @return number of connection cleaned */
innodb_remove(ENGINE_HANDLE * handle,const void * cookie,const void * key,const size_t nkey,uint64_t cas,uint16_t vbucket)1263 static ENGINE_ERROR_CODE innodb_remove(
1264     /*==========*/
1265     ENGINE_HANDLE *handle, /*!< in: Engine handle */
1266     const void *cookie,    /*!< in: connection cookie */
1267     const void *key,       /*!< in: key */
1268     const size_t nkey,     /*!< in: key length */
1269     uint64_t cas __attribute__((unused)),
1270     /*!< in: cas */
1271     uint16_t vbucket __attribute__((unused)))
1272 /*!< in: bucket, used by default
1273 engine only */
1274 {
1275   struct innodb_engine *innodb_eng = innodb_handle(handle);
1276   struct default_engine *def_eng = default_handle(innodb_eng);
1277   ENGINE_ERROR_CODE err_ret = ENGINE_SUCCESS;
1278   innodb_conn_data_t *conn_data;
1279   meta_cfg_info_t *meta_info = innodb_eng->meta_info;
1280   ENGINE_ERROR_CODE cacher_err = ENGINE_KEY_ENOENT;
1281 
1282   if (meta_info->del_option == META_CACHE_OPT_DISABLE) {
1283     return (ENGINE_SUCCESS);
1284   }
1285 
1286   if (meta_info->del_option == META_CACHE_OPT_DEFAULT ||
1287       meta_info->del_option == META_CACHE_OPT_MIX) {
1288     hash_item *item = item_get(def_eng, key, nkey);
1289 
1290     if (item != NULL) {
1291       item_unlink(def_eng, item);
1292       item_release(def_eng, item);
1293       cacher_err = ENGINE_SUCCESS;
1294     }
1295 
1296     if (meta_info->del_option == META_CACHE_OPT_DEFAULT) {
1297       return (cacher_err);
1298     }
1299   }
1300 
1301   conn_data = innodb_conn_init(innodb_eng, cookie, CONN_MODE_WRITE, IB_LOCK_X,
1302                                false, NULL);
1303 
1304   if (!conn_data) {
1305     return (ENGINE_TMPFAIL);
1306   }
1307 
1308 #ifdef UNIV_MEMCACHED_SDI
1309   if (innodb_sdi_remove(innodb_eng, conn_data, &err_ret, key, nkey)) {
1310     return (err_ret);
1311   }
1312 #endif /* UNIV_MEMCACHED_SDI */
1313 
1314   /* In the binary protocol there is such a thing as a CAS delete.
1315   This is the CAS check. If we will also be deleting from the database,
1316   there are two possibilities:
1317     1: The CAS matches; perform the delete.
1318     2: The CAS doesn't match; delete the item because it's stale.
1319   Therefore we skip the check altogether if(do_db_delete) */
1320 
1321   err_ret = innodb_api_delete(innodb_eng, conn_data, (const char *)key, nkey);
1322 
1323   innodb_api_cursor_reset(innodb_eng, conn_data, CONN_OP_DELETE,
1324                           err_ret == ENGINE_SUCCESS);
1325 
1326   return ((cacher_err == ENGINE_SUCCESS) ? ENGINE_SUCCESS : err_ret);
1327 }
1328 
1329 /*******************************************************************/ /**
1330  Switch the table mapping. Open the new table specified in "@@new_table_map.key"
1331  string.
1332  @return ENGINE_SUCCESS if successful, otherwise error code */
innodb_switch_mapping(ENGINE_HANDLE * handle,const void * cookie,const char * name,size_t * name_len,bool has_prefix)1333 static ENGINE_ERROR_CODE innodb_switch_mapping(
1334     /*==================*/
1335     ENGINE_HANDLE *handle, /*!< in: Engine handle */
1336     const void *cookie,    /*!< in: connection cookie */
1337     const char *name,      /*!< in: full name contains
1338                            table map name, and possible
1339                            key value */
1340     size_t *name_len,      /*!< in/out: name length,
1341                            out with length excludes
1342                            the table map name */
1343     bool has_prefix)       /*!< in: whether the name has
1344                            "@@" prefix */
1345 {
1346   struct innodb_engine *innodb_eng = innodb_handle(handle);
1347   innodb_conn_data_t *conn_data;
1348   char new_name[KEY_MAX_LENGTH];
1349   meta_cfg_info_t *meta_info = innodb_eng->meta_info;
1350   char *new_map_name;
1351   unsigned int new_map_name_len = 0;
1352   char *last;
1353   meta_cfg_info_t *new_meta_info;
1354   int sep_len = 0;
1355 
1356   if (has_prefix) {
1357     char *sep = NULL;
1358 
1359     assert(*name_len > 2 && name[0] == '@' && name[1] == '@');
1360     assert(*name_len < KEY_MAX_LENGTH);
1361 
1362     memcpy(new_name, &name[2], (*name_len) - 2);
1363 
1364     new_name[*name_len - 2] = 0;
1365 
1366     GET_OPTION(meta_info, OPTION_ID_TBL_MAP_SEP, sep, sep_len);
1367 
1368     assert(sep_len > 0);
1369 
1370     new_map_name = strtok_r(new_name, sep, &last);
1371 
1372     if (new_map_name == NULL) {
1373       return (ENGINE_KEY_ENOENT);
1374     }
1375 
1376     new_map_name_len = strlen(new_map_name);
1377   } else {
1378     /* This is used in the "bind" command, and without the
1379     "@@" prefix. */
1380     if (name == NULL) {
1381       return (ENGINE_KEY_ENOENT);
1382     }
1383 
1384     new_map_name = (char *)name;
1385     new_map_name_len = *name_len;
1386   }
1387 
1388   conn_data =
1389       (innodb_conn_data_t *)innodb_eng->server.cookie->get_engine_specific(
1390           cookie);
1391 
1392   /* Check if we are getting the same configure setting as existing one */
1393   if (conn_data && conn_data->conn_meta &&
1394       (new_map_name_len ==
1395        conn_data->conn_meta->col_info[CONTAINER_NAME].col_name_len) &&
1396       (strcmp(new_map_name,
1397               conn_data->conn_meta->col_info[CONTAINER_NAME].col_name) == 0)) {
1398     goto get_key_name;
1399   }
1400 
1401   if (conn_data && conn_data->multi_get) {
1402     goto get_key_name;
1403   }
1404 
1405   new_meta_info =
1406       innodb_config(new_map_name, new_map_name_len, &innodb_eng->meta_hash);
1407 
1408   if (!new_meta_info) {
1409     return (ENGINE_KEY_ENOENT);
1410   }
1411 
1412   /* Clean up the existing connection metadata if exists */
1413   if (conn_data) {
1414     innodb_conn_clean_data(conn_data, false, false);
1415 
1416     /* Point to the new metadata */
1417     conn_data->conn_meta = new_meta_info;
1418   }
1419 
1420   conn_data = innodb_conn_init(innodb_eng, cookie, CONN_MODE_NONE,
1421                                ib_lck_mode_t(0), false, new_meta_info);
1422   if (conn_data == nullptr) {
1423     return (ENGINE_TMPFAIL);
1424   }
1425 
1426   assert(conn_data->conn_meta == new_meta_info);
1427 
1428 get_key_name:
1429   /* Now calculate name length exclude the table mapping name,
1430   this is the length for the remaining key portion */
1431   if (has_prefix) {
1432     assert(*name_len >= strlen(new_map_name) + 2);
1433 
1434     if (*name_len >= strlen(new_map_name) + 2 + sep_len) {
1435       *name_len -= strlen(new_map_name) + 2 + sep_len;
1436     } else {
1437       /* the name does not even contain a delimiter,
1438       so there will be no keys either */
1439       *name_len = 0;
1440     }
1441   }
1442 
1443   return (ENGINE_SUCCESS);
1444 }
1445 
1446 /*******************************************************************/ /**
1447  check whether a table mapping switch is needed, if so, switch the table
1448  mapping
1449  @return ENGINE_SUCCESS if successful otherwise error code */
check_key_name_for_map_switch(ENGINE_HANDLE * handle,const void * cookie,const void * key,size_t * nkey)1450 static inline ENGINE_ERROR_CODE check_key_name_for_map_switch(
1451     /*==========================*/
1452     ENGINE_HANDLE *handle, /*!< in: Engine Handle */
1453     const void *cookie,    /*!< in: connection cookie */
1454     const void *key,       /*!< in: search key */
1455     size_t *nkey)          /*!< in/out: key length */
1456 {
1457   ENGINE_ERROR_CODE err_ret = ENGINE_SUCCESS;
1458 
1459   if ((*nkey) > 3 && ((char *)key)[0] == '@' && ((char *)key)[1] == '@') {
1460     err_ret =
1461         innodb_switch_mapping(handle, cookie, (const char *)key, nkey, true);
1462   }
1463 
1464   return (err_ret);
1465 }
1466 
1467 /*******************************************************************/ /**
1468  Function to support the "bind" command, bind the connection to a new
1469  table mapping.
1470  @return ENGINE_SUCCESS if successful, otherwise error code */
innodb_bind(ENGINE_HANDLE * handle,const void * cookie,const void * name,size_t name_len)1471 static ENGINE_ERROR_CODE innodb_bind(
1472     /*========*/
1473     ENGINE_HANDLE *handle, /*!< in: Engine handle */
1474     const void *cookie,    /*!< in: connection cookie */
1475     const void *name,      /*!< in: table ID name */
1476     size_t name_len)       /*!< in: name length */
1477 {
1478   ENGINE_ERROR_CODE err_ret = ENGINE_SUCCESS;
1479 
1480   err_ret = innodb_switch_mapping(handle, cookie, (const char *)name, &name_len,
1481                                   false);
1482 
1483   return (err_ret);
1484 }
1485 
1486 /*******************************************************************/ /**
1487  Release the connection, free resource allocated in innodb_allocate */
innodb_clean_engine(ENGINE_HANDLE * handle,const void * cookie,void * conn)1488 static void innodb_clean_engine(
1489     /*================*/
1490     ENGINE_HANDLE *handle, /*!< in: Engine handle */
1491     const void *cookie __attribute__((unused)),
1492     /*!< in: connection cookie */
1493     void *conn) /*!< in: item to free */
1494 {
1495   innodb_conn_data_t *conn_data = (innodb_conn_data_t *)conn;
1496   struct innodb_engine *engine = innodb_handle(handle);
1497   void *orignal_thd;
1498 
1499   LOCK_CURRENT_CONN_IF_NOT_LOCKED(false, conn_data);
1500   if (conn_data->thd) {
1501     handler_thd_attach(conn_data->thd, &orignal_thd);
1502   }
1503   innodb_reset_conn(conn_data, true, true, engine->enable_binlog);
1504   innodb_conn_clean_data(conn_data, true, false);
1505   conn_data->is_stale = true;
1506   UNLOCK_CURRENT_CONN_IF_NOT_LOCKED(false, conn_data);
1507 }
1508 
1509 /** Release the resources used to store query response item
1510 @param[in]  handle  Engine handle
1511 @param[in]  cookie  connection cookie
1512 @param[in]  item    item to free
1513 */
innodb_release(ENGINE_HANDLE * handle,const void * cookie,item * item)1514 static void innodb_release(ENGINE_HANDLE *handle, const void *cookie,
1515                            item *item) {
1516   struct innodb_engine *innodb_eng = innodb_handle(handle);
1517   innodb_conn_data_t *conn_data;
1518   mem_buf_t *mem_buf;
1519 
1520   conn_data =
1521       (innodb_conn_data_t *)innodb_eng->server.cookie->get_engine_specific(
1522           cookie);
1523 
1524   if (!conn_data) {
1525     return;
1526   }
1527 
1528   conn_data->result_in_use = false;
1529   conn_data->row_buf_slot = 0;
1530   conn_data->row_buf_used = 0;
1531   conn_data->range = false;
1532   conn_data->multi_get = false;
1533   conn_data->mul_col_buf_used = 0;
1534 
1535   mem_buf = UT_LIST_GET_FIRST(conn_data->mul_used_buf);
1536 
1537   while (mem_buf) {
1538     UT_LIST_REMOVE(mem_list, conn_data->mul_used_buf, mem_buf);
1539     free(mem_buf->mem);
1540     mem_buf = UT_LIST_GET_FIRST(conn_data->mul_used_buf);
1541   }
1542 
1543   /* If item's memory comes from Memcached default engine, release it
1544   through Memcached APIs */
1545   if (conn_data->use_default_mem) {
1546     struct default_engine *def_eng = default_handle(innodb_eng);
1547 
1548     item_release(def_eng, (hash_item *)item);
1549     conn_data->use_default_mem = false;
1550   }
1551 
1552   if (conn_data->range_key) {
1553     free(conn_data->range_key);
1554     conn_data->range_key = NULL;
1555   }
1556 
1557   return;
1558 }
1559 
1560 /* maximum number of characters that an 8 bytes integer can convert to */
1561 #define MAX_INT_CHAR_LEN 21
1562 
1563 /*******************************************************************/ /**
1564  Convert an bit int to string
1565  @return length of string */
convert_to_char(char * buf,int buf_len,void * value,int value_len,bool is_unsigned)1566 static int convert_to_char(
1567     /*============*/
1568     char *buf,        /*!< out: converted integer value */
1569     int buf_len,      /*!< in: buffer len */
1570     void *value,      /*!< in: int value */
1571     int value_len,    /*!< in: int len */
1572     bool is_unsigned) /*!< in: whether it is unsigned */
1573 {
1574   assert(buf && buf_len);
1575 
1576   if (value_len == 8) {
1577     if (is_unsigned) {
1578       uint64_t int_val = *(uint64_t *)value;
1579       snprintf(buf, buf_len, "%" PRIu64, int_val);
1580     } else {
1581       int64_t int_val = *(int64_t *)value;
1582       snprintf(buf, buf_len, "%" PRIi64, int_val);
1583     }
1584   } else if (value_len == 4) {
1585     if (is_unsigned) {
1586       uint32_t int_val = *(uint32_t *)value;
1587       snprintf(buf, buf_len, "%" PRIu32, int_val);
1588     } else {
1589       int32_t int_val = *(int32_t *)value;
1590       snprintf(buf, buf_len, "%" PRIi32, int_val);
1591     }
1592   } else if (value_len == 2) {
1593     if (is_unsigned) {
1594       uint16_t int_val = *(uint16_t *)value;
1595       snprintf(buf, buf_len, "%" PRIu16, int_val);
1596     } else {
1597       int16_t int_val = *(int16_t *)value;
1598       snprintf(buf, buf_len, "%" PRIi16, int_val);
1599     }
1600   } else if (value_len == 1) {
1601     if (is_unsigned) {
1602       uint8_t int_val = *(uint8_t *)value;
1603       snprintf(buf, buf_len, "%" PRIu8, int_val);
1604     } else {
1605       int8_t int_val = *(int8_t *)value;
1606       snprintf(buf, buf_len, "%" PRIi8, int_val);
1607     }
1608   } else {
1609     assert(!"invalid byte length of integer");
1610     return 0;
1611   }
1612 
1613   return (strlen(buf));
1614 }
1615 
1616 /*******************************************************************/ /**
1617  Free value assocaited with key */
innodb_free_item(void * item)1618 static void innodb_free_item(
1619     /*=====================*/
1620     void *item) /*!< in: Item to be freed */
1621 {
1622   mci_item_t *result = (mci_item_t *)item;
1623   if (result->extra_col_value) {
1624     for (int i = 0; i < result->n_extra_col; i++) {
1625       if (result->extra_col_value[i].allocated)
1626         free(result->extra_col_value[i].value_str);
1627     }
1628     free(result->extra_col_value);
1629     result->extra_col_value = NULL;
1630   }
1631   if (result->col_value[MCI_COL_VALUE].allocated) {
1632     free(result->col_value[MCI_COL_VALUE].value_str);
1633     result->col_value[MCI_COL_VALUE].allocated = false;
1634   }
1635 }
1636 /*******************************************************************/ /**
1637  Support memcached "GET" command, fetch the value according to key
1638  @return ENGINE_SUCCESS if successfully, otherwise error code */
innodb_get(ENGINE_HANDLE * handle,const void * cookie,item ** item,const void * key,const int nkey,uint16_t next_get)1639 static ENGINE_ERROR_CODE innodb_get(
1640     /*=======*/
1641     ENGINE_HANDLE *handle, /*!< in: Engine Handle */
1642     const void *cookie,    /*!< in: connection cookie */
1643     item **item,           /*!< out: item to fill */
1644     const void *key,       /*!< in: search key */
1645     const int nkey,        /*!< in: key length */
1646     uint16_t next_get)     /*!< in: has more item to get */
1647 {
1648   struct innodb_engine *innodb_eng = innodb_handle(handle);
1649   ib_crsr_t crsr = nullptr;
1650   ib_err_t err = DB_SUCCESS;
1651   mci_item_t *result = NULL;
1652   ENGINE_ERROR_CODE err_ret = ENGINE_SUCCESS;
1653   innodb_conn_data_t *conn_data = NULL;
1654   meta_cfg_info_t *meta_info = innodb_eng->meta_info;
1655   int option_length;
1656   const char *option_delimiter;
1657   size_t key_len = nkey;
1658   int lock_mode;
1659   bool report_table_switch = false;
1660   void *newkey;
1661   bool is_range_srch = false;
1662   ;
1663 
1664   if (memcached_shutdown) {
1665     return (ENGINE_TMPFAIL);
1666   }
1667 
1668   if (meta_info->get_option == META_CACHE_OPT_DISABLE) {
1669     return (ENGINE_KEY_ENOENT);
1670   }
1671 
1672   if (meta_info->get_option == META_CACHE_OPT_DEFAULT ||
1673       meta_info->get_option == META_CACHE_OPT_MIX) {
1674     *item = item_get(default_handle(innodb_eng), key, nkey);
1675 
1676     if (*item != NULL) {
1677       return (ENGINE_SUCCESS);
1678     }
1679 
1680     if (meta_info->get_option == META_CACHE_OPT_DEFAULT) {
1681       return (ENGINE_KEY_ENOENT);
1682     }
1683   }
1684 
1685   /* Check if we need to switch table mapping */
1686   err_ret = check_key_name_for_map_switch(handle, cookie, key, &key_len);
1687 
1688   /* If specified new table map does not exist, or table does not
1689   qualify for InnoDB memcached, return error */
1690   if (err_ret != ENGINE_SUCCESS) {
1691     goto err_exit;
1692   }
1693 
1694   /* If only the new mapping name is provided, and no key value,
1695   return here */
1696   if (key_len <= 0) {
1697     /* If this is a command in the form of "get @@new_table_map",
1698     for the purpose of switching to the specified table with
1699     the table map name, if the switch is successful, we will
1700     return the table name as result */
1701     if (nkey > 0) {
1702       report_table_switch = true;
1703 
1704       goto search_done;
1705     }
1706 
1707     err_ret = ENGINE_KEY_ENOENT;
1708     goto err_exit;
1709   }
1710 
1711   lock_mode = (innodb_eng->trx_level == IB_TRX_SERIALIZABLE &&
1712                innodb_eng->read_batch_size == 1)
1713                   ? IB_LOCK_S
1714                   : IB_LOCK_NONE;
1715 
1716   conn_data = innodb_conn_init(innodb_eng, cookie, CONN_MODE_READ,
1717                                ib_lck_mode_t(lock_mode), false, NULL);
1718 
1719   if (!conn_data) {
1720     return (ENGINE_TMPFAIL);
1721   }
1722 
1723   result = (mci_item_t *)(conn_data->result);
1724 
1725   newkey = (char *)key + nkey - key_len;
1726 
1727   /* This signifies a range search, so set up the range search info */
1728   if (*(char *)newkey == '@' && !conn_data->range) {
1729     assert(!conn_data->range_key);
1730 
1731     if (((char *)newkey)[1] == '<') {
1732       char *start_key;
1733 
1734       is_range_srch = true;
1735 
1736       conn_data->range_key =
1737           (innodb_range_key_t *)malloc(sizeof *(conn_data->range_key));
1738 
1739       if (((char *)newkey)[2] == '=') {
1740         conn_data->range_key->end_mode = IB_CUR_LE;
1741         key_len -= 3;
1742       } else {
1743         conn_data->range_key->end_mode = IB_CUR_L;
1744         key_len -= 2;
1745       }
1746 
1747       conn_data->range_key->end = (char *)key + nkey - key_len;
1748       conn_data->range_key->end_len = key_len;
1749 
1750       start_key = strstr((char *)newkey, "@>");
1751       if (start_key) {
1752         conn_data->range_key->bound = RANGE_BOUND;
1753         uint cmp_len = 2;
1754 
1755         if (start_key[2] == '=') {
1756           conn_data->range_key->start_mode = IB_CUR_GE;
1757           cmp_len = 3;
1758         } else {
1759           conn_data->range_key->start_mode = IB_CUR_G;
1760         }
1761         conn_data->range_key->end_len = start_key - conn_data->range_key->end;
1762         conn_data->range_key->start = &start_key[cmp_len];
1763         conn_data->range_key->start_len =
1764             key_len - conn_data->range_key->end_len - cmp_len;
1765       } else {
1766         conn_data->range_key->start = NULL;
1767         conn_data->range_key->start_len = 0;
1768         conn_data->range_key->start_mode = 0;
1769         conn_data->range_key->bound = UPPER_BOUND;
1770       }
1771     } else if (((char *)newkey)[1] == '>') {
1772       char *end_key;
1773 
1774       is_range_srch = true;
1775 
1776       conn_data->range_key =
1777           (innodb_range_key_t *)malloc(sizeof *(conn_data->range_key));
1778 
1779       if (((char *)newkey)[2] == '=') {
1780         conn_data->range_key->start_mode = IB_CUR_GE;
1781         key_len -= 3;
1782       } else {
1783         conn_data->range_key->start_mode = IB_CUR_G;
1784         key_len -= 2;
1785       }
1786 
1787       conn_data->range_key->start_len = key_len;
1788       conn_data->range_key->start = (char *)key + nkey - key_len;
1789 
1790       end_key = strstr((char *)newkey, "@<");
1791       if (end_key) {
1792         conn_data->range_key->bound = RANGE_BOUND;
1793         uint cmp_len = 2;
1794 
1795         if (end_key[2] == '=') {
1796           conn_data->range_key->end_mode = IB_CUR_LE;
1797           cmp_len = 3;
1798         } else {
1799           conn_data->range_key->end_mode = IB_CUR_L;
1800         }
1801         conn_data->range_key->start_len = end_key - conn_data->range_key->start;
1802         conn_data->range_key->end = &end_key[cmp_len];
1803         conn_data->range_key->end_len =
1804             key_len - conn_data->range_key->start_len - cmp_len;
1805       } else {
1806         conn_data->range_key->end = NULL;
1807         conn_data->range_key->end_len = 0;
1808         conn_data->range_key->end_mode = 0;
1809         conn_data->range_key->bound = LOW_BOUND;
1810       }
1811     }
1812   }
1813 
1814   if (conn_data->range) {
1815     is_range_srch = true;
1816   }
1817 
1818 #ifdef UNIV_MEMCACHED_SDI
1819   if (innodb_sdi_get(conn_data, &err_ret, key, nkey, &item)) {
1820     goto func_exit;
1821   }
1822 #endif /* UNIV_MEMCACHED_SDI */
1823 
1824   err = innodb_api_search(conn_data, &crsr, (const char *)key + nkey - key_len,
1825                           key_len, result, NULL, true,
1826                           is_range_srch ? conn_data->range_key : NULL);
1827 
1828   if (is_range_srch && err != DB_END_OF_INDEX) {
1829     /* we set it only after the first search. This is used to
1830     tell innodb_api_search() if it is the first search, which
1831     might need to do the intial position of cursor */
1832     conn_data->range = true;
1833   }
1834 
1835   if (next_get) {
1836     conn_data->multi_get = true;
1837   }
1838 
1839   if (conn_data->multi_get && next_get == 0) {
1840     conn_data->multi_get = false;
1841   }
1842 
1843   if (err != DB_SUCCESS) {
1844     err_ret = ENGINE_KEY_ENOENT;
1845     goto func_exit;
1846   }
1847 
1848 search_done:
1849   if (report_table_switch) {
1850     char table_name[MAX_TABLE_NAME_LEN + MAX_DATABASE_NAME_LEN];
1851     char *name;
1852     char *dbname;
1853 
1854     conn_data =
1855         (innodb_conn_data_t *)innodb_eng->server.cookie->get_engine_specific(
1856             cookie);
1857     assert(nkey > 0);
1858 
1859     name = conn_data->conn_meta->col_info[CONTAINER_TABLE].col_name;
1860     dbname = conn_data->conn_meta->col_info[CONTAINER_DB].col_name;
1861 #ifdef __WIN__
1862     sprintf(table_name, "%s\%s", dbname, name);
1863 #else
1864     snprintf(table_name, sizeof(table_name), "%s/%s", dbname, name);
1865 #endif
1866 
1867     assert(!conn_data->result_in_use);
1868     conn_data->result_in_use = true;
1869     result = (mci_item_t *)(conn_data->result);
1870 
1871     memset(result, 0, sizeof(*result));
1872     assert(conn_data->row_buf_used + strlen(table_name) < REC_BUF_SLOT_SIZE);
1873     memcpy((char *)(conn_data->row_buf[conn_data->row_buf_slot]) +
1874                conn_data->row_buf_used,
1875            table_name, strlen(table_name));
1876 
1877     result->col_value[MCI_COL_VALUE].value_str =
1878         ((char *)conn_data->row_buf[conn_data->row_buf_slot]) +
1879         conn_data->row_buf_used;
1880     result->col_value[MCI_COL_VALUE].value_len = strlen(table_name);
1881     conn_data->row_buf_used += result->col_value[MCI_COL_VALUE].value_len;
1882     result->col_value[MCI_COL_VALUE].is_str = true;
1883     result->col_value[MCI_COL_VALUE].is_valid = true;
1884   }
1885 
1886   if (!conn_data->range) {
1887     result->col_value[MCI_COL_KEY].value_str = (char *)key;
1888     result->col_value[MCI_COL_KEY].value_len = nkey;
1889   }
1890 
1891   /* Only if expiration field is enabled, and the value is not zero,
1892   we will check whether the item is expired */
1893   if (result->col_value[MCI_COL_EXP].is_valid &&
1894       result->col_value[MCI_COL_EXP].value_int) {
1895     uint64_t time;
1896     time = mci_get_time();
1897     if (time > result->col_value[MCI_COL_EXP].value_int) {
1898       innodb_free_item(result);
1899       err_ret = ENGINE_KEY_ENOENT;
1900       goto func_exit;
1901     }
1902   }
1903 
1904   if (result->extra_col_value) {
1905     int i;
1906     char *c_value;
1907     char *value_end MY_ATTRIBUTE((unused));
1908     unsigned int total_len = 0;
1909     char int_buf[MAX_INT_CHAR_LEN];
1910     GET_OPTION(meta_info, OPTION_ID_COL_SEP, option_delimiter, option_length);
1911 
1912     assert(option_length > 0 && option_delimiter);
1913 
1914     for (i = 0; i < result->n_extra_col; i++) {
1915       mci_column_t *mci_item = &result->extra_col_value[i];
1916 
1917       if (mci_item->value_len == 0) {
1918         total_len += option_length;
1919         continue;
1920       }
1921 
1922       if (!mci_item->is_str) {
1923         memset(int_buf, 0, sizeof int_buf);
1924         assert(!mci_item->value_str);
1925 
1926         total_len +=
1927             convert_to_char(int_buf, sizeof int_buf, &mci_item->value_int,
1928                             mci_item->value_len, mci_item->is_unsigned);
1929       } else {
1930         total_len += result->extra_col_value[i].value_len;
1931       }
1932 
1933       total_len += option_length;
1934     }
1935 
1936     /* No need to add the last separator */
1937     total_len -= option_length;
1938 
1939     if (conn_data->mul_col_buf_len < total_len + conn_data->mul_col_buf_used) {
1940       /* Need to keep the old result buffer, since its
1941       point is already registered with memcached output
1942       buffer. These result buffers will be release
1943       once results are all reported */
1944       if (conn_data->mul_col_buf) {
1945         mem_buf_t *new_temp = (mem_buf_t *)malloc(sizeof(mem_buf_t));
1946         new_temp->mem = conn_data->mul_col_buf;
1947         UT_LIST_ADD_LAST(mem_list, conn_data->mul_used_buf, new_temp);
1948       }
1949 
1950       conn_data->mul_col_buf = (char *)malloc(total_len);
1951       conn_data->mul_col_buf_len = total_len;
1952       conn_data->mul_col_buf_used = 0;
1953     }
1954 
1955     c_value = &conn_data->mul_col_buf[conn_data->mul_col_buf_used];
1956     assert(conn_data->mul_col_buf_used + total_len <=
1957            conn_data->mul_col_buf_len);
1958     value_end = c_value + total_len;
1959 
1960     for (i = 0; i < result->n_extra_col; i++) {
1961       mci_column_t *col_value;
1962 
1963       col_value = &result->extra_col_value[i];
1964 
1965       if (col_value->value_len != 0) {
1966         if (!col_value->is_str) {
1967           ib_ulint_t int_len;
1968           memset(int_buf, 0, sizeof int_buf);
1969 
1970           int_len =
1971               convert_to_char(int_buf, sizeof int_buf, &col_value->value_int,
1972                               col_value->value_len, col_value->is_unsigned);
1973 
1974           assert(int_len <= conn_data->mul_col_buf_len);
1975           assert(c_value + int_len <= value_end);
1976 
1977           memcpy(c_value, int_buf, int_len);
1978           c_value += int_len;
1979         } else {
1980           memcpy(c_value, col_value->value_str, col_value->value_len);
1981           c_value += col_value->value_len;
1982         }
1983       }
1984 
1985       if (i < result->n_extra_col - 1) {
1986         memcpy(c_value, option_delimiter, option_length);
1987         c_value += option_length;
1988       }
1989 
1990       assert(c_value <= value_end);
1991 
1992       if (col_value->allocated) {
1993         free(col_value->value_str);
1994         col_value->value_str = nullptr;
1995         col_value->allocated = false;
1996         col_value->value_len = 0;
1997         col_value->is_str = false;
1998         col_value->is_valid = false;
1999       }
2000     }
2001     assert(c_value == value_end);
2002 
2003     result->col_value[MCI_COL_VALUE].value_str =
2004         &conn_data->mul_col_buf[conn_data->mul_col_buf_used];
2005     result->col_value[MCI_COL_VALUE].value_len = total_len;
2006     result->col_value[MCI_COL_VALUE].is_str = true;
2007     result->col_value[MCI_COL_VALUE].is_valid = true;
2008     conn_data->mul_col_buf_used += total_len;
2009 
2010     free(result->extra_col_value);
2011     result->extra_col_value = nullptr;
2012   } else if (!result->col_value[MCI_COL_VALUE].is_str &&
2013              result->col_value[MCI_COL_VALUE].value_len != 0) {
2014     unsigned int int_len;
2015     char int_buf[MAX_INT_CHAR_LEN] = {};
2016 
2017     int_len = convert_to_char(int_buf, sizeof int_buf,
2018                               &result->col_value[MCI_COL_VALUE].value_int,
2019                               result->col_value[MCI_COL_VALUE].value_len,
2020                               result->col_value[MCI_COL_VALUE].is_unsigned);
2021 
2022     assert(conn_data->mul_col_buf_used == 0);
2023     if (int_len > conn_data->mul_col_buf_len) {
2024       if (conn_data->mul_col_buf) {
2025         free(conn_data->mul_col_buf);
2026       }
2027 
2028       conn_data->mul_col_buf = (char *)malloc(int_len);
2029       conn_data->mul_col_buf_len = int_len;
2030     }
2031 
2032     if (int_len > 0) {
2033       memcpy(conn_data->mul_col_buf, int_buf, int_len);
2034       conn_data->mul_col_buf_used += int_len;
2035     }
2036     result->col_value[MCI_COL_VALUE].value_str = conn_data->mul_col_buf;
2037 
2038     result->col_value[MCI_COL_VALUE].value_len = int_len;
2039     result->col_value[MCI_COL_VALUE].is_str = true;
2040     result->col_value[MCI_COL_VALUE].is_valid = true;
2041   }
2042 
2043   *item = result;
2044 
2045 func_exit:
2046 
2047   if ((!report_table_switch && !is_range_srch && !next_get) ||
2048       err == DB_END_OF_INDEX || (conn_data->range && err != DB_SUCCESS)) {
2049     innodb_api_cursor_reset(innodb_eng, conn_data, CONN_OP_READ, true);
2050   }
2051 
2052 err_exit:
2053 
2054   /* If error return, memcached will not call InnoDB Memcached's
2055   callback function "innodb_release" to reset the result_in_use
2056   value. So we reset it here */
2057   if (err_ret != ENGINE_SUCCESS && conn_data) {
2058     if (conn_data->range_key) {
2059       free(conn_data->range_key);
2060       conn_data->range_key = NULL;
2061     }
2062     conn_data->range = false;
2063 
2064     conn_data->result_in_use = false;
2065   }
2066   return (err_ret);
2067 }
2068 
2069 /*******************************************************************/ /**
2070  Get statistics info
2071  @return ENGINE_SUCCESS if successfully, otherwise error code */
innodb_get_stats(ENGINE_HANDLE * handle,const void * cookie,const char * stat_key,int nkey,ADD_STAT add_stat)2072 static ENGINE_ERROR_CODE innodb_get_stats(
2073     /*=============*/
2074     ENGINE_HANDLE *handle, /*!< in: Engine Handle */
2075     const void *cookie,    /*!< in: connection cookie */
2076     const char *stat_key,  /*!< in: statistics key */
2077     int nkey,              /*!< in: key length */
2078     ADD_STAT add_stat)     /*!< out: stats to fill */
2079 {
2080   struct innodb_engine *innodb_eng = innodb_handle(handle);
2081   struct default_engine *def_eng = default_handle(innodb_eng);
2082   return (def_eng->engine.get_stats(innodb_eng->default_engine, cookie,
2083                                     stat_key, nkey, add_stat));
2084 }
2085 
2086 /*******************************************************************/ /**
2087  reset statistics */
innodb_reset_stats(ENGINE_HANDLE * handle,const void * cookie)2088 static void innodb_reset_stats(
2089     /*===============*/
2090     ENGINE_HANDLE *handle, /*!< in: Engine Handle */
2091     const void *cookie)    /*!< in: connection cookie */
2092 {
2093   struct innodb_engine *innodb_eng = innodb_handle(handle);
2094   struct default_engine *def_eng = default_handle(innodb_eng);
2095   def_eng->engine.reset_stats(innodb_eng->default_engine, cookie);
2096 }
2097 
2098 /*******************************************************************/ /**
2099  API interface for memcached's "SET", "ADD", "REPLACE", "APPEND"
2100  "PREPENT" and "CAS" commands
2101  @return ENGINE_SUCCESS if successfully, otherwise error code */
innodb_store(ENGINE_HANDLE * handle,const void * cookie,item * item,uint64_t * cas,ENGINE_STORE_OPERATION op,uint16_t vbucket)2102 static ENGINE_ERROR_CODE innodb_store(
2103     /*=========*/
2104     ENGINE_HANDLE *handle,     /*!< in: Engine Handle */
2105     const void *cookie,        /*!< in: connection cookie */
2106     item *item,                /*!< out: result to fill */
2107     uint64_t *cas,             /*!< in: cas value */
2108     ENGINE_STORE_OPERATION op, /*!< in: type of operation */
2109     uint16_t vbucket __attribute__((unused)))
2110 /*!< in: bucket, used by default
2111 engine only */
2112 {
2113   struct innodb_engine *innodb_eng = innodb_handle(handle);
2114   uint16_t len = hash_item_get_key_len((const hash_item *)item);
2115   char *value = hash_item_get_key((const hash_item *)item);
2116   uint64_t exptime = hash_item_get_exp((const hash_item *)item);
2117   uint64_t flags = hash_item_get_flag((const hash_item *)item);
2118   ENGINE_ERROR_CODE result;
2119   uint64_t input_cas;
2120   innodb_conn_data_t *conn_data;
2121   meta_cfg_info_t *meta_info = innodb_eng->meta_info;
2122   uint32_t val_len = ((hash_item *)item)->nbytes;
2123   size_t key_len = len;
2124   ENGINE_ERROR_CODE err_ret = ENGINE_SUCCESS;
2125 
2126   if (meta_info->set_option == META_CACHE_OPT_DISABLE) {
2127     return (ENGINE_SUCCESS);
2128   }
2129 
2130   if (meta_info->set_option == META_CACHE_OPT_DEFAULT ||
2131       meta_info->set_option == META_CACHE_OPT_MIX) {
2132     result = store_item(default_handle(innodb_eng), (hash_item *)item, cas, op,
2133                         cookie);
2134 
2135     if (meta_info->set_option == META_CACHE_OPT_DEFAULT) {
2136       return (result);
2137     }
2138   }
2139 
2140   err_ret = check_key_name_for_map_switch(handle, cookie, value, &key_len);
2141 
2142   if (err_ret != ENGINE_SUCCESS) {
2143     return (err_ret);
2144   }
2145 
2146   /* If no key is provided, return here */
2147   if (key_len <= 0) {
2148     return (ENGINE_NOT_STORED);
2149   }
2150 
2151   conn_data = innodb_conn_init(innodb_eng, cookie, CONN_MODE_WRITE, IB_LOCK_X,
2152                                false, NULL);
2153 
2154   if (!conn_data) {
2155     return (ENGINE_NOT_STORED);
2156   }
2157 
2158   input_cas = hash_item_get_cas((const hash_item *)item);
2159 
2160 #ifdef UNIV_MEMCACHED_SDI
2161   if (innodb_sdi_store(innodb_eng, conn_data, &result, value, val_len,
2162                        key_len)) {
2163     return (result);
2164   }
2165 #endif /* UNIV_MEMACHED_SDI */
2166 
2167   result =
2168       innodb_api_store(innodb_eng, conn_data, value + len - key_len, key_len,
2169                        val_len, exptime, cas, input_cas, flags, op);
2170 
2171   innodb_api_cursor_reset(innodb_eng, conn_data, CONN_OP_WRITE,
2172                           result == ENGINE_SUCCESS);
2173   return (result);
2174 }
2175 
2176 /*******************************************************************/ /**
2177  Support memcached "INCR" and "DECR" command, add or subtract a "delta"
2178  value from an integer key value
2179  @return ENGINE_SUCCESS if successfully, otherwise error code */
innodb_arithmetic(ENGINE_HANDLE * handle,const void * cookie,const void * key,const int nkey,const bool increment,const bool create,const uint64_t delta,const uint64_t initial,const rel_time_t exptime,uint64_t * cas,uint64_t * result,uint16_t vbucket)2180 static ENGINE_ERROR_CODE innodb_arithmetic(
2181     /*==============*/
2182     ENGINE_HANDLE *handle,    /*!< in: Engine Handle */
2183     const void *cookie,       /*!< in: connection cookie */
2184     const void *key,          /*!< in: key for the value to add */
2185     const int nkey,           /*!< in: key length */
2186     const bool increment,     /*!< in: whether to increment
2187                               or decrement */
2188     const bool create,        /*!< in: whether to create the key
2189                               value pair if can't find */
2190     const uint64_t delta,     /*!< in: value to add/substract */
2191     const uint64_t initial,   /*!< in: initial */
2192     const rel_time_t exptime, /*!< in: expiration time */
2193     uint64_t *cas,            /*!< out: new cas value */
2194     uint64_t *result,         /*!< out: result value */
2195     uint16_t vbucket)         /*!< in: bucket, used by default
2196                               engine only */
2197 {
2198   struct innodb_engine *innodb_eng = innodb_handle(handle);
2199   struct default_engine *def_eng = default_handle(innodb_eng);
2200   innodb_conn_data_t *conn_data;
2201   meta_cfg_info_t *meta_info = innodb_eng->meta_info;
2202   ENGINE_ERROR_CODE err_ret;
2203 
2204   if (meta_info->set_option == META_CACHE_OPT_DISABLE) {
2205     return (ENGINE_SUCCESS);
2206   }
2207 
2208   if (meta_info->set_option == META_CACHE_OPT_DEFAULT ||
2209       meta_info->set_option == META_CACHE_OPT_MIX) {
2210     /* For cache-only, forward this to the
2211     default engine */
2212     err_ret = def_eng->engine.arithmetic(
2213         innodb_eng->default_engine, cookie, key, nkey, increment, create, delta,
2214         initial, exptime, cas, result, vbucket);
2215 
2216     if (meta_info->set_option == META_CACHE_OPT_DEFAULT) {
2217       return (err_ret);
2218     }
2219   }
2220 
2221   conn_data = innodb_conn_init(innodb_eng, cookie, CONN_MODE_WRITE, IB_LOCK_X,
2222                                false, NULL);
2223 
2224   if (!conn_data) {
2225     return (ENGINE_NOT_STORED);
2226   }
2227 
2228   err_ret = innodb_api_arithmetic(innodb_eng, conn_data, (const char *)key,
2229                                   nkey, delta, increment, cas, exptime, create,
2230                                   initial, result);
2231 
2232   innodb_api_cursor_reset(innodb_eng, conn_data, CONN_OP_WRITE, true);
2233 
2234   return (err_ret);
2235 }
2236 
2237 /*******************************************************************/ /**
2238  Support memcached "FLUSH_ALL" command, clean up storage (trunate InnoDB Table)
2239  @return ENGINE_SUCCESS if successfully, otherwise error code */
innodb_flush(ENGINE_HANDLE * handle,const void * cookie,time_t when)2240 static ENGINE_ERROR_CODE innodb_flush(
2241     /*=========*/
2242     ENGINE_HANDLE *handle, /*!< in: Engine Handle */
2243     const void *cookie,    /*!< in: connection cookie */
2244     time_t when)           /*!< in: when to flush, not used by
2245                            InnoDB */
2246 {
2247   struct innodb_engine *innodb_eng = innodb_handle(handle);
2248   struct default_engine *def_eng = default_handle(innodb_eng);
2249   ENGINE_ERROR_CODE err = ENGINE_SUCCESS;
2250   meta_cfg_info_t *meta_info = innodb_eng->meta_info;
2251   ib_err_t ib_err = DB_SUCCESS;
2252   innodb_conn_data_t *conn_data;
2253 
2254   if (meta_info->flush_option == META_CACHE_OPT_DISABLE) {
2255     return (ENGINE_SUCCESS);
2256   }
2257 
2258   if (meta_info->flush_option == META_CACHE_OPT_DEFAULT ||
2259       meta_info->flush_option == META_CACHE_OPT_MIX) {
2260     /* default engine flush */
2261     err = def_eng->engine.flush(innodb_eng->default_engine, cookie, when);
2262 
2263     if (meta_info->flush_option == META_CACHE_OPT_DEFAULT) {
2264       return (err);
2265     }
2266   }
2267 
2268   conn_data =
2269       (innodb_conn_data_t *)innodb_eng->server.cookie->get_engine_specific(
2270           cookie);
2271 
2272   if (conn_data) {
2273     /* Commit any work on this connection before waiting for locks.
2274     This is to avoid a deadlock, in which another thread also doing flush_all
2275     will wait for a lock we hold on our conn_data */
2276     innodb_api_cursor_reset(innodb_eng, conn_data, CONN_OP_FLUSH, true);
2277     innodb_conn_clean_data(conn_data, false, false);
2278   }
2279 
2280   conn_data = innodb_conn_init(innodb_eng, cookie, CONN_MODE_WRITE,
2281                                IB_LOCK_TABLE_X, false, NULL);
2282 
2283   if (!conn_data) {
2284     /* TBD: why in the past we've returned ENGINE_SUCCESS in this case? */
2285     return (ENGINE_TMPFAIL);
2286   }
2287 
2288   meta_info = conn_data->conn_meta;
2289   ib_err = ib_err_t(innodb_api_flush(
2290       innodb_eng, conn_data, meta_info->col_info[CONTAINER_DB].col_name,
2291       meta_info->col_info[CONTAINER_TABLE].col_name));
2292 
2293   /* Commit work and release the MDL table. */
2294   innodb_api_cursor_reset(innodb_eng, conn_data, CONN_OP_FLUSH, true);
2295   innodb_conn_clean_data(conn_data, false, false);
2296 
2297   return ((ib_err == DB_SUCCESS) ? ENGINE_SUCCESS : ENGINE_TMPFAIL);
2298 }
2299 
2300 /*******************************************************************/ /**
2301  Deal with unknown command. Currently not used
2302  @return ENGINE_SUCCESS if successfully processed, otherwise error code */
innodb_unknown_command(ENGINE_HANDLE * handle,const void * cookie,protocol_binary_request_header * request,ADD_RESPONSE response)2303 static ENGINE_ERROR_CODE innodb_unknown_command(
2304     /*===================*/
2305     ENGINE_HANDLE *handle,                   /*!< in: Engine Handle */
2306     const void *cookie,                      /*!< in: connection cookie */
2307     protocol_binary_request_header *request, /*!< in: request */
2308     ADD_RESPONSE response)                   /*!< out: respondse */
2309 {
2310   struct innodb_engine *innodb_eng = innodb_handle(handle);
2311   struct default_engine *def_eng = default_handle(innodb_eng);
2312 
2313   return (def_eng->engine.unknown_command(innodb_eng->default_engine, cookie,
2314                                           request, response));
2315 }
2316 
innodb_get_item_info(ENGINE_HANDLE * handle,const void * cookie,const item * item,item_info * item_info)2317 static bool innodb_get_item_info(ENGINE_HANDLE *handle, const void *cookie,
2318                                  const item *item, item_info *item_info) {
2319   struct innodb_engine *innodb_eng = innodb_handle(handle);
2320   innodb_conn_data_t *conn_data;
2321 
2322   conn_data =
2323       (innodb_conn_data_t *)innodb_eng->server.cookie->get_engine_specific(
2324           cookie);
2325 
2326   if (!conn_data || !conn_data->result_in_use) {
2327     hash_item *it;
2328 
2329     if (item_info->nvalue < 1) {
2330       return (false);
2331     }
2332 
2333     /* Use a hash item */
2334     it = (hash_item *)item;
2335     item_info->cas = hash_item_get_cas(it);
2336     item_info->exptime = it->exptime;
2337     item_info->nbytes = it->nbytes;
2338     item_info->flags = it->flags;
2339     item_info->clsid = it->slabs_clsid;
2340     item_info->nkey = it->nkey;
2341     item_info->nvalue = 1;
2342     item_info->key = hash_item_get_key(it);
2343     item_info->value[0].iov_base = hash_item_get_data(it);
2344     item_info->value[0].iov_len = it->nbytes;
2345   } else {
2346     mci_item_t *it;
2347 
2348     if (item_info->nvalue < 1) {
2349       return (false);
2350     }
2351 
2352     /* Use a hash item */
2353     it = (mci_item_t *)item;
2354     if (it->col_value[MCI_COL_CAS].is_valid) {
2355       item_info->cas = it->col_value[MCI_COL_CAS].value_int;
2356     } else {
2357       item_info->cas = 0;
2358     }
2359 
2360     if (it->col_value[MCI_COL_EXP].is_valid) {
2361       item_info->exptime = it->col_value[MCI_COL_EXP].value_int;
2362     } else {
2363       item_info->exptime = 0;
2364     }
2365 
2366     item_info->nbytes = it->col_value[MCI_COL_VALUE].value_len;
2367 
2368     if (it->col_value[MCI_COL_FLAG].is_valid) {
2369       item_info->flags = ntohl(it->col_value[MCI_COL_FLAG].value_int);
2370     } else {
2371       item_info->flags = 0;
2372     }
2373 
2374     item_info->clsid = 1;
2375 
2376     item_info->nkey = it->col_value[MCI_COL_KEY].value_len;
2377 
2378     item_info->nvalue = 1;
2379 
2380     item_info->key = it->col_value[MCI_COL_KEY].value_str;
2381 
2382     item_info->value[0].iov_base = it->col_value[MCI_COL_VALUE].value_str;
2383     ;
2384 
2385     item_info->value[0].iov_len = it->col_value[MCI_COL_VALUE].value_len;
2386   }
2387 
2388   return (true);
2389 }
2390 
2391 #ifdef UNIV_MEMCACHED_SDI
2392 /** Remove SDI entry from tablespace
2393 @param[in,out]	innodb_eng	innodb engine structure
2394 @param[in,out]	conn_data	innodb connection data
2395 @param[in,out]	err_ret		error code
2396 @param[in]	key		memcached key
2397 @param[in]	nkey		memcached key length
2398 @return true if key is SDI key else false */
innodb_sdi_remove(struct innodb_engine * innodb_eng,innodb_conn_data_t * conn_data,ENGINE_ERROR_CODE * err_ret,const void * key,const size_t nkey)2399 static bool innodb_sdi_remove(struct innodb_engine *innodb_eng,
2400                               innodb_conn_data_t *conn_data,
2401                               ENGINE_ERROR_CODE *err_ret, const void *key,
2402                               const size_t nkey) {
2403   if (!check_key_name_for_sdi(key, nkey, SDI_PREFIX)) {
2404     return (false);
2405   }
2406 
2407   ib_trx_t trx = conn_data->crsr_trx;
2408   ib_crsr_t crsr = nullptr;
2409   /* +2 for the '/' and trailing '\0' */
2410   char table_name[MAX_TABLE_NAME_LEN + MAX_DATABASE_NAME_LEN + 2];
2411   char *name;
2412   char *dbname;
2413   ib_err_t err;
2414 
2415   assert(nkey > 0);
2416 
2417   name = conn_data->conn_meta->col_info[CONTAINER_TABLE].col_name;
2418   dbname = conn_data->conn_meta->col_info[CONTAINER_DB].col_name;
2419 
2420   snprintf(table_name, sizeof(table_name), "%s/%s", dbname, name);
2421 
2422   err = innodb_cb_open_table(table_name, trx, &crsr);
2423 
2424   /* Mapped InnoDB table must be able to open */
2425   if (err != DB_SUCCESS) {
2426     fprintf(stderr,
2427             "InnoDB_Memcached: failed to open table"
2428             " '%s' \n",
2429             table_name);
2430     err = DB_ERROR;
2431   } else {
2432     err = ib_cb_memc_sdi_delete(crsr, (const char *)key);
2433   }
2434 
2435   ib_cb_cursor_close(crsr);
2436 
2437   if (err != DB_SUCCESS) {
2438     *err_ret = ENGINE_KEY_ENOENT;
2439   } else {
2440     *err_ret = ENGINE_SUCCESS;
2441   }
2442 
2443   innodb_api_cursor_reset(innodb_eng, conn_data, CONN_OP_DELETE,
2444                           *err_ret == ENGINE_SUCCESS);
2445 
2446   return (true);
2447 }
2448 
2449 /** Retrieve SDI for a given SDI key from tablespace
2450 @param[in,out]	conn_data	innodb connection data
2451 @param[in,out]	err_ret		error code
2452 @param[in]	key		memcached key
2453 @param[in]	nkey		memcached key length
2454 @param[in,out]	item		memcached item to fill
2455 @return true if key is SDI key else false */
innodb_sdi_get(innodb_conn_data_t * conn_data,ENGINE_ERROR_CODE * err_ret,const void * key,const size_t nkey,item *** item)2456 static bool innodb_sdi_get(innodb_conn_data_t *conn_data,
2457                            ENGINE_ERROR_CODE *err_ret, const void *key,
2458                            const size_t nkey, item ***item) {
2459   if (!check_key_name_for_sdi(key, nkey, SDI_PREFIX)) {
2460     return (false);
2461   }
2462 
2463   mci_item_t *result = (mci_item_t *)conn_data->result;
2464 
2465   ib_trx_t trx = conn_data->crsr_trx;
2466   ib_crsr_t crsr = nullptr;
2467 
2468   /* +2 for the '/' and trailing '\0' */
2469   char table_name[MAX_TABLE_NAME_LEN + MAX_DATABASE_NAME_LEN + 2];
2470   char *name;
2471   char *dbname;
2472   ib_err_t err;
2473 
2474   assert(nkey > 0);
2475 
2476   name = conn_data->conn_meta->col_info[CONTAINER_TABLE].col_name;
2477   dbname = conn_data->conn_meta->col_info[CONTAINER_DB].col_name;
2478 
2479   snprintf(table_name, sizeof(table_name), "%s/%s", dbname, name);
2480 
2481   err = innodb_cb_open_table(table_name, trx, &crsr);
2482 
2483   /* Mapped InnoDB table must be able to open */
2484   if (err != DB_SUCCESS) {
2485     fprintf(stderr,
2486             "InnoDB_Memcached: failed to open table"
2487             " '%s' \n",
2488             table_name);
2489 
2490     ib_cb_cursor_close(crsr);
2491     *err_ret = ENGINE_KEY_ENOENT;
2492     return (true);
2493   }
2494 
2495   uint64_t ret_len;
2496   if (check_key_name_for_sdi(key, nkey, SDI_CREATE_PREFIX)) {
2497     /* Create SDI Index in the tablespace */
2498     err = ib_cb_memc_sdi_create(crsr);
2499     ib_cb_cursor_close(crsr);
2500     *err_ret = ENGINE_KEY_ENOENT;
2501     return (true);
2502   }
2503 
2504   if (check_key_name_for_sdi(key, nkey, SDI_DROP_PREFIX)) {
2505     /* Create SDI Index in the tablespace */
2506     err = ib_cb_memc_sdi_drop(crsr);
2507     ib_cb_cursor_close(crsr);
2508     *err_ret = ENGINE_KEY_ENOENT;
2509     return (true);
2510   }
2511 
2512   if (check_key_name_for_sdi(key, nkey, SDI_LIST_PREFIX)) {
2513     if (conn_data->sdi_buf) {
2514       free(conn_data->sdi_buf);
2515     }
2516     conn_data->sdi_buf = malloc(SDI_LIST_BUF_MAX_LEN);
2517 
2518     err = ib_cb_memc_sdi_get_keys(crsr, (const char *)key,
2519                                   (char *)conn_data->sdi_buf,
2520                                   SDI_LIST_BUF_MAX_LEN);
2521     ret_len = strlen((char *)conn_data->sdi_buf);
2522   } else {
2523     /* Allocate memory of 64 KB, assuming SDI will fit into
2524     it. If retrieval fails, we will get actual length of SDI.
2525     We retry afer allocating the required memory */
2526     const uint32_t mem_size = 64 * 1024;
2527     void *new_mem = realloc(conn_data->sdi_buf, mem_size);
2528 
2529     if (new_mem == NULL) {
2530       free(conn_data->sdi_buf);
2531       conn_data->sdi_buf = NULL;
2532       *err_ret = ENGINE_KEY_ENOENT;
2533       ib_cb_cursor_close(crsr);
2534       return (true);
2535     }
2536 
2537     conn_data->sdi_buf = new_mem;
2538     ret_len = mem_size;
2539     err = ib_cb_memc_sdi_get(crsr, (const char *)key, conn_data->sdi_buf,
2540                              &ret_len);
2541 
2542     if (err == DB_SUCCESS) {
2543       assert(ret_len < mem_size);
2544     } else if (ret_len != UINT64_MAX) {
2545       /* Retry with required memory */
2546       void *new_mem = realloc(conn_data->sdi_buf, ret_len);
2547 
2548       if (new_mem == NULL) {
2549         free(conn_data->sdi_buf);
2550         conn_data->sdi_buf = NULL;
2551         *err_ret = ENGINE_KEY_ENOENT;
2552         ib_cb_cursor_close(crsr);
2553         return (true);
2554       }
2555 
2556       conn_data->sdi_buf = new_mem;
2557       err = ib_cb_memc_sdi_get(crsr, (const char *)key, conn_data->sdi_buf,
2558                                &ret_len);
2559     }
2560   }
2561 
2562   ib_cb_cursor_close(crsr);
2563 
2564   if (err != DB_SUCCESS) {
2565     *err_ret = ENGINE_KEY_ENOENT;
2566   } else {
2567     *err_ret = ENGINE_SUCCESS;
2568 
2569     memset(result, 0, sizeof(*result));
2570     result->col_value[MCI_COL_KEY].value_str = (char *)key;
2571     result->col_value[MCI_COL_KEY].value_len = nkey;
2572     result->col_value[MCI_COL_KEY].is_str = true;
2573     result->col_value[MCI_COL_KEY].is_valid = true;
2574 
2575     result->col_value[MCI_COL_VALUE].value_str = (char *)conn_data->sdi_buf;
2576     result->col_value[MCI_COL_VALUE].value_len = ret_len;
2577     result->col_value[MCI_COL_VALUE].is_str = true;
2578     result->col_value[MCI_COL_VALUE].is_valid = true;
2579 
2580     result->col_value[MCI_COL_CAS].is_null = true;
2581     result->col_value[MCI_COL_EXP].is_null = true;
2582     result->col_value[MCI_COL_FLAG].is_null = true;
2583     conn_data->result_in_use = true;
2584     **item = result;
2585   }
2586   return (true);
2587 }
2588 
2589 /** Store SDI entry into a tablespace
2590 @param[in,out]	innodb_eng	innodb engine structure
2591 @param[in,out]	conn_data	innodb connection data
2592 @param[in,out]	err_ret		error code
2593 @param[in]	value		memcached value
2594 @param[in]	value_len	memcached value length
2595 @param[in]	nkey		memcached key length
2596 @return true if key is SDI key else false */
innodb_sdi_store(struct innodb_engine * innodb_eng,innodb_conn_data_t * conn_data,ENGINE_ERROR_CODE * err_ret,char * value,uint32_t val_len,const size_t nkey)2597 static bool innodb_sdi_store(struct innodb_engine *innodb_eng,
2598                              innodb_conn_data_t *conn_data,
2599                              ENGINE_ERROR_CODE *err_ret, char *value,
2600                              uint32_t val_len, const size_t nkey) {
2601   if (!check_key_name_for_sdi(value, nkey, SDI_PREFIX)) {
2602     return (false);
2603   }
2604 
2605   ib_trx_t trx = conn_data->crsr_trx;
2606   ib_crsr_t crsr = nullptr;
2607 
2608   /* +2 for the '/' and trailing '\0' */
2609   char table_name[MAX_TABLE_NAME_LEN + MAX_DATABASE_NAME_LEN + 2];
2610   char *name;
2611   char *dbname;
2612 
2613   name = conn_data->conn_meta->col_info[CONTAINER_TABLE].col_name;
2614   dbname = conn_data->conn_meta->col_info[CONTAINER_DB].col_name;
2615 
2616   snprintf(table_name, sizeof(table_name), "%s/%s", dbname, name);
2617 
2618   ib_err_t err = innodb_cb_open_table(table_name, trx, &crsr);
2619 
2620   /* Mapped InnoDB table must be able to open */
2621   if (err != DB_SUCCESS) {
2622     fprintf(stderr,
2623             "InnoDB_Memcached: failed to open table"
2624             " '%s' \n",
2625             table_name);
2626   } else {
2627     uint64_t sdi_len = val_len;
2628     char *sdi = value + nkey;
2629     char key[100];
2630     /* Extract key from value */
2631     assert(nkey < 100);
2632     strncpy(key, value, nkey);
2633     key[nkey] = 0;
2634     err = ib_cb_memc_sdi_set(crsr, key, sdi, &sdi_len);
2635   }
2636 
2637   ib_cb_cursor_close(crsr);
2638 
2639   if (err != DB_SUCCESS) {
2640     *err_ret = ENGINE_NOT_STORED;
2641   } else {
2642     *err_ret = ENGINE_SUCCESS;
2643   }
2644   innodb_api_cursor_reset(innodb_eng, conn_data, CONN_OP_WRITE,
2645                           *err_ret == ENGINE_SUCCESS);
2646   return (true);
2647 }
2648 #endif /* UNIV_MEMCACHED_SDI */
2649