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