MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
fts0fts.cc
Go to the documentation of this file.
1 /*****************************************************************************
2 
3 Copyright (c) 2011, 2013, Oracle and/or its affiliates. All Rights Reserved.
4 
5 This program is free software; you can redistribute it and/or modify it under
6 the terms of the GNU General Public License as published by the Free Software
7 Foundation; version 2 of the License.
8 
9 This program is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 
13 You should have received a copy of the GNU General Public License along with
14 this program; if not, write to the Free Software Foundation, Inc.,
15 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
16 
17 *****************************************************************************/
18 
19 /**************************************************/
24 #include "trx0roll.h"
25 #include "row0mysql.h"
26 #include "row0upd.h"
27 #include "dict0types.h"
28 #include "row0sel.h"
29 
30 #include "fts0fts.h"
31 #include "fts0priv.h"
32 #include "fts0types.h"
33 
34 #include "fts0types.ic"
35 #include "fts0vlc.ic"
36 #include "dict0priv.h"
37 #include "dict0stats.h"
38 #include "btr0pcur.h"
39 
40 #include "ha_prototypes.h"
41 
42 #define FTS_MAX_ID_LEN 32
43 
45 #define FTS_MAX_CACHE_SIZE_IN_MB "cache_size_in_mb"
46 
49 UNIV_INTERN ulong fts_max_cache_size;
50 
53 UNIV_INTERN bool fts_need_sync = false;
54 
56 UNIV_INTERN ulong fts_max_total_cache_size;
57 
60 UNIV_INTERN ulong fts_result_cache_limit;
61 
63 UNIV_INTERN ulong fts_max_token_size;
64 
66 UNIV_INTERN ulong fts_min_token_size;
67 
68 
69 // FIXME: testing
70 ib_time_t elapsed_time = 0;
71 ulint n_nodes = 0;
72 
74 const ulint UTF8_ERROR = 0xFFFFFFFF;
75 
77 static const ulint FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB = 1;
78 
80 static const ulint FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB = 1024;
81 
83 static const ulint FTS_DEADLOCK_RETRY_WAIT = 100000;
84 
85 #ifdef UNIV_PFS_RWLOCK
86 UNIV_INTERN mysql_pfs_key_t fts_cache_rw_lock_key;
87 UNIV_INTERN mysql_pfs_key_t fts_cache_init_rw_lock_key;
88 #endif /* UNIV_PFS_RWLOCK */
89 
90 #ifdef UNIV_PFS_MUTEX
91 UNIV_INTERN mysql_pfs_key_t fts_delete_mutex_key;
92 UNIV_INTERN mysql_pfs_key_t fts_optimize_mutex_key;
93 UNIV_INTERN mysql_pfs_key_t fts_bg_threads_mutex_key;
94 UNIV_INTERN mysql_pfs_key_t fts_doc_id_mutex_key;
95 #endif /* UNIV_PFS_MUTEX */
96 
99 UNIV_INTERN char* fts_internal_tbl_name = NULL;
100 
107 const char *fts_default_stopword[] =
108 {
109  "a",
110  "about",
111  "an",
112  "are",
113  "as",
114  "at",
115  "be",
116  "by",
117  "com",
118  "de",
119  "en",
120  "for",
121  "from",
122  "how",
123  "i",
124  "in",
125  "is",
126  "it",
127  "la",
128  "of",
129  "on",
130  "or",
131  "that",
132  "the",
133  "this",
134  "to",
135  "was",
136  "what",
137  "when",
138  "where",
139  "who",
140  "will",
141  "with",
142  "und",
143  "the",
144  "www",
145  NULL
146 };
147 
150  table_id_t id;
151  table_id_t parent_id;
152  table_id_t index_id;
153  char* name;
154 };
155 
157 static const char* fts_create_common_tables_sql = {
158  "BEGIN\n"
159  ""
160  "CREATE TABLE \"%s_DELETED\" (\n"
161  " doc_id BIGINT UNSIGNED\n"
162  ") COMPACT;\n"
163  "CREATE UNIQUE CLUSTERED INDEX IND ON \"%s_DELETED\"(doc_id);\n"
164  ""
165  "CREATE TABLE \"%s_DELETED_CACHE\" (\n"
166  " doc_id BIGINT UNSIGNED\n"
167  ") COMPACT;\n"
168  "CREATE UNIQUE CLUSTERED INDEX IND "
169  "ON \"%s_DELETED_CACHE\"(doc_id);\n"
170  ""
171  "CREATE TABLE \"%s_BEING_DELETED\" (\n"
172  " doc_id BIGINT UNSIGNED\n"
173  ") COMPACT;\n"
174  "CREATE UNIQUE CLUSTERED INDEX IND "
175  "ON \"%s_BEING_DELETED\"(doc_id);\n"
176  ""
177  "CREATE TABLE \"%s_BEING_DELETED_CACHE\" (\n"
178  " doc_id BIGINT UNSIGNED\n"
179  ") COMPACT;\n"
180  "CREATE UNIQUE CLUSTERED INDEX IND "
181  "ON \"%s_BEING_DELETED_CACHE\"(doc_id);\n"
182  ""
183  "CREATE TABLE \"%s_CONFIG\" (\n"
184  " key CHAR(50),\n"
185  " value CHAR(50) NOT NULL\n"
186  ") COMPACT;\n"
187  "CREATE UNIQUE CLUSTERED INDEX IND ON \"%s_CONFIG\"(key);\n"
188 };
189 
190 #ifdef FTS_DOC_STATS_DEBUG
191 
193 static const char* fts_create_index_tables_sql = {
194  "BEGIN\n"
195  ""
196  "CREATE TABLE \"%s_DOC_ID\" (\n"
197  " doc_id BIGINT UNSIGNED,\n"
198  " word_count INTEGER UNSIGNED NOT NULL\n"
199  ") COMPACT;\n"
200  "CREATE UNIQUE CLUSTERED INDEX IND ON \"%s_DOC_ID\"(doc_id);\n"
201 };
202 #endif
203 
205 static const char* fts_create_index_sql = {
206  "BEGIN\n"
207  ""
208  "CREATE UNIQUE CLUSTERED INDEX FTS_INDEX_TABLE_IND "
209  "ON \"%s\"(word, first_doc_id);\n"
210 };
211 
213 static const char* fts_common_tables[] = {
214  "BEING_DELETED",
215  "BEING_DELETED_CACHE",
216  "CONFIG",
217  "DELETED",
218  "DELETED_CACHE",
219  NULL
220 };
221 
224  { 9, "INDEX_1" },
225  { 65, "INDEX_2" },
226  { 70, "INDEX_3" },
227  { 75, "INDEX_4" },
228  { 80, "INDEX_5" },
229  { 85, "INDEX_6" },
230  { 0 , NULL }
231 };
232 
234 static const char* fts_config_table_insert_values_sql =
235  "BEGIN\n"
236  "\n"
237  "INSERT INTO \"%s\" VALUES('"
238  FTS_MAX_CACHE_SIZE_IN_MB "', '256');\n"
239  ""
240  "INSERT INTO \"%s\" VALUES('"
241  FTS_OPTIMIZE_LIMIT_IN_SECS "', '180');\n"
242  ""
243  "INSERT INTO \"%s\" VALUES ('"
244  FTS_SYNCED_DOC_ID "', '0');\n"
245  ""
246  "INSERT INTO \"%s\" VALUES ('"
247  FTS_TOTAL_DELETED_COUNT "', '0');\n"
248  "" /* Note: 0 == FTS_TABLE_STATE_RUNNING */
249  "INSERT INTO \"%s\" VALUES ('"
250  FTS_TABLE_STATE "', '0');\n";
251 
252 /****************************************************************/
256 static
257 dberr_t
258 fts_sync(
259 /*=====*/
260  fts_sync_t* sync)
261  __attribute__((nonnull));
262 
263 /****************************************************************/
265 static
266 void
267 fts_words_free(
268 /*===========*/
269  ib_rbt_t* words)
270  __attribute__((nonnull));
271 #ifdef FTS_CACHE_SIZE_DEBUG
272 /****************************************************************/
274 static
275 void
276 fts_update_max_cache_size(
277 /*======================*/
278  fts_sync_t* sync);
279 #endif
280 
281 /*********************************************************************/
286 static
287 ulint
288 fts_add_doc_by_id(
289 /*==============*/
290  fts_trx_table_t*ftt,
291  doc_id_t doc_id,
292  ib_vector_t* fts_indexes __attribute__((unused)));
294 #ifdef FTS_DOC_STATS_DEBUG
295 /****************************************************************/
298 static
299 dberr_t
300 fts_is_word_in_index(
301 /*=================*/
302  trx_t* trx,
303  que_t** graph,
305  const fts_string_t* word,
306  ibool* found)
307  __attribute__((nonnull, warn_unused_result));
308 #endif /* FTS_DOC_STATS_DEBUG */
309 
310 /******************************************************************/
314 static
315 dberr_t
316 fts_update_sync_doc_id(
317 /*===================*/
318  const dict_table_t* table,
319  const char* table_name,
320  doc_id_t doc_id,
321  trx_t* trx)
322  __attribute__((nonnull(1)));
323 /********************************************************************
324 Check if we should stop. */
325 UNIV_INLINE
326 ibool
328 /*==================*/
329  fts_t* fts)
330 {
331  ibool stop_signalled = FALSE;
332 
333  mutex_enter(&fts->bg_threads_mutex);
334 
335  if (fts->fts_status & BG_THREAD_STOP) {
336 
337  stop_signalled = TRUE;
338  }
339 
340  mutex_exit(&fts->bg_threads_mutex);
341 
342  return(stop_signalled);
343 }
344 
345 /****************************************************************/
347 static
348 void
349 fts_load_default_stopword(
350 /*======================*/
351  fts_stopword_t* stopword_info)
352 {
353  fts_string_t str;
354  mem_heap_t* heap;
355  ib_alloc_t* allocator;
356  ib_rbt_t* stop_words;
357 
358  allocator = stopword_info->heap;
359  heap = static_cast<mem_heap_t*>(allocator->arg);
360 
361  if (!stopword_info->cached_stopword) {
362  /* For default stopword, we always use fts_utf8_string_cmp() */
363  stopword_info->cached_stopword = rbt_create(
365  }
366 
367  stop_words = stopword_info->cached_stopword;
368 
369  str.f_n_char = 0;
370 
371  for (ulint i = 0; fts_default_stopword[i]; ++i) {
372  char* word;
373  fts_tokenizer_word_t new_word;
374 
375  /* We are going to duplicate the value below. */
376  word = const_cast<char*>(fts_default_stopword[i]);
377 
378  new_word.nodes = ib_vector_create(
379  allocator, sizeof(fts_node_t), 4);
380 
381  str.f_len = ut_strlen(word);
382  str.f_str = reinterpret_cast<byte*>(word);
383 
384  fts_utf8_string_dup(&new_word.text, &str, heap);
385 
386  rbt_insert(stop_words, &new_word, &new_word);
387  }
388 
389  stopword_info->status = STOPWORD_FROM_DEFAULT;
390 }
391 
392 /****************************************************************/
395 static
396 ibool
397 fts_read_stopword(
398 /*==============*/
399  void* row,
400  void* user_arg)
401 {
402  ib_alloc_t* allocator;
403  fts_stopword_t* stopword_info;
404  sel_node_t* sel_node;
405  que_node_t* exp;
406  ib_rbt_t* stop_words;
407  dfield_t* dfield;
408  fts_string_t str;
409  mem_heap_t* heap;
410  ib_rbt_bound_t parent;
411 
412  sel_node = static_cast<sel_node_t*>(row);
413  stopword_info = static_cast<fts_stopword_t*>(user_arg);
414 
415  stop_words = stopword_info->cached_stopword;
416  allocator = static_cast<ib_alloc_t*>(stopword_info->heap);
417  heap = static_cast<mem_heap_t*>(allocator->arg);
418 
419  exp = sel_node->select_list;
420 
421  /* We only need to read the first column */
422  dfield = que_node_get_val(exp);
423 
424  str.f_n_char = 0;
425  str.f_str = static_cast<byte*>(dfield_get_data(dfield));
426  str.f_len = dfield_get_len(dfield);
427 
428  /* Only create new node if it is a value not already existed */
429  if (str.f_len != UNIV_SQL_NULL
430  && rbt_search(stop_words, &parent, &str) != 0) {
431 
432  fts_tokenizer_word_t new_word;
433 
434  new_word.nodes = ib_vector_create(
435  allocator, sizeof(fts_node_t), 4);
436 
437  new_word.text.f_str = static_cast<byte*>(
438  mem_heap_alloc(heap, str.f_len + 1));
439 
440  memcpy(new_word.text.f_str, str.f_str, str.f_len);
441 
442  new_word.text.f_n_char = 0;
443  new_word.text.f_len = str.f_len;
444  new_word.text.f_str[str.f_len] = 0;
445 
446  rbt_insert(stop_words, &new_word, &new_word);
447  }
448 
449  return(TRUE);
450 }
451 
452 /******************************************************************/
455 static
456 ibool
457 fts_load_user_stopword(
458 /*===================*/
459  fts_t* fts,
460  const char* stopword_table_name,
462  fts_stopword_t* stopword_info)
463 {
464  pars_info_t* info;
465  que_t* graph;
466  dberr_t error = DB_SUCCESS;
467  ibool ret = TRUE;
468  trx_t* trx;
469  ibool has_lock = fts->fts_status & TABLE_DICT_LOCKED;
470 
472  trx->op_info = "Load user stopword table into FTS cache";
473 
474  if (!has_lock) {
475  mutex_enter(&dict_sys->mutex);
476  }
477 
478  /* Validate the user table existence and in the right
479  format */
480  stopword_info->charset = fts_valid_stopword_table(stopword_table_name);
481  if (!stopword_info->charset) {
482  ret = FALSE;
483  goto cleanup;
484  } else if (!stopword_info->cached_stopword) {
485  /* Create the stopword RB tree with the stopword column
486  charset. All comparison will use this charset */
487  stopword_info->cached_stopword = rbt_create_arg_cmp(
489  stopword_info->charset);
490 
491  }
492 
493  info = pars_info_create();
494 
495  pars_info_bind_id(info, TRUE, "table_stopword", stopword_table_name);
496 
497  pars_info_bind_function(info, "my_func", fts_read_stopword,
498  stopword_info);
499 
501  NULL,
502  info,
503  "DECLARE FUNCTION my_func;\n"
504  "DECLARE CURSOR c IS"
505  " SELECT value "
506  " FROM $table_stopword;\n"
507  "BEGIN\n"
508  "\n"
509  "OPEN c;\n"
510  "WHILE 1 = 1 LOOP\n"
511  " FETCH c INTO my_func();\n"
512  " IF c % NOTFOUND THEN\n"
513  " EXIT;\n"
514  " END IF;\n"
515  "END LOOP;\n"
516  "CLOSE c;");
517 
518  for (;;) {
519  error = fts_eval_sql(trx, graph);
520 
521  if (error == DB_SUCCESS) {
522  fts_sql_commit(trx);
523  stopword_info->status = STOPWORD_USER_TABLE;
524  break;
525  } else {
526 
527  fts_sql_rollback(trx);
528 
529  ut_print_timestamp(stderr);
530 
531  if (error == DB_LOCK_WAIT_TIMEOUT) {
532  fprintf(stderr, " InnoDB: Warning: lock wait "
533  "timeout reading user stopword table. "
534  "Retrying!\n");
535 
536  trx->error_state = DB_SUCCESS;
537  } else {
538  fprintf(stderr, " InnoDB: Error '%s' "
539  "while reading user stopword table.\n",
540  ut_strerr(error));
541  ret = FALSE;
542  break;
543  }
544  }
545  }
546 
547  que_graph_free(graph);
548 
549 cleanup:
550  if (!has_lock) {
551  mutex_exit(&dict_sys->mutex);
552  }
553 
555  return(ret);
556 }
557 
558 /******************************************************************/
560 static
561 void
562 fts_index_cache_init(
563 /*=================*/
564  ib_alloc_t* allocator,
565  fts_index_cache_t* index_cache)
566 {
567  ulint i;
568 
569  ut_a(index_cache->words == NULL);
570 
571  index_cache->words = rbt_create_arg_cmp(
573  index_cache->charset);
574 
575  ut_a(index_cache->doc_stats == NULL);
576 
577  index_cache->doc_stats = ib_vector_create(
578  allocator, sizeof(fts_doc_stats_t), 4);
579 
580  for (i = 0; fts_index_selector[i].value; ++i) {
581  ut_a(index_cache->ins_graph[i] == NULL);
582  ut_a(index_cache->sel_graph[i] == NULL);
583  }
584 }
585 
586 /*********************************************************************/
588 UNIV_INTERN
589 void
591 /*===========*/
592  fts_cache_t* cache)
593 {
594  ulint i;
595 
596  /* Just to make sure */
597  ut_a(cache->sync_heap->arg == NULL);
598 
599  cache->sync_heap->arg = mem_heap_create(1024);
600 
601  cache->total_size = 0;
602 
603  cache->deleted_doc_ids = ib_vector_create(
604  cache->sync_heap, sizeof(fts_update_t), 4);
605 
606  /* Reset the cache data for all the FTS indexes. */
607  for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
608  fts_index_cache_t* index_cache;
609 
610  index_cache = static_cast<fts_index_cache_t*>(
611  ib_vector_get(cache->indexes, i));
612 
613  fts_index_cache_init(cache->sync_heap, index_cache);
614  }
615 }
616 
617 /****************************************************************/
619 UNIV_INTERN
622 /*=============*/
624 {
625  mem_heap_t* heap;
626  fts_cache_t* cache;
627 
628  heap = static_cast<mem_heap_t*>(mem_heap_create(512));
629 
630  cache = static_cast<fts_cache_t*>(
631  mem_heap_zalloc(heap, sizeof(*cache)));
632 
633  cache->cache_heap = heap;
634 
635  rw_lock_create(fts_cache_rw_lock_key, &cache->lock, SYNC_FTS_CACHE);
636 
638  fts_cache_init_rw_lock_key, &cache->init_lock,
639  SYNC_FTS_CACHE_INIT);
640 
641  mutex_create(
642  fts_delete_mutex_key, &cache->deleted_lock, SYNC_FTS_OPTIMIZE);
643 
644  mutex_create(
645  fts_optimize_mutex_key, &cache->optimize_lock,
646  SYNC_FTS_OPTIMIZE);
647 
648  mutex_create(
649  fts_doc_id_mutex_key, &cache->doc_id_lock, SYNC_FTS_OPTIMIZE);
650 
651  /* This is the heap used to create the cache itself. */
652  cache->self_heap = ib_heap_allocator_create(heap);
653 
654  /* This is a transient heap, used for storing sync data. */
655  cache->sync_heap = ib_heap_allocator_create(heap);
656  cache->sync_heap->arg = NULL;
657 
658  fts_need_sync = false;
659 
660  cache->sync = static_cast<fts_sync_t*>(
661  mem_heap_zalloc(heap, sizeof(fts_sync_t)));
662 
663  cache->sync->table = table;
664 
665  /* Create the index cache vector that will hold the inverted indexes. */
666  cache->indexes = ib_vector_create(
667  cache->self_heap, sizeof(fts_index_cache_t), 2);
668 
669  fts_cache_init(cache);
670 
671  cache->stopword_info.cached_stopword = NULL;
672  cache->stopword_info.charset = NULL;
673 
674  cache->stopword_info.heap = cache->self_heap;
675 
677 
678  return(cache);
679 }
680 
681 /*******************************************************************/
683 UNIV_INTERN
684 void
686 /*==========*/
689 {
690  fts_t* fts = table->fts;
691  fts_cache_t* cache;
692  fts_index_cache_t* index_cache;
693 
694  ut_ad(fts);
695  cache = table->fts->cache;
696 
697  rw_lock_x_lock(&cache->init_lock);
698 
699  ib_vector_push(fts->indexes, &index);
700 
701  index_cache = fts_find_index_cache(cache, index);
702 
703  if (!index_cache) {
704  /* Add new index cache structure */
705  index_cache = fts_cache_index_cache_create(table, index);
706  }
707 
708  rw_lock_x_unlock(&cache->init_lock);
709 }
710 
711 /*******************************************************************/
713 static
714 void
715 fts_reset_get_doc(
716 /*==============*/
717  fts_cache_t* cache)
718 {
719  fts_get_doc_t* get_doc;
720  ulint i;
721 
722 #ifdef UNIV_SYNC_DEBUG
723  ut_ad(rw_lock_own(&cache->init_lock, RW_LOCK_EX));
724 #endif
725  ib_vector_reset(cache->get_docs);
726 
727  for (i = 0; i < ib_vector_size(cache->indexes); i++) {
728  fts_index_cache_t* ind_cache;
729 
730  ind_cache = static_cast<fts_index_cache_t*>(
731  ib_vector_get(cache->indexes, i));
732 
733  get_doc = static_cast<fts_get_doc_t*>(
734  ib_vector_push(cache->get_docs, NULL));
735 
736  memset(get_doc, 0x0, sizeof(*get_doc));
737 
738  get_doc->index_cache = ind_cache;
739  }
740 
741  ut_ad(ib_vector_size(cache->get_docs)
742  == ib_vector_size(cache->indexes));
743 }
744 
745 /*******************************************************************/
748 static
749 ibool
750 fts_in_dict_index(
751 /*==============*/
753  dict_index_t* index_check)
754 {
756 
757  for (index = dict_table_get_first_index(table);
758  index != NULL;
759  index = dict_table_get_next_index(index)) {
760 
761  if (index == index_check) {
762  return(TRUE);
763  }
764  }
765 
766  return(FALSE);
767 }
768 
769 /*******************************************************************/
772 static
773 ibool
774 fts_in_index_cache(
775 /*===============*/
776  dict_table_t* table,
777  dict_index_t* index)
778 {
779  ulint i;
780 
781  for (i = 0; i < ib_vector_size(table->fts->cache->indexes); i++) {
782  fts_index_cache_t* index_cache;
783 
784  index_cache = static_cast<fts_index_cache_t*>(
785  ib_vector_get(table->fts->cache->indexes, i));
786 
787  if (index_cache->index == index) {
788  return(TRUE);
789  }
790  }
791 
792  return(FALSE);
793 }
794 
795 /*******************************************************************/
799 UNIV_INTERN
800 ibool
802 /*===================*/
803  dict_table_t* table)
804 {
805  ulint i;
806 
807  if (!table->fts || !table->fts->cache) {
808  return(TRUE);
809  }
810 
811  ut_a(ib_vector_size(table->fts->indexes)
812  == ib_vector_size(table->fts->cache->indexes));
813 
814  for (i = 0; i < ib_vector_size(table->fts->indexes); i++) {
816 
817  index = static_cast<dict_index_t*>(
818  ib_vector_getp(table->fts->indexes, i));
819 
820  if (!fts_in_index_cache(table, index)) {
821  return(FALSE);
822  }
823 
824  if (!fts_in_dict_index(table, index)) {
825  return(FALSE);
826  }
827  }
828 
829  return(TRUE);
830 }
831 
832 /*******************************************************************/
835 UNIV_INTERN
836 dberr_t
838 /*===========*/
839  dict_table_t* table,
840  dict_index_t* index,
841  trx_t* trx)
842 {
843  ib_vector_t* indexes = table->fts->indexes;
844  dberr_t err = DB_SUCCESS;
845 
846  ut_a(indexes);
847 
848  if ((ib_vector_size(indexes) == 1
849  && (index == static_cast<dict_index_t*>(
850  ib_vector_getp(table->fts->indexes, 0))))
851  || ib_vector_is_empty(indexes)) {
852  doc_id_t current_doc_id;
853  doc_id_t first_doc_id;
854 
855  /* If we are dropping the only FTS index of the table,
856  remove it from optimize thread */
858 
859  DICT_TF2_FLAG_UNSET(table, DICT_TF2_FTS);
860 
861  /* If Doc ID column is not added internally by FTS index,
862  we can drop all FTS auxiliary tables. Otherwise, we will
863  need to keep some common table such as CONFIG table, so
864  as to keep track of incrementing Doc IDs */
865  if (!DICT_TF2_FLAG_IS_SET(
866  table, DICT_TF2_FTS_HAS_DOC_ID)) {
867 
868  err = fts_drop_tables(trx, table);
869 
870  err = fts_drop_index_tables(trx, index);
871 
872  fts_free(table);
873 
874  return(err);
875  }
876 
877  current_doc_id = table->fts->cache->next_doc_id;
878  first_doc_id = table->fts->cache->first_doc_id;
879  fts_cache_clear(table->fts->cache, TRUE);
880  fts_cache_destroy(table->fts->cache);
881  table->fts->cache = fts_cache_create(table);
882  table->fts->cache->next_doc_id = current_doc_id;
883  table->fts->cache->first_doc_id = first_doc_id;
884  } else {
885  fts_cache_t* cache = table->fts->cache;
886  fts_index_cache_t* index_cache;
887 
888  rw_lock_x_lock(&cache->init_lock);
889 
890  index_cache = fts_find_index_cache(cache, index);
891 
892  if (index_cache->words) {
893  fts_words_free(index_cache->words);
894  rbt_free(index_cache->words);
895  }
896 
897  ib_vector_remove(cache->indexes, *(void**) index_cache);
898 
899  if (cache->get_docs) {
900  fts_reset_get_doc(cache);
901  }
902 
903  rw_lock_x_unlock(&cache->init_lock);
904  }
905 
906  err = fts_drop_index_tables(trx, index);
907 
908  ib_vector_remove(indexes, (const void*) index);
909 
910  return(err);
911 }
912 
913 /****************************************************************/
916 UNIV_INTERN
917 void
919 /*==========================*/
921  const fts_index_cache_t*index_cache,
922  que_t* graph)
923 {
924  ibool has_dict = FALSE;
925 
926  if (fts_table && fts_table->table) {
927  ut_ad(fts_table->table->fts);
928 
929  has_dict = fts_table->table->fts->fts_status
931  } else if (index_cache) {
932  ut_ad(index_cache->index->table->fts);
933 
934  has_dict = index_cache->index->table->fts->fts_status
936  }
937 
938  if (!has_dict) {
939  mutex_enter(&dict_sys->mutex);
940  }
941 
942  ut_ad(mutex_own(&dict_sys->mutex));
943 
944  que_graph_free(graph);
945 
946  if (!has_dict) {
947  mutex_exit(&dict_sys->mutex);
948  }
949 }
950 
951 /****************************************************************/
953 UNIV_INTERN
956 /*==================*/
957  dict_index_t* index)
958 {
959  CHARSET_INFO* charset = NULL;
960  dict_field_t* field;
961  ulint prtype;
962 
963  field = dict_index_get_nth_field(index, 0);
964  prtype = field->col->prtype;
965 
966  charset = innobase_get_fts_charset(
967  (int) (prtype & DATA_MYSQL_TYPE_MASK),
968  (uint) dtype_get_charset_coll(prtype));
969 
970 #ifdef FTS_DEBUG
971  /* Set up charset info for this index. Please note all
972  field of the FTS index should have the same charset */
973  for (i = 1; i < index->n_fields; i++) {
974  CHARSET_INFO* fld_charset;
975 
976  field = dict_index_get_nth_field(index, i);
977  prtype = field->col->prtype;
978 
979  fld_charset = innobase_get_fts_charset(
980  (int)(prtype & DATA_MYSQL_TYPE_MASK),
981  (uint) dtype_get_charset_coll(prtype));
982 
983  /* All FTS columns should have the same charset */
984  if (charset) {
985  ut_a(charset == fld_charset);
986  } else {
987  charset = fld_charset;
988  }
989  }
990 #endif
991 
992  return(charset);
993 
994 }
995 /****************************************************************/
998 UNIV_INTERN
1001 /*=========================*/
1002  dict_table_t* table,
1003  dict_index_t* index)
1004 {
1005  ulint n_bytes;
1006  fts_index_cache_t* index_cache;
1007  fts_cache_t* cache = table->fts->cache;
1008 
1009  ut_a(cache != NULL);
1010 
1011 #ifdef UNIV_SYNC_DEBUG
1012  ut_ad(rw_lock_own(&cache->init_lock, RW_LOCK_EX));
1013 #endif
1014 
1015  /* Must not already exist in the cache vector. */
1016  ut_a(fts_find_index_cache(cache, index) == NULL);
1017 
1018  index_cache = static_cast<fts_index_cache_t*>(
1019  ib_vector_push(cache->indexes, NULL));
1020 
1021  memset(index_cache, 0x0, sizeof(*index_cache));
1022 
1023  index_cache->index = index;
1024 
1025  index_cache->charset = fts_index_get_charset(index);
1026 
1027  n_bytes = sizeof(que_t*) * sizeof(fts_index_selector);
1028 
1029  index_cache->ins_graph = static_cast<que_t**>(
1030  mem_heap_zalloc(static_cast<mem_heap_t*>(
1031  cache->self_heap->arg), n_bytes));
1032 
1033  index_cache->sel_graph = static_cast<que_t**>(
1034  mem_heap_zalloc(static_cast<mem_heap_t*>(
1035  cache->self_heap->arg), n_bytes));
1036 
1037  fts_index_cache_init(cache->sync_heap, index_cache);
1038 
1039  if (cache->get_docs) {
1040  fts_reset_get_doc(cache);
1041  }
1042 
1043  return(index_cache);
1044 }
1045 
1046 /****************************************************************/
1048 static
1049 void
1050 fts_words_free(
1051 /*===========*/
1052  ib_rbt_t* words)
1053 {
1054  const ib_rbt_node_t* rbt_node;
1055 
1056  /* Free the resources held by a word. */
1057  for (rbt_node = rbt_first(words);
1058  rbt_node != NULL;
1059  rbt_node = rbt_first(words)) {
1060 
1061  ulint i;
1063 
1064  word = rbt_value(fts_tokenizer_word_t, rbt_node);
1065 
1066  /* Free the ilists of this word. */
1067  for (i = 0; i < ib_vector_size(word->nodes); ++i) {
1068 
1069  fts_node_t* fts_node = static_cast<fts_node_t*>(
1070  ib_vector_get(word->nodes, i));
1071 
1072  ut_free(fts_node->ilist);
1073  fts_node->ilist = NULL;
1074  }
1075 
1076  /* NOTE: We are responsible for free'ing the node */
1077  ut_free(rbt_remove_node(words, rbt_node));
1078  }
1079 }
1080 
1081 /*********************************************************************/
1085 UNIV_INTERN
1086 void
1088 /*============*/
1089  fts_cache_t* cache,
1090  ibool free_words)
1092 {
1093  ulint i;
1094 
1095  for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
1096  ulint j;
1097  fts_index_cache_t* index_cache;
1098 
1099  index_cache = static_cast<fts_index_cache_t*>(
1100  ib_vector_get(cache->indexes, i));
1101 
1102  if (free_words) {
1103  fts_words_free(index_cache->words);
1104  }
1105 
1106  ut_a(rbt_empty(index_cache->words));
1107 
1108  rbt_free(index_cache->words);
1109 
1110  index_cache->words = NULL;
1111 
1112  for (j = 0; fts_index_selector[j].value; ++j) {
1113 
1114  if (index_cache->ins_graph[j] != NULL) {
1115 
1117  NULL, index_cache,
1118  index_cache->ins_graph[j]);
1119 
1120  index_cache->ins_graph[j] = NULL;
1121  }
1122 
1123  if (index_cache->sel_graph[j] != NULL) {
1124 
1126  NULL, index_cache,
1127  index_cache->sel_graph[j]);
1128 
1129  index_cache->sel_graph[j] = NULL;
1130  }
1131  }
1132 
1133  index_cache->doc_stats = NULL;
1134  }
1135 
1136  mem_heap_free(static_cast<mem_heap_t*>(cache->sync_heap->arg));
1137  cache->sync_heap->arg = NULL;
1138 
1139  cache->total_size = 0;
1140  cache->deleted_doc_ids = NULL;
1141 }
1142 
1143 /*********************************************************************/
1146 UNIV_INLINE
1149 /*================*/
1150  fts_cache_t* cache,
1151  const dict_index_t* index)
1152 {
1153  ulint i;
1154 
1155 #ifdef UNIV_SYNC_DEBUG
1156  ut_ad(rw_lock_own((rw_lock_t*) &cache->lock, RW_LOCK_EX)
1157  || rw_lock_own((rw_lock_t*) &cache->init_lock, RW_LOCK_EX));
1158 #endif
1159 
1160  for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
1161  fts_index_cache_t* index_cache;
1162 
1163  index_cache = static_cast<fts_index_cache_t*>(
1164  ib_vector_get(cache->indexes, i));
1165 
1166  if (index_cache->index == index) {
1167 
1168  return(index_cache);
1169  }
1170  }
1171 
1172  return(NULL);
1173 }
1174 
1175 #ifdef FTS_DEBUG
1176 /*********************************************************************/
1179 static
1181 fts_get_index_get_doc(
1182 /*==================*/
1183  fts_cache_t* cache,
1184  const dict_index_t* index)
1185 {
1186  ulint i;
1187 
1188 #ifdef UNIV_SYNC_DEBUG
1189  ut_ad(rw_lock_own((rw_lock_t*) &cache->init_lock, RW_LOCK_EX));
1190 #endif
1191 
1192  for (i = 0; i < ib_vector_size(cache->get_docs); ++i) {
1193  fts_get_doc_t* get_doc;
1194 
1195  get_doc = static_cast<fts_get_doc_t*>(
1196  ib_vector_get(cache->get_docs, i));
1197 
1198  if (get_doc->index_cache->index == index) {
1199 
1200  return(get_doc);
1201  }
1202  }
1203 
1204  return(NULL);
1205 }
1206 #endif
1207 
1208 /**********************************************************************/
1210 UNIV_INTERN
1211 void
1213 /*==============*/
1214  fts_cache_t* cache)
1215 {
1216  rw_lock_free(&cache->lock);
1217  rw_lock_free(&cache->init_lock);
1218  mutex_free(&cache->optimize_lock);
1219  mutex_free(&cache->deleted_lock);
1220  mutex_free(&cache->doc_id_lock);
1221 
1222  if (cache->stopword_info.cached_stopword) {
1224  }
1225 
1226  if (cache->sync_heap->arg) {
1227  mem_heap_free(static_cast<mem_heap_t*>(cache->sync_heap->arg));
1228  }
1229 
1230  mem_heap_free(cache->cache_heap);
1231 }
1232 
1233 /**********************************************************************/
1236 static
1238 fts_tokenizer_word_get(
1239 /*===================*/
1240  fts_cache_t* cache,
1242  index_cache,
1243  fts_string_t* text)
1244 {
1246  ib_rbt_bound_t parent;
1247 
1248 #ifdef UNIV_SYNC_DEBUG
1249  ut_ad(rw_lock_own(&cache->lock, RW_LOCK_EX));
1250 #endif
1251 
1252  /* If it is a stopword, do not index it */
1254  &parent, text) == 0) {
1255 
1256  return(NULL);
1257  }
1258 
1259  /* Check if we found a match, if not then add word to tree. */
1260  if (rbt_search(index_cache->words, &parent, text) != 0) {
1261  mem_heap_t* heap;
1262  fts_tokenizer_word_t new_word;
1263 
1264  heap = static_cast<mem_heap_t*>(cache->sync_heap->arg);
1265 
1266  new_word.nodes = ib_vector_create(
1267  cache->sync_heap, sizeof(fts_node_t), 4);
1268 
1269  fts_utf8_string_dup(&new_word.text, text, heap);
1270 
1271  parent.last = rbt_add_node(
1272  index_cache->words, &parent, &new_word);
1273 
1274  /* Take into account the RB tree memory use and the vector. */
1275  cache->total_size += sizeof(new_word)
1276  + sizeof(ib_rbt_node_t)
1277  + text->f_len
1278  + (sizeof(fts_node_t) * 4)
1279  + sizeof(*new_word.nodes);
1280 
1281  ut_ad(rbt_validate(index_cache->words));
1282  }
1283 
1284  word = rbt_value(fts_tokenizer_word_t, parent.last);
1285 
1286  return(word);
1287 }
1288 
1289 /**********************************************************************/
1291 UNIV_INTERN
1292 void
1294 /*=========================*/
1295  fts_cache_t* cache,
1296  fts_node_t* node,
1297  doc_id_t doc_id,
1298  ib_vector_t* positions)
1299 {
1300  ulint i;
1301  byte* ptr;
1302  byte* ilist;
1303  ulint enc_len;
1304  ulint last_pos;
1305  byte* ptr_start;
1306  ulint doc_id_delta;
1307 
1308 #ifdef UNIV_SYNC_DEBUG
1309  if (cache) {
1310  ut_ad(rw_lock_own(&cache->lock, RW_LOCK_EX));
1311  }
1312 #endif
1313  ut_ad(doc_id >= node->last_doc_id);
1314 
1315  /* Calculate the space required to store the ilist. */
1316  doc_id_delta = (ulint)(doc_id - node->last_doc_id);
1317  enc_len = fts_get_encoded_len(doc_id_delta);
1318 
1319  last_pos = 0;
1320  for (i = 0; i < ib_vector_size(positions); i++) {
1321  ulint pos = *(static_cast<ulint*>(
1322  ib_vector_get(positions, i)));
1323 
1324  ut_ad(last_pos == 0 || pos > last_pos);
1325 
1326  enc_len += fts_get_encoded_len(pos - last_pos);
1327  last_pos = pos;
1328  }
1329 
1330  /* The 0x00 byte at the end of the token positions list. */
1331  enc_len++;
1332 
1333  if ((node->ilist_size_alloc - node->ilist_size) >= enc_len) {
1334  /* No need to allocate more space, we can fit in the new
1335  data at the end of the old one. */
1336  ilist = NULL;
1337  ptr = node->ilist + node->ilist_size;
1338  } else {
1339  ulint new_size = node->ilist_size + enc_len;
1340 
1341  /* Over-reserve space by a fixed size for small lengths and
1342  by 20% for lengths >= 48 bytes. */
1343  if (new_size < 16) {
1344  new_size = 16;
1345  } else if (new_size < 32) {
1346  new_size = 32;
1347  } else if (new_size < 48) {
1348  new_size = 48;
1349  } else {
1350  new_size = (ulint)(1.2 * new_size);
1351  }
1352 
1353  ilist = static_cast<byte*>(ut_malloc(new_size));
1354  ptr = ilist + node->ilist_size;
1355 
1356  node->ilist_size_alloc = new_size;
1357  }
1358 
1359  ptr_start = ptr;
1360 
1361  /* Encode the new fragment. */
1362  ptr += fts_encode_int(doc_id_delta, ptr);
1363 
1364  last_pos = 0;
1365  for (i = 0; i < ib_vector_size(positions); i++) {
1366  ulint pos = *(static_cast<ulint*>(
1367  ib_vector_get(positions, i)));
1368 
1369  ptr += fts_encode_int(pos - last_pos, ptr);
1370  last_pos = pos;
1371  }
1372 
1373  *ptr++ = 0;
1374 
1375  ut_a(enc_len == (ulint)(ptr - ptr_start));
1376 
1377  if (ilist) {
1378  /* Copy old ilist to the start of the new one and switch the
1379  new one into place in the node. */
1380  if (node->ilist_size > 0) {
1381  memcpy(ilist, node->ilist, node->ilist_size);
1382  ut_free(node->ilist);
1383  }
1384 
1385  node->ilist = ilist;
1386  }
1387 
1388  node->ilist_size += enc_len;
1389 
1390  if (cache) {
1391  cache->total_size += enc_len;
1392  }
1393 
1394  if (node->first_doc_id == FTS_NULL_DOC_ID) {
1395  node->first_doc_id = doc_id;
1396  }
1397 
1398  node->last_doc_id = doc_id;
1399  ++node->doc_count;
1400 }
1401 
1402 /**********************************************************************/
1404 static
1405 void
1406 fts_cache_add_doc(
1407 /*==============*/
1408  fts_cache_t* cache,
1410  index_cache,
1411  doc_id_t doc_id,
1412  ib_rbt_t* tokens)
1413 {
1414  const ib_rbt_node_t* node;
1415  ulint n_words;
1416  fts_doc_stats_t* doc_stats;
1417 
1418  if (!tokens) {
1419  return;
1420  }
1421 
1422 #ifdef UNIV_SYNC_DEBUG
1423  ut_ad(rw_lock_own(&cache->lock, RW_LOCK_EX));
1424 #endif
1425 
1426  n_words = rbt_size(tokens);
1427 
1428  for (node = rbt_first(tokens); node; node = rbt_first(tokens)) {
1429 
1431  fts_node_t* fts_node = NULL;
1432  fts_token_t* token = rbt_value(fts_token_t, node);
1433 
1434  /* Find and/or add token to the cache. */
1435  word = fts_tokenizer_word_get(
1436  cache, index_cache, &token->text);
1437 
1438  if (!word) {
1439  ut_free(rbt_remove_node(tokens, node));
1440  continue;
1441  }
1442 
1443  if (ib_vector_size(word->nodes) > 0) {
1444  fts_node = static_cast<fts_node_t*>(
1445  ib_vector_last(word->nodes));
1446  }
1447 
1448  if (fts_node == NULL
1449  || fts_node->ilist_size > FTS_ILIST_MAX_SIZE
1450  || doc_id < fts_node->last_doc_id) {
1451 
1452  fts_node = static_cast<fts_node_t*>(
1453  ib_vector_push(word->nodes, NULL));
1454 
1455  memset(fts_node, 0x0, sizeof(*fts_node));
1456 
1457  cache->total_size += sizeof(*fts_node);
1458  }
1459 
1461  cache, fts_node, doc_id, token->positions);
1462 
1463  ut_free(rbt_remove_node(tokens, node));
1464  }
1465 
1466  ut_a(rbt_empty(tokens));
1467 
1468  /* Add to doc ids processed so far. */
1469  doc_stats = static_cast<fts_doc_stats_t*>(
1470  ib_vector_push(index_cache->doc_stats, NULL));
1471 
1472  doc_stats->doc_id = doc_id;
1473  doc_stats->word_count = n_words;
1474 
1475  /* Add the doc stats memory usage too. */
1476  cache->total_size += sizeof(*doc_stats);
1477 
1478  if (doc_id > cache->sync->max_doc_id) {
1479  cache->sync->max_doc_id = doc_id;
1480  }
1481 }
1482 
1483 /****************************************************************/
1486 static __attribute__((nonnull, warn_unused_result))
1487 dberr_t
1488 fts_drop_table(
1489 /*===========*/
1490  trx_t* trx,
1491  const char* table_name)
1492 {
1494  dberr_t error = DB_SUCCESS;
1495 
1496  /* Check that the table exists in our data dictionary.
1497  Similar to regular drop table case, we will open table with
1498  DICT_ERR_IGNORE_INDEX_ROOT and DICT_ERR_IGNORE_CORRUPT option */
1499  table = dict_table_open_on_name(
1500  table_name, TRUE, FALSE,
1501  static_cast<dict_err_ignore_t>(
1503 
1504  if (table != 0) {
1505 
1506  dict_table_close(table, TRUE, FALSE);
1507 
1508  /* Pass nonatomic=false (dont allow data dict unlock),
1509  because the transaction may hold locks on SYS_* tables from
1510  previous calls to fts_drop_table(). */
1511  error = row_drop_table_for_mysql(table_name, trx, true, false);
1512 
1513  if (error != DB_SUCCESS) {
1514  ib_logf(IB_LOG_LEVEL_ERROR,
1515  "Unable to drop FTS index aux table %s: %s",
1516  table_name, ut_strerr(error));
1517  }
1518  } else {
1519  error = DB_FAIL;
1520  }
1521 
1522  return(error);
1523 }
1524 
1525 /****************************************************************/
1528 static __attribute__((nonnull, warn_unused_result))
1529 dberr_t
1530 fts_rename_one_aux_table(
1531 /*=====================*/
1532  const char* new_name,
1533  const char* fts_table_old_name,
1534  trx_t* trx)
1535 {
1536  char fts_table_new_name[MAX_TABLE_NAME_LEN];
1537  ulint new_db_name_len = dict_get_db_name_len(new_name);
1538  ulint old_db_name_len = dict_get_db_name_len(fts_table_old_name);
1539  ulint table_new_name_len = strlen(fts_table_old_name)
1540  + new_db_name_len - old_db_name_len;
1541 
1542  /* Check if the new and old database names are the same, if so,
1543  nothing to do */
1544  ut_ad((new_db_name_len != old_db_name_len)
1545  || strncmp(new_name, fts_table_old_name, old_db_name_len) != 0);
1546 
1547  /* Get the database name from "new_name", and table name
1548  from the fts_table_old_name */
1549  strncpy(fts_table_new_name, new_name, new_db_name_len);
1550  strncpy(fts_table_new_name + new_db_name_len,
1551  strchr(fts_table_old_name, '/'),
1552  table_new_name_len - new_db_name_len);
1553  fts_table_new_name[table_new_name_len] = 0;
1554 
1556  fts_table_old_name, fts_table_new_name, trx, false));
1557 }
1558 
1559 /****************************************************************/
1564 dberr_t
1566 /*==================*/
1567  dict_table_t* table,
1568  const char* new_name,
1569  trx_t* trx)
1570 {
1571  ulint i;
1573 
1574  FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table);
1575 
1576  /* Rename common auxiliary tables */
1577  for (i = 0; fts_common_tables[i] != NULL; ++i) {
1578  char* old_table_name;
1579  dberr_t err = DB_SUCCESS;
1580 
1581  fts_table.suffix = fts_common_tables[i];
1582 
1583  old_table_name = fts_get_table_name(&fts_table);
1584 
1585  err = fts_rename_one_aux_table(new_name, old_table_name, trx);
1586 
1587  mem_free(old_table_name);
1588 
1589  if (err != DB_SUCCESS) {
1590  return(err);
1591  }
1592  }
1593 
1594  fts_t* fts = table->fts;
1595 
1596  /* Rename index specific auxiliary tables */
1597  for (i = 0; fts->indexes != 0 && i < ib_vector_size(fts->indexes);
1598  ++i) {
1600 
1601  index = static_cast<dict_index_t*>(
1602  ib_vector_getp(fts->indexes, i));
1603 
1604  FTS_INIT_INDEX_TABLE(&fts_table, NULL, FTS_INDEX_TABLE, index);
1605 
1606  for (ulint j = 0; fts_index_selector[j].value; ++j) {
1607  dberr_t err;
1608  char* old_table_name;
1609 
1610  fts_table.suffix = fts_get_suffix(j);
1611 
1612  old_table_name = fts_get_table_name(&fts_table);
1613 
1614  err = fts_rename_one_aux_table(
1615  new_name, old_table_name, trx);
1616 
1617  DBUG_EXECUTE_IF("fts_rename_failure",
1618  err = DB_DEADLOCK;);
1619 
1620  mem_free(old_table_name);
1621 
1622  if (err != DB_SUCCESS) {
1623  return(err);
1624  }
1625  }
1626  }
1627 
1628  return(DB_SUCCESS);
1629 }
1630 
1631 /****************************************************************/
1636 static __attribute__((nonnull, warn_unused_result))
1637 dberr_t
1638 fts_drop_common_tables(
1639 /*===================*/
1640  trx_t* trx,
1641  fts_table_t* fts_table)
1643 {
1644  ulint i;
1645  dberr_t error = DB_SUCCESS;
1646 
1647  for (i = 0; fts_common_tables[i] != NULL; ++i) {
1648  dberr_t err;
1649  char* table_name;
1650 
1651  fts_table->suffix = fts_common_tables[i];
1652 
1653  table_name = fts_get_table_name(fts_table);
1654 
1655  err = fts_drop_table(trx, table_name);
1656 
1657  /* We only return the status of the last error. */
1658  if (err != DB_SUCCESS && err != DB_FAIL) {
1659  error = err;
1660  }
1661 
1662  mem_free(table_name);
1663  }
1664 
1665  return(error);
1666 }
1667 
1668 /****************************************************************/
1672 UNIV_INTERN
1673 dberr_t
1675 /*========================*/
1676  trx_t* trx,
1677  dict_index_t* index)
1679 {
1680  ulint i;
1682  dberr_t error = DB_SUCCESS;
1683 
1684  FTS_INIT_INDEX_TABLE(&fts_table, NULL, FTS_INDEX_TABLE, index);
1685 
1686  for (i = 0; fts_index_selector[i].value; ++i) {
1687  dberr_t err;
1688  char* table_name;
1689 
1690  fts_table.suffix = fts_get_suffix(i);
1691 
1692  table_name = fts_get_table_name(&fts_table);
1693 
1694  err = fts_drop_table(trx, table_name);
1695 
1696  /* We only return the status of the last error. */
1697  if (err != DB_SUCCESS && err != DB_FAIL) {
1698  error = err;
1699  }
1700 
1701  mem_free(table_name);
1702  }
1703 
1704  return(error);
1705 }
1706 
1707 /****************************************************************/
1710 UNIV_INTERN
1711 dberr_t
1713 /*==================*/
1714  trx_t* trx,
1715  dict_index_t* index)
1716 {
1717  dberr_t error = DB_SUCCESS;
1718 
1719 #ifdef FTS_DOC_STATS_DEBUG
1721  static const char* index_tables[] = {
1722  "DOC_ID",
1723  NULL
1724  };
1725 #endif /* FTS_DOC_STATS_DEBUG */
1726 
1727  dberr_t err = fts_drop_index_split_tables(trx, index);
1728 
1729  /* We only return the status of the last error. */
1730  if (err != DB_SUCCESS) {
1731  error = err;
1732  }
1733 
1734 #ifdef FTS_DOC_STATS_DEBUG
1735  FTS_INIT_INDEX_TABLE(&fts_table, NULL, FTS_INDEX_TABLE, index);
1736 
1737  for (ulint i = 0; index_tables[i] != NULL; ++i) {
1738  char* table_name;
1739 
1740  fts_table.suffix = index_tables[i];
1741 
1742  table_name = fts_get_table_name(&fts_table);
1743 
1744  err = fts_drop_table(trx, table_name);
1745 
1746  /* We only return the status of the last error. */
1747  if (err != DB_SUCCESS && err != DB_FAIL) {
1748  error = err;
1749  }
1750 
1751  mem_free(table_name);
1752  }
1753 #endif /* FTS_DOC_STATS_DEBUG */
1754 
1755  return(error);
1756 }
1757 
1758 /****************************************************************/
1763 static __attribute__((nonnull, warn_unused_result))
1764 dberr_t
1765 fts_drop_all_index_tables(
1766 /*======================*/
1767  trx_t* trx,
1768  fts_t* fts)
1769 {
1770  dberr_t error = DB_SUCCESS;
1771 
1772  for (ulint i = 0;
1773  fts->indexes != 0 && i < ib_vector_size(fts->indexes);
1774  ++i) {
1775 
1776  dberr_t err;
1778 
1779  index = static_cast<dict_index_t*>(
1780  ib_vector_getp(fts->indexes, i));
1781 
1782  err = fts_drop_index_tables(trx, index);
1783 
1784  if (err != DB_SUCCESS) {
1785  error = err;
1786  }
1787  }
1788 
1789  return(error);
1790 }
1791 
1792 /*********************************************************************/
1797 UNIV_INTERN
1798 dberr_t
1800 /*============*/
1801  trx_t* trx,
1802  dict_table_t* table)
1803 {
1804  dberr_t error;
1806 
1807  FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table);
1808 
1809  /* TODO: This is not atomic and can cause problems during recovery. */
1810 
1811  error = fts_drop_common_tables(trx, &fts_table);
1812 
1813  if (error == DB_SUCCESS) {
1814  error = fts_drop_all_index_tables(trx, table->fts);
1815  }
1816 
1817  return(error);
1818 }
1819 
1820 /*********************************************************************/
1823 static
1824 char*
1825 fts_prepare_sql(
1826 /*============*/
1827  fts_table_t* fts_table,
1828  const char* my_template)
1829 {
1830  char* sql;
1831  char* name_prefix;
1832 
1833  name_prefix = fts_get_table_name_prefix(fts_table);
1834  sql = ut_strreplace(my_template, "%s", name_prefix);
1835  mem_free(name_prefix);
1836 
1837  return(sql);
1838 }
1839 
1840 /*********************************************************************/
1845 UNIV_INTERN
1846 dberr_t
1848 /*=====================*/
1849  trx_t* trx,
1850  const dict_table_t* table,
1851  const char* name,
1852  bool skip_doc_id_index)
1853 {
1854  char* sql;
1855  dberr_t error;
1856  que_t* graph;
1858  mem_heap_t* heap = mem_heap_create(1024);
1859  pars_info_t* info;
1860 
1861  FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table);
1862 
1863  error = fts_drop_common_tables(trx, &fts_table);
1864 
1865  if (error != DB_SUCCESS) {
1866 
1867  goto func_exit;
1868  }
1869 
1870  /* Create the FTS tables that are common to an FTS index. */
1871  sql = fts_prepare_sql(&fts_table, fts_create_common_tables_sql);
1872  graph = fts_parse_sql_no_dict_lock(NULL, NULL, sql);
1873  mem_free(sql);
1874 
1875  error = fts_eval_sql(trx, graph);
1876 
1877  que_graph_free(graph);
1878 
1879  if (error != DB_SUCCESS) {
1880 
1881  goto func_exit;
1882  }
1883 
1884  /* Write the default settings to the config table. */
1885  fts_table.suffix = "CONFIG";
1887  &fts_table, NULL, fts_config_table_insert_values_sql);
1888 
1889  error = fts_eval_sql(trx, graph);
1890 
1891  que_graph_free(graph);
1892 
1893  if (error != DB_SUCCESS || skip_doc_id_index) {
1894 
1895  goto func_exit;
1896  }
1897 
1898  info = pars_info_create();
1899 
1900  pars_info_bind_id(info, TRUE, "table_name", name);
1901  pars_info_bind_id(info, TRUE, "index_name", FTS_DOC_ID_INDEX_NAME);
1902  pars_info_bind_id(info, TRUE, "doc_id_col_name", FTS_DOC_ID_COL_NAME);
1903 
1904  /* Create the FTS DOC_ID index on the hidden column. Currently this
1905  is common for any FT index created on the table. */
1907  NULL,
1908  info,
1910  heap,
1911  "BEGIN\n"
1912  ""
1913  "CREATE UNIQUE INDEX $index_name ON $table_name("
1914  "$doc_id_col_name);\n"));
1915 
1916  error = fts_eval_sql(trx, graph);
1917  que_graph_free(graph);
1918 
1919 func_exit:
1920  if (error != DB_SUCCESS) {
1921  /* We have special error handling here */
1922 
1923  trx->error_state = DB_SUCCESS;
1924 
1925  trx_rollback_to_savepoint(trx, NULL);
1926 
1927  row_drop_table_for_mysql(table->name, trx, FALSE);
1928 
1929  trx->error_state = DB_SUCCESS;
1930  }
1931 
1932  mem_heap_free(heap);
1933 
1934  return(error);
1935 }
1936 
1937 /*************************************************************/
1941 static
1942 dict_table_t*
1943 fts_create_one_index_table(
1944 /*=======================*/
1945  trx_t* trx,
1946  const dict_index_t*
1947  index,
1948  fts_table_t* fts_table,
1949  mem_heap_t* heap)
1950 {
1951  dict_field_t* field;
1952  dict_table_t* new_table = NULL;
1953  char* table_name = fts_get_table_name(fts_table);
1954  dberr_t error;
1955  CHARSET_INFO* charset;
1956 
1957  ut_ad(index->type & DICT_FTS);
1958 
1959  new_table = dict_mem_table_create(table_name, 0, 5, 1, 0);
1960 
1961  field = dict_index_get_nth_field(index, 0);
1962  charset = innobase_get_fts_charset(
1963  (int)(field->col->prtype & DATA_MYSQL_TYPE_MASK),
1964  (uint) dtype_get_charset_coll(field->col->prtype));
1965 
1966  if (strcmp(charset->name, "latin1_swedish_ci") == 0) {
1967  dict_mem_table_add_col(new_table, heap, "word", DATA_VARCHAR,
1968  field->col->prtype, FTS_MAX_WORD_LEN);
1969  } else {
1970  dict_mem_table_add_col(new_table, heap, "word", DATA_VARMYSQL,
1971  field->col->prtype, FTS_MAX_WORD_LEN);
1972  }
1973 
1974  dict_mem_table_add_col(new_table, heap, "first_doc_id", DATA_INT,
1975  DATA_NOT_NULL | DATA_UNSIGNED,
1976  sizeof(doc_id_t));
1977 
1978  dict_mem_table_add_col(new_table, heap, "last_doc_id", DATA_INT,
1979  DATA_NOT_NULL | DATA_UNSIGNED,
1980  sizeof(doc_id_t));
1981 
1982  dict_mem_table_add_col(new_table, heap, "doc_count", DATA_INT,
1983  DATA_NOT_NULL | DATA_UNSIGNED, 4);
1984 
1985  dict_mem_table_add_col(new_table, heap, "ilist", DATA_BLOB,
1986  4130048, 0);
1987 
1988  error = row_create_table_for_mysql(new_table, trx, true);
1989 
1990  if (error != DB_SUCCESS) {
1991  trx->error_state = error;
1992  dict_mem_table_free(new_table);
1993  new_table = NULL;
1994  ib_logf(IB_LOG_LEVEL_WARN,
1995  "Fail to create FTS index table %s", table_name);
1996  }
1997 
1998  mem_free(table_name);
1999 
2000  return(new_table);
2001 }
2002 
2003 /*************************************************************/
2007 UNIV_INTERN
2008 dberr_t
2010 /*========================*/
2011  trx_t* trx,
2012  const dict_index_t*
2013  index,
2014  const char* table_name,
2015  table_id_t table_id)
2017 {
2018  ulint i;
2019  que_t* graph;
2021  dberr_t error = DB_SUCCESS;
2022  mem_heap_t* heap = mem_heap_create(1024);
2023 
2024  fts_table.type = FTS_INDEX_TABLE;
2025  fts_table.index_id = index->id;
2026  fts_table.table_id = table_id;
2027  fts_table.parent = table_name;
2028  fts_table.table = NULL;
2029 
2030 #ifdef FTS_DOC_STATS_DEBUG
2031  char* sql;
2032 
2033  /* Create the FTS auxiliary tables that are specific
2034  to an FTS index. */
2035  sql = fts_prepare_sql(&fts_table, fts_create_index_tables_sql);
2036 
2037  graph = fts_parse_sql_no_dict_lock(NULL, NULL, sql);
2038  mem_free(sql);
2039 
2040  error = fts_eval_sql(trx, graph);
2041  que_graph_free(graph);
2042 #endif /* FTS_DOC_STATS_DEBUG */
2043 
2044  for (i = 0; fts_index_selector[i].value && error == DB_SUCCESS; ++i) {
2045  dict_table_t* new_table;
2046 
2047  /* Create the FTS auxiliary tables that are specific
2048  to an FTS index. We need to preserve the table_id %s
2049  which fts_parse_sql_no_dict_lock() will fill in for us. */
2050  fts_table.suffix = fts_get_suffix(i);
2051 
2052  new_table = fts_create_one_index_table(
2053  trx, index, &fts_table, heap);
2054 
2055  if (!new_table) {
2056  error = DB_FAIL;
2057  break;
2058  }
2059 
2061  &fts_table, NULL, fts_create_index_sql);
2062 
2063  error = fts_eval_sql(trx, graph);
2064  que_graph_free(graph);
2065  }
2066 
2067  if (error != DB_SUCCESS) {
2068  /* We have special error handling here */
2069 
2070  trx->error_state = DB_SUCCESS;
2071 
2072  trx_rollback_to_savepoint(trx, NULL);
2073 
2074  row_drop_table_for_mysql(table_name, trx, FALSE);
2075 
2076  trx->error_state = DB_SUCCESS;
2077  }
2078 
2079  mem_heap_free(heap);
2080 
2081  return(error);
2082 }
2083 
2084 /******************************************************************/
2089 UNIV_INTERN
2090 dberr_t
2092 /*====================*/
2093  trx_t* trx,
2094  const dict_index_t* index)
2095 {
2096  dberr_t err;
2098 
2099  table = dict_table_get_low(index->table_name);
2100  ut_a(table != NULL);
2101 
2102  err = fts_create_index_tables_low(trx, index, table->name, table->id);
2103 
2104  if (err == DB_SUCCESS) {
2105  trx_commit(trx);
2106  }
2107 
2108  return(err);
2109 }
2110 #if 0
2111 /******************************************************************/
2113 static
2114 const char*
2115 fts_get_state_str(
2116 /*==============*/
2117  /* out: string representation of state */
2118  fts_row_state state)
2119 {
2120  switch (state) {
2121  case FTS_INSERT:
2122  return("INSERT");
2123 
2124  case FTS_MODIFY:
2125  return("MODIFY");
2126 
2127  case FTS_DELETE:
2128  return("DELETE");
2129 
2130  case FTS_NOTHING:
2131  return("NOTHING");
2132 
2133  case FTS_INVALID:
2134  return("INVALID");
2135 
2136  default:
2137  return("UNKNOWN");
2138  }
2139 }
2140 #endif
2141 
2142 /******************************************************************/
2145 static
2147 fts_trx_row_get_new_state(
2148 /*======================*/
2149  fts_row_state old_state,
2151 {
2152  /* The rules for transforming states:
2153 
2154  I = inserted
2155  M = modified
2156  D = deleted
2157  N = nothing
2158 
2159  M+D -> D:
2160 
2161  If the row existed before the transaction started and it is modified
2162  during the transaction, followed by a deletion of the row, only the
2163  deletion will be signaled.
2164 
2165  M+ -> M:
2166 
2167  If the row existed before the transaction started and it is modified
2168  more than once during the transaction, only the last modification
2169  will be signaled.
2170 
2171  IM*D -> N:
2172 
2173  If a new row is added during the transaction (and possibly modified
2174  after its initial insertion) but it is deleted before the end of the
2175  transaction, nothing will be signaled.
2176 
2177  IM* -> I:
2178 
2179  If a new row is added during the transaction and modified after its
2180  initial insertion, only the addition will be signaled.
2181 
2182  M*DI -> M:
2183 
2184  If the row existed before the transaction started and it is deleted,
2185  then re-inserted, only a modification will be signaled. Note that
2186  this case is only possible if the table is using the row's primary
2187  key for FTS row ids, since those can be re-inserted by the user,
2188  which is not true for InnoDB generated row ids.
2189 
2190  It is easily seen that the above rules decompose such that we do not
2191  need to store the row's entire history of events. Instead, we can
2192  store just one state for the row and update that when new events
2193  arrive. Then we can implement the above rules as a two-dimensional
2194  look-up table, and get checking of invalid combinations "for free"
2195  in the process. */
2196 
2197  /* The lookup table for transforming states. old_state is the
2198  Y-axis, event is the X-axis. */
2199  static const fts_row_state table[4][4] = {
2200  /* I M D N */
2201  /* I */ { FTS_INVALID, FTS_INSERT, FTS_NOTHING, FTS_INVALID },
2202  /* M */ { FTS_INVALID, FTS_MODIFY, FTS_DELETE, FTS_INVALID },
2203  /* D */ { FTS_MODIFY, FTS_INVALID, FTS_INVALID, FTS_INVALID },
2204  /* N */ { FTS_INVALID, FTS_INVALID, FTS_INVALID, FTS_INVALID }
2205  };
2206 
2207  fts_row_state result;
2208 
2209  ut_a(old_state < FTS_INVALID);
2210  ut_a(event < FTS_INVALID);
2211 
2212  result = table[(int) old_state][(int) event];
2213  ut_a(result != FTS_INVALID);
2214 
2215  return(result);
2216 }
2217 
2218 /******************************************************************/
2221 static
2223 fts_savepoint_create(
2224 /*=================*/
2225  ib_vector_t* savepoints,
2226  const char* name,
2227  mem_heap_t* heap)
2228 {
2229  fts_savepoint_t* savepoint;
2230 
2231  savepoint = static_cast<fts_savepoint_t*>(
2232  ib_vector_push(savepoints, NULL));
2233 
2234  memset(savepoint, 0x0, sizeof(*savepoint));
2235 
2236  if (name) {
2237  savepoint->name = mem_heap_strdup(heap, name);
2238  }
2239 
2240  savepoint->tables = rbt_create(
2242 
2243  return(savepoint);
2244 }
2245 
2246 /******************************************************************/
2249 static
2250 fts_trx_t*
2251 fts_trx_create(
2252 /*===========*/
2253  trx_t* trx)
2254 {
2255  fts_trx_t* ftt;
2256  ib_alloc_t* heap_alloc;
2257  mem_heap_t* heap = mem_heap_create(1024);
2258 
2259  ftt = static_cast<fts_trx_t*>(mem_heap_alloc(heap, sizeof(fts_trx_t)));
2260  ftt->trx = trx;
2261  ftt->heap = heap;
2262 
2263  heap_alloc = ib_heap_allocator_create(heap);
2264 
2265  ftt->savepoints = static_cast<ib_vector_t*>(ib_vector_create(
2266  heap_alloc, sizeof(fts_savepoint_t), 4));
2267 
2268  ftt->last_stmt = static_cast<ib_vector_t*>(ib_vector_create(
2269  heap_alloc, sizeof(fts_savepoint_t), 4));
2270 
2271  /* Default instance has no name and no heap. */
2272  fts_savepoint_create(ftt->savepoints, NULL, NULL);
2273  fts_savepoint_create(ftt->last_stmt, NULL, NULL);
2274 
2275  return(ftt);
2276 }
2277 
2278 /******************************************************************/
2281 static
2283 fts_trx_table_create(
2284 /*=================*/
2285  fts_trx_t* fts_trx,
2286  dict_table_t* table)
2287 {
2288  fts_trx_table_t* ftt;
2289 
2290  ftt = static_cast<fts_trx_table_t*>(
2291  mem_heap_alloc(fts_trx->heap, sizeof(*ftt)));
2292 
2293  memset(ftt, 0x0, sizeof(*ftt));
2294 
2295  ftt->table = table;
2296  ftt->fts_trx = fts_trx;
2297 
2299 
2300  return(ftt);
2301 }
2302 
2303 /******************************************************************/
2306 static
2308 fts_trx_table_clone(
2309 /*=================*/
2310  const fts_trx_table_t* ftt_src)
2311 {
2312  fts_trx_table_t* ftt;
2313 
2314  ftt = static_cast<fts_trx_table_t*>(
2315  mem_heap_alloc(ftt_src->fts_trx->heap, sizeof(*ftt)));
2316 
2317  memset(ftt, 0x0, sizeof(*ftt));
2318 
2319  ftt->table = ftt_src->table;
2320  ftt->fts_trx = ftt_src->fts_trx;
2321 
2323 
2324  /* Copy the rb tree values to the new savepoint. */
2325  rbt_merge_uniq(ftt_src->rows, ftt->rows);
2326 
2327  /* These are only added on commit. At this stage we only have
2328  the updated row state. */
2329  ut_a(ftt_src->added_doc_ids == NULL);
2330 
2331  return(ftt);
2332 }
2333 
2334 /******************************************************************/
2337 static
2339 fts_trx_init(
2340 /*=========*/
2341  trx_t* trx,
2342  dict_table_t* table,
2343  ib_vector_t* savepoints)
2344 {
2345  fts_trx_table_t* ftt;
2346  ib_rbt_bound_t parent;
2347  ib_rbt_t* tables;
2348  fts_savepoint_t* savepoint;
2349 
2350  savepoint = static_cast<fts_savepoint_t*>(ib_vector_last(savepoints));
2351 
2352  tables = savepoint->tables;
2353  rbt_search_cmp(tables, &parent, &table->id, fts_trx_table_id_cmp, NULL);
2354 
2355  if (parent.result == 0) {
2356  fts_trx_table_t** fttp;
2357 
2358  fttp = rbt_value(fts_trx_table_t*, parent.last);
2359  ftt = *fttp;
2360  } else {
2361  ftt = fts_trx_table_create(trx->fts_trx, table);
2362  rbt_add_node(tables, &parent, &ftt);
2363  }
2364 
2365  ut_a(ftt->table == table);
2366 
2367  return(ftt);
2368 }
2369 
2370 /******************************************************************/
2372 static
2373 void
2374 fts_trx_table_add_op(
2375 /*=================*/
2376  fts_trx_table_t*ftt,
2377  doc_id_t doc_id,
2378  fts_row_state state,
2379  ib_vector_t* fts_indexes)
2380 {
2381  ib_rbt_t* rows;
2382  ib_rbt_bound_t parent;
2383 
2384  rows = ftt->rows;
2385  rbt_search(rows, &parent, &doc_id);
2386 
2387  /* Row id found, update state, and if new state is FTS_NOTHING,
2388  we delete the row from our tree. */
2389  if (parent.result == 0) {
2390  fts_trx_row_t* row = rbt_value(fts_trx_row_t, parent.last);
2391 
2392  row->state = fts_trx_row_get_new_state(row->state, state);
2393 
2394  if (row->state == FTS_NOTHING) {
2395  if (row->fts_indexes) {
2396  ib_vector_free(row->fts_indexes);
2397  }
2398 
2399  ut_free(rbt_remove_node(rows, parent.last));
2400  row = NULL;
2401  } else if (row->fts_indexes != NULL) {
2402  ib_vector_free(row->fts_indexes);
2403  row->fts_indexes = fts_indexes;
2404  }
2405 
2406  } else { /* Row-id not found, create a new one. */
2407  fts_trx_row_t row;
2408 
2409  row.doc_id = doc_id;
2410  row.state = state;
2411  row.fts_indexes = fts_indexes;
2412 
2413  rbt_add_node(rows, &parent, &row);
2414  }
2415 }
2416 
2417 /******************************************************************/
2419 UNIV_INTERN
2420 void
2422 /*===========*/
2423  trx_t* trx,
2424  dict_table_t* table,
2425  doc_id_t doc_id,
2426  fts_row_state state,
2427  ib_vector_t* fts_indexes)
2429 {
2430  fts_trx_table_t* tran_ftt;
2431  fts_trx_table_t* stmt_ftt;
2432 
2433  if (!trx->fts_trx) {
2434  trx->fts_trx = fts_trx_create(trx);
2435  }
2436 
2437  tran_ftt = fts_trx_init(trx, table, trx->fts_trx->savepoints);
2438  stmt_ftt = fts_trx_init(trx, table, trx->fts_trx->last_stmt);
2439 
2440  fts_trx_table_add_op(tran_ftt, doc_id, state, fts_indexes);
2441  fts_trx_table_add_op(stmt_ftt, doc_id, state, fts_indexes);
2442 }
2443 
2444 /******************************************************************/
2448 static
2449 ibool
2450 fts_fetch_store_doc_id(
2451 /*===================*/
2452  void* row,
2453  void* user_arg)
2455 {
2456  int n_parsed;
2457  sel_node_t* node = static_cast<sel_node_t*>(row);
2458  doc_id_t* doc_id = static_cast<doc_id_t*>(user_arg);
2459  dfield_t* dfield = que_node_get_val(node->select_list);
2460  dtype_t* type = dfield_get_type(dfield);
2461  ulint len = dfield_get_len(dfield);
2462 
2463  char buf[32];
2464 
2465  ut_a(dtype_get_mtype(type) == DATA_VARCHAR);
2466  ut_a(len > 0 && len < sizeof(buf));
2467 
2468  memcpy(buf, dfield_get_data(dfield), len);
2469  buf[len] = '\0';
2470 
2471  n_parsed = sscanf(buf, FTS_DOC_ID_FORMAT, doc_id);
2472  ut_a(n_parsed == 1);
2473 
2474  return(FALSE);
2475 }
2476 
2477 #ifdef FTS_CACHE_SIZE_DEBUG
2478 /******************************************************************/
2483 static
2484 ulint
2485 fts_get_max_cache_size(
2486 /*===================*/
2487  trx_t* trx,
2488  fts_table_t* fts_table)
2489 {
2490  dberr_t error;
2491  fts_string_t value;
2492  ulint cache_size_in_mb;
2493 
2494  /* Set to the default value. */
2495  cache_size_in_mb = FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB;
2496 
2497  /* We set the length of value to the max bytes it can hold. This
2498  information is used by the callback that reads the value. */
2499  value.f_n_char = 0;
2501  value.f_str = ut_malloc(value.f_len + 1);
2502 
2503  error = fts_config_get_value(
2504  trx, fts_table, FTS_MAX_CACHE_SIZE_IN_MB, &value);
2505 
2506  if (error == DB_SUCCESS) {
2507 
2508  value.f_str[value.f_len] = 0;
2509  cache_size_in_mb = strtoul((char*) value.f_str, NULL, 10);
2510 
2511  if (cache_size_in_mb > FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB) {
2512 
2513  ut_print_timestamp(stderr);
2514  fprintf(stderr, " InnoDB: Warning: FTS max cache size "
2515  " (%lu) out of range. Minimum value is "
2516  "%luMB and the maximum values is %luMB, "
2517  "setting cache size to upper limit\n",
2518  cache_size_in_mb,
2519  FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB,
2520  FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB);
2521 
2522  cache_size_in_mb = FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB;
2523 
2524  } else if (cache_size_in_mb
2525  < FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB) {
2526 
2527  ut_print_timestamp(stderr);
2528  fprintf(stderr, " InnoDB: Warning: FTS max cache size "
2529  " (%lu) out of range. Minimum value is "
2530  "%luMB and the maximum values is %luMB, "
2531  "setting cache size to lower limit\n",
2532  cache_size_in_mb,
2533  FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB,
2534  FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB);
2535 
2536  cache_size_in_mb = FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB;
2537  }
2538  } else {
2539  ut_print_timestamp(stderr);
2540  fprintf(stderr, "InnoDB: Error: (%lu) reading max cache "
2541  "config value from config table\n", error);
2542  }
2543 
2544  ut_free(value.f_str);
2545 
2546  return(cache_size_in_mb * 1024 * 1024);
2547 }
2548 #endif
2549 
2550 #ifdef FTS_DOC_STATS_DEBUG
2551 /*********************************************************************/
2554 UNIV_INTERN
2555 dberr_t
2556 fts_get_total_word_count(
2557 /*=====================*/
2558  trx_t* trx,
2559  dict_index_t* index,
2560  ulint* total) /* out: total words */
2561 {
2562  dberr_t error;
2563  fts_string_t value;
2564 
2565  *total = 0;
2566 
2567  /* We set the length of value to the max bytes it can hold. This
2568  information is used by the callback that reads the value. */
2569  value.f_n_char = 0;
2571  value.f_str = static_cast<byte*>(ut_malloc(value.f_len + 1));
2572 
2574  trx, index, FTS_TOTAL_WORD_COUNT, &value);
2575 
2576  if (error == DB_SUCCESS) {
2577 
2578  value.f_str[value.f_len] = 0;
2579  *total = strtoul((char*) value.f_str, NULL, 10);
2580  } else {
2581  ut_print_timestamp(stderr);
2582  fprintf(stderr, " InnoDB: Error: (%s) reading total words "
2583  "value from config table\n", ut_strerr(error));
2584  }
2585 
2586  ut_free(value.f_str);
2587 
2588  return(error);
2589 }
2590 #endif /* FTS_DOC_STATS_DEBUG */
2591 
2592 /*********************************************************************/
2596 UNIV_INTERN
2597 void
2599 /*===================*/
2600  trx_t* trx,
2601  const dict_table_t* table,
2602  const char* table_name,
2603  doc_id_t doc_id)
2604 {
2605  table->fts->cache->synced_doc_id = doc_id;
2606  table->fts->cache->next_doc_id = doc_id + 1;
2607 
2608  table->fts->cache->first_doc_id = table->fts->cache->next_doc_id;
2609 
2610  fts_update_sync_doc_id(
2611  table, table_name, table->fts->cache->synced_doc_id, trx);
2612 
2613 }
2614 
2615 /*********************************************************************/
2618 UNIV_INTERN
2619 dberr_t
2621 /*================*/
2622  const dict_table_t* table,
2623  doc_id_t* doc_id)
2624 {
2625  fts_cache_t* cache = table->fts->cache;
2626 
2627  /* If the Doc ID system has not yet been initialized, we
2628  will consult the CONFIG table and user table to re-establish
2629  the initial value of the Doc ID */
2630 
2631  if (cache->first_doc_id != 0 || !fts_init_doc_id(table)) {
2632  if (!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID)) {
2633  *doc_id = FTS_NULL_DOC_ID;
2634  return(DB_SUCCESS);
2635  }
2636 
2637  /* Otherwise, simply increment the value in cache */
2638  mutex_enter(&cache->doc_id_lock);
2639  *doc_id = ++cache->next_doc_id;
2640  mutex_exit(&cache->doc_id_lock);
2641  } else {
2642  mutex_enter(&cache->doc_id_lock);
2643  *doc_id = cache->next_doc_id;
2644  mutex_exit(&cache->doc_id_lock);
2645  }
2646 
2647  return(DB_SUCCESS);
2648 }
2649 
2650 /*********************************************************************/
2654 static __attribute__((nonnull))
2655 dberr_t
2656 fts_cmp_set_sync_doc_id(
2657 /*====================*/
2658  const dict_table_t* table,
2659  doc_id_t doc_id_cmp,
2660  ibool read_only,
2662  doc_id_t* doc_id)
2666 {
2667  trx_t* trx;
2668  pars_info_t* info;
2669  dberr_t error;
2671  que_t* graph = NULL;
2672  fts_cache_t* cache = table->fts->cache;
2673 retry:
2674  ut_a(table->fts->doc_col != ULINT_UNDEFINED);
2675 
2676  fts_table.suffix = "CONFIG";
2677  fts_table.table_id = table->id;
2678  fts_table.type = FTS_COMMON_TABLE;
2679  fts_table.table = table;
2680 
2681  fts_table.parent = table->name;
2682 
2684 
2685  trx->op_info = "update the next FTS document id";
2686 
2687  info = pars_info_create();
2688 
2690  info, "my_func", fts_fetch_store_doc_id, doc_id);
2691 
2692  graph = fts_parse_sql(
2693  &fts_table, info,
2694  "DECLARE FUNCTION my_func;\n"
2695  "DECLARE CURSOR c IS SELECT value FROM \"%s\""
2696  " WHERE key = 'synced_doc_id' FOR UPDATE;\n"
2697  "BEGIN\n"
2698  ""
2699  "OPEN c;\n"
2700  "WHILE 1 = 1 LOOP\n"
2701  " FETCH c INTO my_func();\n"
2702  " IF c % NOTFOUND THEN\n"
2703  " EXIT;\n"
2704  " END IF;\n"
2705  "END LOOP;\n"
2706  "CLOSE c;");
2707 
2708  *doc_id = 0;
2709 
2710  error = fts_eval_sql(trx, graph);
2711 
2712  fts_que_graph_free_check_lock(&fts_table, NULL, graph);
2713 
2714  // FIXME: We need to retry deadlock errors
2715  if (error != DB_SUCCESS) {
2716  goto func_exit;
2717  }
2718 
2719  if (read_only) {
2720  goto func_exit;
2721  }
2722 
2723  if (doc_id_cmp == 0 && *doc_id) {
2724  cache->synced_doc_id = *doc_id - 1;
2725  } else {
2726  cache->synced_doc_id = ut_max(doc_id_cmp, *doc_id);
2727  }
2728 
2729  mutex_enter(&cache->doc_id_lock);
2730  /* For each sync operation, we will add next_doc_id by 1,
2731  so to mark a sync operation */
2732  if (cache->next_doc_id < cache->synced_doc_id + 1) {
2733  cache->next_doc_id = cache->synced_doc_id + 1;
2734  }
2735  mutex_exit(&cache->doc_id_lock);
2736 
2737  if (doc_id_cmp > *doc_id) {
2738  error = fts_update_sync_doc_id(
2739  table, table->name, cache->synced_doc_id, trx);
2740  }
2741 
2742  *doc_id = cache->next_doc_id;
2743 
2744 func_exit:
2745 
2746  if (error == DB_SUCCESS) {
2747  fts_sql_commit(trx);
2748  } else {
2749  *doc_id = 0;
2750 
2751  ut_print_timestamp(stderr);
2752  fprintf(stderr, " InnoDB: Error: (%s) "
2753  "while getting next doc id.\n", ut_strerr(error));
2754 
2755  fts_sql_rollback(trx);
2756 
2757  if (error == DB_DEADLOCK) {
2758  os_thread_sleep(FTS_DEADLOCK_RETRY_WAIT);
2759  goto retry;
2760  }
2761  }
2762 
2764 
2765  return(error);
2766 }
2767 
2768 /*********************************************************************/
2772 static
2773 dberr_t
2774 fts_update_sync_doc_id(
2775 /*===================*/
2776  const dict_table_t* table,
2777  const char* table_name,
2778  doc_id_t doc_id,
2779  trx_t* trx)
2780 {
2781  byte id[FTS_MAX_ID_LEN];
2782  pars_info_t* info;
2784  ulint id_len;
2785  que_t* graph = NULL;
2786  dberr_t error;
2787  ibool local_trx = FALSE;
2788  fts_cache_t* cache = table->fts->cache;
2789 
2790  fts_table.suffix = "CONFIG";
2791  fts_table.table_id = table->id;
2792  fts_table.type = FTS_COMMON_TABLE;
2793  fts_table.table = table;
2794  if (table_name) {
2795  fts_table.parent = table_name;
2796  } else {
2797  fts_table.parent = table->name;
2798  }
2799 
2800  if (!trx) {
2802 
2803  trx->op_info = "setting last FTS document id";
2804  local_trx = TRUE;
2805  }
2806 
2807  info = pars_info_create();
2808 
2809  id_len = ut_snprintf(
2810  (char*) id, sizeof(id), FTS_DOC_ID_FORMAT, doc_id + 1);
2811 
2812  pars_info_bind_varchar_literal(info, "doc_id", id, id_len);
2813 
2814  graph = fts_parse_sql(
2815  &fts_table, info,
2816  "BEGIN "
2817  "UPDATE %s SET value = :doc_id"
2818  " WHERE key = 'synced_doc_id';");
2819 
2820  error = fts_eval_sql(trx, graph);
2821 
2822  fts_que_graph_free_check_lock(&fts_table, NULL, graph);
2823 
2824  if (local_trx) {
2825  if (error == DB_SUCCESS) {
2826  fts_sql_commit(trx);
2827  cache->synced_doc_id = doc_id;
2828  } else {
2829 
2830  ib_logf(IB_LOG_LEVEL_ERROR,
2831  "(%s) while updating last doc id.",
2832  ut_strerr(error));
2833 
2834  fts_sql_rollback(trx);
2835  }
2837  }
2838 
2839  return(error);
2840 }
2841 
2842 /*********************************************************************/
2845 UNIV_INTERN
2847 fts_doc_ids_create(void)
2848 /*====================*/
2849 {
2850  fts_doc_ids_t* fts_doc_ids;
2851  mem_heap_t* heap = mem_heap_create(512);
2852 
2853  fts_doc_ids = static_cast<fts_doc_ids_t*>(
2854  mem_heap_alloc(heap, sizeof(*fts_doc_ids)));
2855 
2856  fts_doc_ids->self_heap = ib_heap_allocator_create(heap);
2857 
2858  fts_doc_ids->doc_ids = static_cast<ib_vector_t*>(ib_vector_create(
2859  fts_doc_ids->self_heap, sizeof(fts_update_t), 32));
2860 
2861  return(fts_doc_ids);
2862 }
2863 
2864 /*********************************************************************/
2867 void
2869 /*=============*/
2870  fts_doc_ids_t* fts_doc_ids)
2871 {
2872  mem_heap_t* heap = static_cast<mem_heap_t*>(
2873  fts_doc_ids->self_heap->arg);
2874 
2875  memset(fts_doc_ids, 0, sizeof(*fts_doc_ids));
2876 
2877  mem_heap_free(heap);
2878 }
2879 
2880 /*********************************************************************/
2883 static __attribute__((nonnull, warn_unused_result))
2884 dberr_t
2885 fts_add(
2886 /*====*/
2887  fts_trx_table_t*ftt,
2888  fts_trx_row_t* row)
2889 {
2890  dict_table_t* table = ftt->table;
2891  dberr_t error = DB_SUCCESS;
2892  doc_id_t doc_id = row->doc_id;
2893 
2894  ut_a(row->state == FTS_INSERT || row->state == FTS_MODIFY);
2895 
2896  fts_add_doc_by_id(ftt, doc_id, row->fts_indexes);
2897 
2898  if (error == DB_SUCCESS) {
2899  mutex_enter(&table->fts->cache->deleted_lock);
2900  ++table->fts->cache->added;
2901  mutex_exit(&table->fts->cache->deleted_lock);
2902 
2903  if (!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID)
2904  && doc_id >= table->fts->cache->next_doc_id) {
2905  table->fts->cache->next_doc_id = doc_id + 1;
2906  }
2907  }
2908 
2909  return(error);
2910 }
2911 
2912 /*********************************************************************/
2915 static __attribute__((nonnull, warn_unused_result))
2916 dberr_t
2917 fts_delete(
2918 /*=======*/
2919  fts_trx_table_t*ftt,
2920  fts_trx_row_t* row)
2921 {
2922  que_t* graph;
2924  dberr_t error = DB_SUCCESS;
2925  doc_id_t write_doc_id;
2926  dict_table_t* table = ftt->table;
2927  doc_id_t doc_id = row->doc_id;
2928  trx_t* trx = ftt->fts_trx->trx;
2929  pars_info_t* info = pars_info_create();
2930  fts_cache_t* cache = table->fts->cache;
2931 
2932  /* we do not index Documents whose Doc ID value is 0 */
2933  if (doc_id == FTS_NULL_DOC_ID) {
2934  ut_ad(!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID));
2935  return(error);
2936  }
2937 
2938  ut_a(row->state == FTS_DELETE || row->state == FTS_MODIFY);
2939 
2940  FTS_INIT_FTS_TABLE(&fts_table, "DELETED", FTS_COMMON_TABLE, table);
2941 
2942  /* Convert to "storage" byte order. */
2943  fts_write_doc_id((byte*) &write_doc_id, doc_id);
2944  fts_bind_doc_id(info, "doc_id", &write_doc_id);
2945 
2946  /* It is possible we update a record that has not yet been sync-ed
2947  into cache from last crash (delete Doc will not initialize the
2948  sync). Avoid any added counter accounting until the FTS cache
2949  is re-established and sync-ed */
2950  if (table->fts->fts_status & ADDED_TABLE_SYNCED
2951  && doc_id > cache->synced_doc_id) {
2952  mutex_enter(&table->fts->cache->deleted_lock);
2953 
2954  /* The Doc ID could belong to those left in
2955  ADDED table from last crash. So need to check
2956  if it is less than first_doc_id when we initialize
2957  the Doc ID system after reboot */
2958  if (doc_id >= table->fts->cache->first_doc_id
2959  && table->fts->cache->added > 0) {
2960  --table->fts->cache->added;
2961  }
2962 
2963  mutex_exit(&table->fts->cache->deleted_lock);
2964 
2965  /* Only if the row was really deleted. */
2966  ut_a(row->state == FTS_DELETE || row->state == FTS_MODIFY);
2967  }
2968 
2969  /* Note the deleted document for OPTIMIZE to purge. */
2970  if (error == DB_SUCCESS) {
2971 
2972  trx->op_info = "adding doc id to FTS DELETED";
2973 
2974  info->graph_owns_us = TRUE;
2975 
2976  fts_table.suffix = "DELETED";
2977 
2978  graph = fts_parse_sql(
2979  &fts_table,
2980  info,
2981  "BEGIN INSERT INTO \"%s\" VALUES (:doc_id);");
2982 
2983  error = fts_eval_sql(trx, graph);
2984 
2985  fts_que_graph_free(graph);
2986  } else {
2987  pars_info_free(info);
2988  }
2989 
2990  /* Increment the total deleted count, this is used to calculate the
2991  number of documents indexed. */
2992  if (error == DB_SUCCESS) {
2993  mutex_enter(&table->fts->cache->deleted_lock);
2994 
2995  ++table->fts->cache->deleted;
2996 
2997  mutex_exit(&table->fts->cache->deleted_lock);
2998  }
2999 
3000  return(error);
3001 }
3002 
3003 /*********************************************************************/
3006 static __attribute__((nonnull, warn_unused_result))
3007 dberr_t
3008 fts_modify(
3009 /*=======*/
3010  fts_trx_table_t* ftt,
3011  fts_trx_row_t* row)
3012 {
3013  dberr_t error;
3014 
3015  ut_a(row->state == FTS_MODIFY);
3016 
3017  error = fts_delete(ftt, row);
3018 
3019  if (error == DB_SUCCESS) {
3020  error = fts_add(ftt, row);
3021  }
3022 
3023  return(error);
3024 }
3025 
3026 /*********************************************************************/
3029 UNIV_INTERN
3030 dberr_t
3032 /*==============*/
3033  dict_table_t* table,
3034  dtuple_t* row, /* in/out: add doc id value to this
3035  row. This is the current row that is
3036  being inserted. */
3037  mem_heap_t* heap)
3038 {
3039  doc_id_t doc_id;
3040  dberr_t error = DB_SUCCESS;
3041 
3042  ut_a(table->fts->doc_col != ULINT_UNDEFINED);
3043 
3044  if (!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID)) {
3045  if (table->fts->cache->first_doc_id == FTS_NULL_DOC_ID) {
3046  error = fts_get_next_doc_id(table, &doc_id);
3047  }
3048  return(error);
3049  }
3050 
3051  error = fts_get_next_doc_id(table, &doc_id);
3052 
3053  if (error == DB_SUCCESS) {
3054  dfield_t* dfield;
3055  doc_id_t* write_doc_id;
3056 
3057  ut_a(doc_id > 0);
3058 
3059  dfield = dtuple_get_nth_field(row, table->fts->doc_col);
3060  write_doc_id = static_cast<doc_id_t*>(
3061  mem_heap_alloc(heap, sizeof(*write_doc_id)));
3062 
3063  ut_a(doc_id != FTS_NULL_DOC_ID);
3064  ut_a(sizeof(doc_id) == dfield->type.len);
3065  fts_write_doc_id((byte*) write_doc_id, doc_id);
3066 
3067  dfield_set_data(dfield, write_doc_id, sizeof(*write_doc_id));
3068  }
3069 
3070  return(error);
3071 }
3072 
3073 /*********************************************************************/
3077 static __attribute__((nonnull, warn_unused_result))
3078 dberr_t
3079 fts_commit_table(
3080 /*=============*/
3081  fts_trx_table_t* ftt)
3082 {
3083  const ib_rbt_node_t* node;
3084  ib_rbt_t* rows;
3085  dberr_t error = DB_SUCCESS;
3086  fts_cache_t* cache = ftt->table->fts->cache;
3088 
3089  rows = ftt->rows;
3090 
3091  ftt->fts_trx->trx = trx;
3092 
3093  if (cache->get_docs == NULL) {
3094  rw_lock_x_lock(&cache->init_lock);
3095  if (cache->get_docs == NULL) {
3096  cache->get_docs = fts_get_docs_create(cache);
3097  }
3098  rw_lock_x_unlock(&cache->init_lock);
3099  }
3100 
3101  for (node = rbt_first(rows);
3102  node != NULL && error == DB_SUCCESS;
3103  node = rbt_next(rows, node)) {
3104 
3105  fts_trx_row_t* row = rbt_value(fts_trx_row_t, node);
3106 
3107  switch (row->state) {
3108  case FTS_INSERT:
3109  error = fts_add(ftt, row);
3110  break;
3111 
3112  case FTS_MODIFY:
3113  error = fts_modify(ftt, row);
3114  break;
3115 
3116  case FTS_DELETE:
3117  error = fts_delete(ftt, row);
3118  break;
3119 
3120  default:
3121  ut_error;
3122  }
3123  }
3124 
3125  fts_sql_commit(trx);
3126 
3128 
3129  return(error);
3130 }
3131 
3132 /*********************************************************************/
3136 UNIV_INTERN
3137 dberr_t
3138 fts_commit(
3139 /*=======*/
3140  trx_t* trx)
3141 {
3142  const ib_rbt_node_t* node;
3143  dberr_t error;
3144  ib_rbt_t* tables;
3145  fts_savepoint_t* savepoint;
3146 
3147  savepoint = static_cast<fts_savepoint_t*>(
3148  ib_vector_last(trx->fts_trx->savepoints));
3149  tables = savepoint->tables;
3150 
3151  for (node = rbt_first(tables), error = DB_SUCCESS;
3152  node != NULL && error == DB_SUCCESS;
3153  node = rbt_next(tables, node)) {
3154 
3155  fts_trx_table_t** ftt;
3156 
3157  ftt = rbt_value(fts_trx_table_t*, node);
3158 
3159  error = fts_commit_table(*ftt);
3160  }
3161 
3162  return(error);
3163 }
3164 
3165 /*********************************************************************/
3167 UNIV_INTERN
3168 void
3169 fts_doc_init(
3170 /*=========*/
3171  fts_doc_t* doc)
3172 {
3173  mem_heap_t* heap = mem_heap_create(32);
3174 
3175  memset(doc, 0, sizeof(*doc));
3176 
3177  doc->self_heap = ib_heap_allocator_create(heap);
3178 }
3179 
3180 /*********************************************************************/
3182 UNIV_INTERN
3183 void
3184 fts_doc_free(
3185 /*=========*/
3186  fts_doc_t* doc)
3187 {
3188  mem_heap_t* heap = static_cast<mem_heap_t*>(doc->self_heap->arg);
3189 
3190  if (doc->tokens) {
3191  rbt_free(doc->tokens);
3192  }
3193 
3194 #ifdef UNIV_DEBUG
3195  memset(doc, 0, sizeof(*doc));
3196 #endif /* UNIV_DEBUG */
3197 
3198  mem_heap_free(heap);
3199 }
3200 
3201 /*********************************************************************/
3205 UNIV_INTERN
3206 void*
3207 fts_fetch_row_id(
3208 /*=============*/
3209  void* row,
3210  void* user_arg)
3211 {
3212  sel_node_t* node = static_cast<sel_node_t*>(row);
3213 
3214  dfield_t* dfield = que_node_get_val(node->select_list);
3215  dtype_t* type = dfield_get_type(dfield);
3216  ulint len = dfield_get_len(dfield);
3217 
3218  ut_a(dtype_get_mtype(type) == DATA_FIXBINARY);
3219  ut_a(dtype_get_prtype(type) & DATA_BINARY_TYPE);
3220  ut_a(len == 8);
3221 
3222  memcpy(user_arg, dfield_get_data(dfield), 8);
3223 
3224  return(NULL);
3225 }
3226 
3227 /*********************************************************************/
3231 UNIV_INTERN
3232 ibool
3234 /*==========================*/
3235  void* row,
3236  void* user_arg)
3237 {
3238  que_node_t* exp;
3239  sel_node_t* node = static_cast<sel_node_t*>(row);
3240  fts_doc_t* result_doc = static_cast<fts_doc_t*>(user_arg);
3241  dfield_t* dfield;
3242  ulint len;
3243  ulint doc_len;
3244  fts_doc_t doc;
3245  CHARSET_INFO* doc_charset = NULL;
3246  ulint field_no = 0;
3247 
3248  len = 0;
3249 
3250  fts_doc_init(&doc);
3251  doc.found = TRUE;
3252 
3253  exp = node->select_list;
3254  doc_len = 0;
3255 
3256  doc_charset = result_doc->charset;
3257 
3258  /* Copy each indexed column content into doc->text.f_str */
3259  while (exp) {
3260  dfield = que_node_get_val(exp);
3261  len = dfield_get_len(dfield);
3262 
3263  /* NULL column */
3264  if (len == UNIV_SQL_NULL) {
3265  exp = que_node_get_next(exp);
3266  continue;
3267  }
3268 
3269  if (!doc_charset) {
3270  ulint prtype = dfield->type.prtype;
3271  doc_charset = innobase_get_fts_charset(
3272  (int)(prtype & DATA_MYSQL_TYPE_MASK),
3273  (uint) dtype_get_charset_coll(prtype));
3274  }
3275 
3276  doc.charset = doc_charset;
3277 
3278  if (dfield_is_ext(dfield)) {
3279  /* We ignore columns that are stored externally, this
3280  could result in too many words to search */
3281  exp = que_node_get_next(exp);
3282  continue;
3283  } else {
3284  doc.text.f_n_char = 0;
3285 
3286  doc.text.f_str = static_cast<byte*>(
3287  dfield_get_data(dfield));
3288 
3289  doc.text.f_len = len;
3290  }
3291 
3292  if (field_no == 0) {
3293  fts_tokenize_document(&doc, result_doc);
3294  } else {
3295  fts_tokenize_document_next(&doc, doc_len, result_doc);
3296  }
3297 
3298  exp = que_node_get_next(exp);
3299 
3300  doc_len += (exp) ? len + 1 : len;
3301 
3302  field_no++;
3303  }
3304 
3305  ut_ad(doc_charset);
3306 
3307  if (!result_doc->charset) {
3308  result_doc->charset = doc_charset;
3309  }
3310 
3311  fts_doc_free(&doc);
3312 
3313  return(FALSE);
3314 }
3315 
3316 /*********************************************************************/
3318 static
3319 void
3320 fts_fetch_doc_from_rec(
3321 /*===================*/
3322  fts_get_doc_t* get_doc,
3323  dict_index_t* clust_index,
3324  btr_pcur_t* pcur,
3326  ulint* offsets,
3327  fts_doc_t* doc)
3329 {
3332  const rec_t* clust_rec;
3333  ulint num_field;
3334  const dict_field_t* ifield;
3335  const dict_col_t* col;
3336  ulint clust_pos;
3337  ulint i;
3338  ulint doc_len = 0;
3339  ulint processed_doc = 0;
3340 
3341  if (!get_doc) {
3342  return;
3343  }
3344 
3345  index = get_doc->index_cache->index;
3346  table = get_doc->index_cache->index->table;
3347 
3348  clust_rec = btr_pcur_get_rec(pcur);
3349 
3350  num_field = dict_index_get_n_fields(index);
3351 
3352  for (i = 0; i < num_field; i++) {
3353  ifield = dict_index_get_nth_field(index, i);
3354  col = dict_field_get_col(ifield);
3355  clust_pos = dict_col_get_clust_pos(col, clust_index);
3356 
3357  if (!get_doc->index_cache->charset) {
3358  ulint prtype = ifield->col->prtype;
3359 
3360  get_doc->index_cache->charset =
3362  (int) (prtype & DATA_MYSQL_TYPE_MASK),
3363  (uint) dtype_get_charset_coll(prtype));
3364  }
3365 
3366  if (rec_offs_nth_extern(offsets, clust_pos)) {
3367  doc->text.f_str =
3369  clust_rec, offsets,
3370  dict_table_zip_size(table),
3371  clust_pos, &doc->text.f_len,
3372  static_cast<mem_heap_t*>(
3373  doc->self_heap->arg));
3374  } else {
3375  doc->text.f_str = (byte*) rec_get_nth_field(
3376  clust_rec, offsets, clust_pos,
3377  &doc->text.f_len);
3378  }
3379 
3380  doc->found = TRUE;
3381  doc->charset = get_doc->index_cache->charset;
3382 
3383  /* Null Field */
3384  if (doc->text.f_len == UNIV_SQL_NULL) {
3385  continue;
3386  }
3387 
3388  if (processed_doc == 0) {
3389  fts_tokenize_document(doc, NULL);
3390  } else {
3391  fts_tokenize_document_next(doc, doc_len, NULL);
3392  }
3393 
3394  processed_doc++;
3395  doc_len += doc->text.f_len + 1;
3396  }
3397 }
3398 
3399 /*********************************************************************/
3404 static
3405 ulint
3406 fts_add_doc_by_id(
3407 /*==============*/
3408  fts_trx_table_t*ftt,
3409  doc_id_t doc_id,
3410  ib_vector_t* fts_indexes __attribute__((unused)))
3412 {
3413  mtr_t mtr;
3414  mem_heap_t* heap;
3415  btr_pcur_t pcur;
3417  dtuple_t* tuple;
3418  dfield_t* dfield;
3419  fts_get_doc_t* get_doc;
3420  doc_id_t temp_doc_id;
3421  dict_index_t* clust_index;
3422  dict_index_t* fts_id_index;
3423  ibool is_id_cluster;
3424  fts_cache_t* cache = ftt->table->fts->cache;
3425 
3426  ut_ad(cache->get_docs);
3427 
3428  /* If Doc ID has been supplied by the user, then the table
3429  might not yet be sync-ed */
3430 
3431  if (!(ftt->table->fts->fts_status & ADDED_TABLE_SYNCED)) {
3432  fts_init_index(ftt->table, FALSE);
3433  }
3434 
3435  /* Get the first FTS index's get_doc */
3436  get_doc = static_cast<fts_get_doc_t*>(
3437  ib_vector_get(cache->get_docs, 0));
3438  ut_ad(get_doc);
3439 
3440  table = get_doc->index_cache->index->table;
3441 
3442  heap = mem_heap_create(512);
3443 
3444  clust_index = dict_table_get_first_index(table);
3445  fts_id_index = dict_table_get_index_on_name(
3446  table, FTS_DOC_ID_INDEX_NAME);
3447 
3448  /* Check whether the index on FTS_DOC_ID is cluster index */
3449  is_id_cluster = (clust_index == fts_id_index);
3450 
3451  mtr_start(&mtr);
3452  btr_pcur_init(&pcur);
3453 
3454  /* Search based on Doc ID. Here, we'll need to consider the case
3455  when there is no primary index on Doc ID */
3456  tuple = dtuple_create(heap, 1);
3457  dfield = dtuple_get_nth_field(tuple, 0);
3458  dfield->type.mtype = DATA_INT;
3459  dfield->type.prtype = DATA_NOT_NULL | DATA_UNSIGNED | DATA_BINARY_TYPE;
3460 
3461  mach_write_to_8((byte*) &temp_doc_id, doc_id);
3462  dfield_set_data(dfield, &temp_doc_id, sizeof(temp_doc_id));
3463 
3464  btr_pcur_open_with_no_init(
3465  fts_id_index, tuple, PAGE_CUR_LE, BTR_SEARCH_LEAF,
3466  &pcur, 0, &mtr);
3467 
3468  /* If we have a match, add the data to doc structure */
3469  if (btr_pcur_get_low_match(&pcur) == 1) {
3470  const rec_t* rec;
3471  btr_pcur_t* doc_pcur;
3472  const rec_t* clust_rec;
3473  btr_pcur_t clust_pcur;
3474  ulint* offsets = NULL;
3475  ulint num_idx = ib_vector_size(cache->get_docs);
3476 
3477  rec = btr_pcur_get_rec(&pcur);
3478 
3479  /* Doc could be deleted */
3480  if (page_rec_is_infimum(rec)
3481  || rec_get_deleted_flag(rec, dict_table_is_comp(table))) {
3482 
3483  goto func_exit;
3484  }
3485 
3486  if (is_id_cluster) {
3487  clust_rec = rec;
3488  doc_pcur = &pcur;
3489  } else {
3490  dtuple_t* clust_ref;
3491  ulint n_fields;
3492 
3493  btr_pcur_init(&clust_pcur);
3494  n_fields = dict_index_get_n_unique(clust_index);
3495 
3496  clust_ref = dtuple_create(heap, n_fields);
3497  dict_index_copy_types(clust_ref, clust_index, n_fields);
3498 
3500  clust_ref, rec, fts_id_index, NULL, NULL);
3501 
3502  btr_pcur_open_with_no_init(
3503  clust_index, clust_ref, PAGE_CUR_LE,
3504  BTR_SEARCH_LEAF, &clust_pcur, 0, &mtr);
3505 
3506  doc_pcur = &clust_pcur;
3507  clust_rec = btr_pcur_get_rec(&clust_pcur);
3508 
3509  }
3510 
3511  offsets = rec_get_offsets(clust_rec, clust_index,
3512  NULL, ULINT_UNDEFINED, &heap);
3513 
3514  for (ulint i = 0; i < num_idx; ++i) {
3515  fts_doc_t doc;
3517  fts_get_doc_t* get_doc;
3518 
3519  get_doc = static_cast<fts_get_doc_t*>(
3520  ib_vector_get(cache->get_docs, i));
3521 
3522  table = get_doc->index_cache->index->table;
3523 
3524  fts_doc_init(&doc);
3525 
3526  fts_fetch_doc_from_rec(
3527  get_doc, clust_index, doc_pcur, offsets, &doc);
3528 
3529  if (doc.found) {
3530  ibool success __attribute__((unused));
3531 
3532  btr_pcur_store_position(doc_pcur, &mtr);
3533  mtr_commit(&mtr);
3534 
3535  rw_lock_x_lock(&table->fts->cache->lock);
3536 
3537  fts_cache_add_doc(
3538  table->fts->cache,
3539  get_doc->index_cache,
3540  doc_id, doc.tokens);
3541 
3542  rw_lock_x_unlock(&table->fts->cache->lock);
3543 
3544  DBUG_EXECUTE_IF(
3545  "fts_instrument_sync",
3546  fts_sync(cache->sync);
3547  );
3548 
3549  if (cache->total_size > fts_max_cache_size
3550  || fts_need_sync) {
3551  fts_sync(cache->sync);
3552  }
3553 
3554  mtr_start(&mtr);
3555 
3556  if (i < num_idx - 1) {
3557 
3558  success = btr_pcur_restore_position(
3559  BTR_SEARCH_LEAF, doc_pcur,
3560  &mtr);
3561 
3562  ut_ad(success);
3563  }
3564  }
3565 
3566  fts_doc_free(&doc);
3567  }
3568 
3569  if (!is_id_cluster) {
3570  btr_pcur_close(doc_pcur);
3571  }
3572  }
3573 func_exit:
3574  mtr_commit(&mtr);
3575 
3576  btr_pcur_close(&pcur);
3577 
3578  mem_heap_free(heap);
3579  return(TRUE);
3580 }
3581 
3582 
3583 /*********************************************************************/
3586 static
3587 ibool
3588 fts_read_ulint(
3589 /*===========*/
3590  void* row,
3591  void* user_arg)
3592 {
3593  sel_node_t* sel_node = static_cast<sel_node_t*>(row);
3594  ulint* value = static_cast<ulint*>(user_arg);
3595  que_node_t* exp = sel_node->select_list;
3596  dfield_t* dfield = que_node_get_val(exp);
3597  void* data = dfield_get_data(dfield);
3598 
3599  *value = static_cast<ulint>(mach_read_from_4(
3600  static_cast<const byte*>(data)));
3601 
3602  return(TRUE);
3603 }
3604 
3605 /*********************************************************************/
3608 UNIV_INTERN
3609 doc_id_t
3611 /*===============*/
3612  dict_table_t* table)
3613 {
3615  dict_field_t* dfield __attribute__((unused)) = NULL;
3616  doc_id_t doc_id = 0;
3617  mtr_t mtr;
3618  btr_pcur_t pcur;
3619 
3621 
3622  if (!index) {
3623  return(0);
3624  }
3625 
3626  dfield = dict_index_get_nth_field(index, 0);
3627 
3628 #if 0 /* This can fail when renaming a column to FTS_DOC_ID_COL_NAME. */
3629  ut_ad(innobase_strcasecmp(FTS_DOC_ID_COL_NAME, dfield->name) == 0);
3630 #endif
3631 
3632  mtr_start(&mtr);
3633 
3634  /* fetch the largest indexes value */
3636  false, index, BTR_SEARCH_LEAF, &pcur, true, 0, &mtr);
3637 
3638  if (!page_is_empty(btr_pcur_get_page(&pcur))) {
3639  const rec_t* rec = NULL;
3640  ulint offsets_[REC_OFFS_NORMAL_SIZE];
3641  ulint* offsets = offsets_;
3642  mem_heap_t* heap = NULL;
3643  ulint len;
3644  const void* data;
3645 
3646  rec_offs_init(offsets_);
3647 
3648  do {
3649  rec = btr_pcur_get_rec(&pcur);
3650 
3651  if (page_rec_is_user_rec(rec)) {
3652  break;
3653  }
3654  } while (btr_pcur_move_to_prev(&pcur, &mtr));
3655 
3656  if (!rec) {
3657  goto func_exit;
3658  }
3659 
3660  offsets = rec_get_offsets(
3661  rec, index, offsets, ULINT_UNDEFINED, &heap);
3662 
3663  data = rec_get_nth_field(rec, offsets, 0, &len);
3664 
3665  doc_id = static_cast<doc_id_t>(fts_read_doc_id(
3666  static_cast<const byte*>(data)));
3667  }
3668 
3669 func_exit:
3670  btr_pcur_close(&pcur);
3671  mtr_commit(&mtr);
3672  return(doc_id);
3673 }
3674 
3675 /*********************************************************************/
3678 UNIV_INTERN
3679 dberr_t
3681 /*====================*/
3682  fts_get_doc_t* get_doc,
3683  doc_id_t doc_id,
3685  dict_index_t* index_to_use,
3687  ulint option,
3690  callback,
3691  void* arg)
3692 {
3693  pars_info_t* info;
3694  dberr_t error;
3695  const char* select_str;
3696  doc_id_t write_doc_id;
3699  que_t* graph;
3700 
3701  trx->op_info = "fetching indexed FTS document";
3702 
3703  /* The FTS index can be supplied by caller directly with
3704  "index_to_use", otherwise, get it from "get_doc" */
3705  index = (index_to_use) ? index_to_use : get_doc->index_cache->index;
3706 
3707  if (get_doc && get_doc->get_document_graph) {
3708  info = get_doc->get_document_graph->info;
3709  } else {
3710  info = pars_info_create();
3711  }
3712 
3713  /* Convert to "storage" byte order. */
3714  fts_write_doc_id((byte*) &write_doc_id, doc_id);
3715  fts_bind_doc_id(info, "doc_id", &write_doc_id);
3716  pars_info_bind_function(info, "my_func", callback, arg);
3717 
3718  select_str = fts_get_select_columns_str(index, info, info->heap);
3719  pars_info_bind_id(info, TRUE, "table_name", index->table_name);
3720 
3721  if (!get_doc || !get_doc->get_document_graph) {
3722  if (option == FTS_FETCH_DOC_BY_ID_EQUAL) {
3723  graph = fts_parse_sql(
3724  NULL,
3725  info,
3726  mem_heap_printf(info->heap,
3727  "DECLARE FUNCTION my_func;\n"
3728  "DECLARE CURSOR c IS"
3729  " SELECT %s FROM $table_name"
3730  " WHERE %s = :doc_id;\n"
3731  "BEGIN\n"
3732  ""
3733  "OPEN c;\n"
3734  "WHILE 1 = 1 LOOP\n"
3735  " FETCH c INTO my_func();\n"
3736  " IF c %% NOTFOUND THEN\n"
3737  " EXIT;\n"
3738  " END IF;\n"
3739  "END LOOP;\n"
3740  "CLOSE c;",
3741  select_str, FTS_DOC_ID_COL_NAME));
3742  } else {
3743  ut_ad(option == FTS_FETCH_DOC_BY_ID_LARGE);
3744 
3745  /* This is used for crash recovery of table with
3746  hidden DOC ID or FTS indexes. We will scan the table
3747  to re-processing user table rows whose DOC ID or
3748  FTS indexed documents have not been sync-ed to disc
3749  during recent crash.
3750  In the case that all fulltext indexes are dropped
3751  for a table, we will keep the "hidden" FTS_DOC_ID
3752  column, and this scan is to retreive the largest
3753  DOC ID being used in the table to determine the
3754  appropriate next DOC ID.
3755  In the case of there exists fulltext index(es), this
3756  operation will re-tokenize any docs that have not
3757  been sync-ed to the disk, and re-prime the FTS
3758  cached */
3759  graph = fts_parse_sql(
3760  NULL,
3761  info,
3762  mem_heap_printf(info->heap,
3763  "DECLARE FUNCTION my_func;\n"
3764  "DECLARE CURSOR c IS"
3765  " SELECT %s, %s FROM $table_name"
3766  " WHERE %s > :doc_id;\n"
3767  "BEGIN\n"
3768  ""
3769  "OPEN c;\n"
3770  "WHILE 1 = 1 LOOP\n"
3771  " FETCH c INTO my_func();\n"
3772  " IF c %% NOTFOUND THEN\n"
3773  " EXIT;\n"
3774  " END IF;\n"
3775  "END LOOP;\n"
3776  "CLOSE c;",
3778  select_str, FTS_DOC_ID_COL_NAME));
3779  }
3780  if (get_doc) {
3781  get_doc->get_document_graph = graph;
3782  }
3783  } else {
3784  graph = get_doc->get_document_graph;
3785  }
3786 
3787  error = fts_eval_sql(trx, graph);
3788 
3789  if (error == DB_SUCCESS) {
3790  fts_sql_commit(trx);
3791  } else {
3792  fts_sql_rollback(trx);
3793  }
3794 
3796 
3797  if (!get_doc) {
3798  fts_que_graph_free(graph);
3799  }
3800 
3801  return(error);
3802 }
3803 
3804 /*********************************************************************/
3807 UNIV_INTERN
3808 dberr_t
3810 /*===========*/
3811  trx_t* trx,
3812  que_t** graph,
3813  fts_table_t* fts_table,
3814  fts_string_t* word,
3815  fts_node_t* node)
3816 {
3817  pars_info_t* info;
3818  dberr_t error;
3819  ib_uint32_t doc_count;
3820  ib_time_t start_time;
3821  doc_id_t last_doc_id;
3822  doc_id_t first_doc_id;
3823 
3824  if (*graph) {
3825  info = (*graph)->info;
3826  } else {
3827  info = pars_info_create();
3828  }
3829 
3830  pars_info_bind_varchar_literal(info, "token", word->f_str, word->f_len);
3831 
3832  /* Convert to "storage" byte order. */
3833  fts_write_doc_id((byte*) &first_doc_id, node->first_doc_id);
3834  fts_bind_doc_id(info, "first_doc_id", &first_doc_id);
3835 
3836  /* Convert to "storage" byte order. */
3837  fts_write_doc_id((byte*) &last_doc_id, node->last_doc_id);
3838  fts_bind_doc_id(info, "last_doc_id", &last_doc_id);
3839 
3840  ut_a(node->last_doc_id >= node->first_doc_id);
3841 
3842  /* Convert to "storage" byte order. */
3843  mach_write_to_4((byte*) &doc_count, node->doc_count);
3845  info, "doc_count", (const ib_uint32_t*) &doc_count);
3846 
3847  /* Set copy_name to FALSE since it's a static. */
3848  pars_info_bind_literal(
3849  info, "ilist", node->ilist, node->ilist_size,
3850  DATA_BLOB, DATA_BINARY_TYPE);
3851 
3852  if (!*graph) {
3853  *graph = fts_parse_sql(
3854  fts_table,
3855  info,
3856  "BEGIN\n"
3857  "INSERT INTO \"%s\" VALUES "
3858  "(:token, :first_doc_id,"
3859  " :last_doc_id, :doc_count, :ilist);");
3860  }
3861 
3862  start_time = ut_time();
3863  error = fts_eval_sql(trx, *graph);
3864  elapsed_time += ut_time() - start_time;
3865  ++n_nodes;
3866 
3867  return(error);
3868 }
3869 
3870 /*********************************************************************/
3873 static __attribute__((nonnull, warn_unused_result))
3874 dberr_t
3875 fts_sync_add_deleted_cache(
3876 /*=======================*/
3877  fts_sync_t* sync,
3878  ib_vector_t* doc_ids)
3879 {
3880  ulint i;
3881  pars_info_t* info;
3882  que_t* graph;
3884  doc_id_t dummy = 0;
3885  dberr_t error = DB_SUCCESS;
3886  ulint n_elems = ib_vector_size(doc_ids);
3887 
3888  ut_a(ib_vector_size(doc_ids) > 0);
3889 
3890  ib_vector_sort(doc_ids, fts_update_doc_id_cmp);
3891 
3892  info = pars_info_create();
3893 
3894  fts_bind_doc_id(info, "doc_id", &dummy);
3895 
3897  &fts_table, "DELETED_CACHE", FTS_COMMON_TABLE, sync->table);
3898 
3899  graph = fts_parse_sql(
3900  &fts_table,
3901  info,
3902  "BEGIN INSERT INTO \"%s\" VALUES (:doc_id);");
3903 
3904  for (i = 0; i < n_elems && error == DB_SUCCESS; ++i) {
3905  fts_update_t* update;
3906  doc_id_t write_doc_id;
3907 
3908  update = static_cast<fts_update_t*>(ib_vector_get(doc_ids, i));
3909 
3910  /* Convert to "storage" byte order. */
3911  fts_write_doc_id((byte*) &write_doc_id, update->doc_id);
3912  fts_bind_doc_id(info, "doc_id", &write_doc_id);
3913 
3914  error = fts_eval_sql(sync->trx, graph);
3915  }
3916 
3917  fts_que_graph_free(graph);
3918 
3919  return(error);
3920 }
3921 
3922 /*********************************************************************/
3925 static __attribute__((nonnull, warn_unused_result))
3926 dberr_t
3927 fts_sync_write_words(
3928 /*=================*/
3929  trx_t* trx,
3931  index_cache)
3932 {
3934  ulint n_nodes = 0;
3935  ulint n_words = 0;
3936  const ib_rbt_node_t* rbt_node;
3937  dberr_t error = DB_SUCCESS;
3938  ibool print_error = FALSE;
3939 #ifdef FTS_DOC_STATS_DEBUG
3940  dict_table_t* table = index_cache->index->table;
3941  ulint n_new_words = 0;
3942 #endif /* FTS_DOC_STATS_DEBUG */
3943 
3944  FTS_INIT_INDEX_TABLE(
3945  &fts_table, NULL, FTS_INDEX_TABLE, index_cache->index);
3946 
3947  n_words = rbt_size(index_cache->words);
3948 
3949  /* We iterate over the entire tree, even if there is an error,
3950  since we want to free the memory used during caching. */
3951  for (rbt_node = rbt_first(index_cache->words);
3952  rbt_node;
3953  rbt_node = rbt_first(index_cache->words)) {
3954 
3955  ulint i;
3956  ulint selected;
3958 
3959  word = rbt_value(fts_tokenizer_word_t, rbt_node);
3960 
3961  selected = fts_select_index(
3962  index_cache->charset, word->text.f_str,
3963  word->text.f_len);
3964 
3965  fts_table.suffix = fts_get_suffix(selected);
3966 
3967 #ifdef FTS_DOC_STATS_DEBUG
3968  /* Check if the word exists in the FTS index and if not
3969  then we need to increment the total word count stats. */
3970  if (error == DB_SUCCESS && fts_enable_diag_print) {
3971  ibool found = FALSE;
3972 
3973  error = fts_is_word_in_index(
3974  trx,
3975  &index_cache->sel_graph[selected],
3976  &fts_table,
3977  &word->text, &found);
3978 
3979  if (error == DB_SUCCESS && !found) {
3980 
3981  ++n_new_words;
3982  }
3983  }
3984 #endif /* FTS_DOC_STATS_DEBUG */
3985 
3986  n_nodes += ib_vector_size(word->nodes);
3987 
3988  /* We iterate over all the nodes even if there was an error,
3989  this is to free the memory of the fts_node_t elements. */
3990  for (i = 0; i < ib_vector_size(word->nodes); ++i) {
3991 
3992  fts_node_t* fts_node = static_cast<fts_node_t*>(
3993  ib_vector_get(word->nodes, i));
3994 
3995  if (error == DB_SUCCESS) {
3996 
3997  error = fts_write_node(
3998  trx,
3999  &index_cache->ins_graph[selected],
4000  &fts_table, &word->text, fts_node);
4001  }
4002 
4003  ut_free(fts_node->ilist);
4004  fts_node->ilist = NULL;
4005  }
4006 
4007  if (error != DB_SUCCESS && !print_error) {
4008  ut_print_timestamp(stderr);
4009  fprintf(stderr, " InnoDB: Error (%s) writing "
4010  "word node to FTS auxiliary index "
4011  "table.\n", ut_strerr(error));
4012 
4013  print_error = TRUE;
4014  }
4015 
4016  /* NOTE: We are responsible for free'ing the node */
4017  ut_free(rbt_remove_node(index_cache->words, rbt_node));
4018  }
4019 
4020 #ifdef FTS_DOC_STATS_DEBUG
4021  if (error == DB_SUCCESS && n_new_words > 0 && fts_enable_diag_print) {
4023 
4024  FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table);
4025 
4026  /* Increment the total number of words in the FTS index */
4028  trx, index_cache->index, FTS_TOTAL_WORD_COUNT,
4029  n_new_words);
4030  }
4031 #endif /* FTS_DOC_STATS_DEBUG */
4032 
4033  if (fts_enable_diag_print) {
4034  printf("Avg number of nodes: %lf\n",
4035  (double) n_nodes / (double) (n_words > 1 ? n_words : 1));
4036  }
4037 
4038  return(error);
4039 }
4040 
4041 #ifdef FTS_DOC_STATS_DEBUG
4042 /*********************************************************************/
4045 static __attribute__((nonnull, warn_unused_result))
4046 dberr_t
4047 fts_sync_write_doc_stat(
4048 /*====================*/
4049  trx_t* trx,
4050  dict_index_t* index,
4051  que_t** graph, /* out: query graph */
4052  const fts_doc_stats_t* doc_stat)
4053 {
4054  pars_info_t* info;
4055  doc_id_t doc_id;
4056  dberr_t error = DB_SUCCESS;
4057  ib_uint32_t word_count;
4058 
4059  if (*graph) {
4060  info = (*graph)->info;
4061  } else {
4062  info = pars_info_create();
4063  }
4064 
4065  /* Convert to "storage" byte order. */
4066  mach_write_to_4((byte*) &word_count, doc_stat->word_count);
4068  info, "count", (const ib_uint32_t*) &word_count);
4069 
4070  /* Convert to "storage" byte order. */
4071  fts_write_doc_id((byte*) &doc_id, doc_stat->doc_id);
4072  fts_bind_doc_id(info, "doc_id", &doc_id);
4073 
4074  if (!*graph) {
4076 
4077  FTS_INIT_INDEX_TABLE(
4078  &fts_table, "DOC_ID", FTS_INDEX_TABLE, index);
4079 
4080  *graph = fts_parse_sql(
4081  &fts_table,
4082  info,
4083  "BEGIN INSERT INTO \"%s\" VALUES (:doc_id, :count);");
4084  }
4085 
4086  for (;;) {
4087  error = fts_eval_sql(trx, *graph);
4088 
4089  if (error == DB_SUCCESS) {
4090 
4091  break; /* Exit the loop. */
4092  } else {
4093  ut_print_timestamp(stderr);
4094 
4095  if (error == DB_LOCK_WAIT_TIMEOUT) {
4096  fprintf(stderr, " InnoDB: Warning: lock wait "
4097  "timeout writing to FTS doc_id. "
4098  "Retrying!\n");
4099 
4100  trx->error_state = DB_SUCCESS;
4101  } else {
4102  fprintf(stderr, " InnoDB: Error: (%s) "
4103  "while writing to FTS doc_id.\n",
4104  ut_strerr(error));
4105 
4106  break; /* Exit the loop. */
4107  }
4108  }
4109  }
4110 
4111  return(error);
4112 }
4113 
4114 /*********************************************************************/
4117 static
4118 ulint
4119 fts_sync_write_doc_stats(
4120 /*=====================*/
4121  trx_t* trx,
4122  const fts_index_cache_t*index_cache)
4123 {
4124  dberr_t error = DB_SUCCESS;
4125  que_t* graph = NULL;
4126  fts_doc_stats_t* doc_stat;
4127 
4128  if (ib_vector_is_empty(index_cache->doc_stats)) {
4129  return(DB_SUCCESS);
4130  }
4131 
4132  doc_stat = static_cast<ts_doc_stats_t*>(
4133  ib_vector_pop(index_cache->doc_stats));
4134 
4135  while (doc_stat) {
4136  error = fts_sync_write_doc_stat(
4137  trx, index_cache->index, &graph, doc_stat);
4138 
4139  if (error != DB_SUCCESS) {
4140  break;
4141  }
4142 
4143  if (ib_vector_is_empty(index_cache->doc_stats)) {
4144  break;
4145  }
4146 
4147  doc_stat = static_cast<ts_doc_stats_t*>(
4148  ib_vector_pop(index_cache->doc_stats));
4149  }
4150 
4151  if (graph != NULL) {
4152  fts_que_graph_free_check_lock(NULL, index_cache, graph);
4153  }
4154 
4155  return(error);
4156 }
4157 
4158 /*********************************************************************/
4161 static
4162 ibool
4163 fts_lookup_word(
4164 /*============*/
4165  void* row,
4166  void* user_arg)
4167 {
4168 
4169  que_node_t* exp;
4170  sel_node_t* node = static_cast<sel_node_t*>(row);
4171  ibool* found = static_cast<ibool*>(user_arg);
4172 
4173  exp = node->select_list;
4174 
4175  while (exp) {
4176  dfield_t* dfield = que_node_get_val(exp);
4177  ulint len = dfield_get_len(dfield);
4178 
4179  if (len != UNIV_SQL_NULL && len != 0) {
4180  *found = TRUE;
4181  }
4182 
4183  exp = que_node_get_next(exp);
4184  }
4185 
4186  return(FALSE);
4187 }
4188 
4189 /*********************************************************************/
4192 static
4193 dberr_t
4194 fts_is_word_in_index(
4195 /*=================*/
4196  trx_t* trx,
4197  que_t** graph, /* out: Query graph */
4198  fts_table_t* fts_table,
4199  const fts_string_t*
4200  word,
4201  ibool* found) /* out: TRUE if exists */
4202 {
4203  pars_info_t* info;
4204  dberr_t error;
4205 
4206  trx->op_info = "looking up word in FTS index";
4207 
4208  if (*graph) {
4209  info = (*graph)->info;
4210  } else {
4211  info = pars_info_create();
4212  }
4213 
4214  pars_info_bind_function(info, "my_func", fts_lookup_word, found);
4215  pars_info_bind_varchar_literal(info, "word", word->f_str, word->f_len);
4216 
4217  if (*graph == NULL) {
4218  *graph = fts_parse_sql(
4219  fts_table,
4220  info,
4221  "DECLARE FUNCTION my_func;\n"
4222  "DECLARE CURSOR c IS"
4223  " SELECT doc_count\n"
4224  " FROM %s\n"
4225  " WHERE word = :word "
4226  " ORDER BY first_doc_id;\n"
4227  "BEGIN\n"
4228  "\n"
4229  "OPEN c;\n"
4230  "WHILE 1 = 1 LOOP\n"
4231  " FETCH c INTO my_func();\n"
4232  " IF c % NOTFOUND THEN\n"
4233  " EXIT;\n"
4234  " END IF;\n"
4235  "END LOOP;\n"
4236  "CLOSE c;");
4237  }
4238 
4239  for (;;) {
4240  error = fts_eval_sql(trx, *graph);
4241 
4242  if (error == DB_SUCCESS) {
4243 
4244  break; /* Exit the loop. */
4245  } else {
4246  ut_print_timestamp(stderr);
4247 
4248  if (error == DB_LOCK_WAIT_TIMEOUT) {
4249  fprintf(stderr, " InnoDB: Warning: lock wait "
4250  "timeout reading FTS index. "
4251  "Retrying!\n");
4252 
4253  trx->error_state = DB_SUCCESS;
4254  } else {
4255  fprintf(stderr, " InnoDB: Error: (%s) "
4256  "while reading FTS index.\n",
4257  ut_strerr(error));
4258 
4259  break; /* Exit the loop. */
4260  }
4261  }
4262  }
4263 
4264  return(error);
4265 }
4266 #endif /* FTS_DOC_STATS_DEBUG */
4267 
4268 /*********************************************************************/
4270 static
4271 void
4272 fts_sync_begin(
4273 /*===========*/
4274  fts_sync_t* sync)
4275 {
4276  fts_cache_t* cache = sync->table->fts->cache;
4277 
4278  n_nodes = 0;
4279  elapsed_time = 0;
4280 
4281  sync->start_time = ut_time();
4282 
4283  sync->trx = trx_allocate_for_background();
4284 
4285  if (fts_enable_diag_print) {
4286  ib_logf(IB_LOG_LEVEL_INFO,
4287  "FTS SYNC for table %s, deleted count: %ld size: "
4288  "%lu bytes",
4289  sync->table->name,
4290  ib_vector_size(cache->deleted_doc_ids),
4291  cache->total_size);
4292  }
4293 }
4294 
4295 /*********************************************************************/
4299 static __attribute__((nonnull, warn_unused_result))
4300 dberr_t
4301 fts_sync_index(
4302 /*===========*/
4303  fts_sync_t* sync,
4304  fts_index_cache_t* index_cache)
4305 {
4306  trx_t* trx = sync->trx;
4307  dberr_t error = DB_SUCCESS;
4308 
4309  trx->op_info = "doing SYNC index";
4310 
4311  if (fts_enable_diag_print) {
4312  ib_logf(IB_LOG_LEVEL_INFO,
4313  "SYNC words: %ld", rbt_size(index_cache->words));
4314  }
4315 
4316  ut_ad(rbt_validate(index_cache->words));
4317 
4318  error = fts_sync_write_words(trx, index_cache);
4319 
4320 #ifdef FTS_DOC_STATS_DEBUG
4321  /* FTS_RESOLVE: the word counter info in auxiliary table "DOC_ID"
4322  is not used currently for ranking. We disable fts_sync_write_doc_stats()
4323  for now */
4324  /* Write the per doc statistics that will be used for ranking. */
4325  if (error == DB_SUCCESS) {
4326 
4327  error = fts_sync_write_doc_stats(trx, index_cache);
4328  }
4329 #endif /* FTS_DOC_STATS_DEBUG */
4330 
4331  return(error);
4332 }
4333 
4334 /*********************************************************************/
4337 static __attribute__((nonnull, warn_unused_result))
4338 dberr_t
4339 fts_sync_commit(
4340 /*============*/
4341  fts_sync_t* sync)
4342 {
4343  dberr_t error;
4344  trx_t* trx = sync->trx;
4345  fts_cache_t* cache = sync->table->fts->cache;
4346  doc_id_t last_doc_id;
4347 
4348  trx->op_info = "doing SYNC commit";
4349 
4350  /* After each Sync, update the CONFIG table about the max doc id
4351  we just sync-ed to index table */
4352  error = fts_cmp_set_sync_doc_id(sync->table, sync->max_doc_id, FALSE,
4353  &last_doc_id);
4354 
4355  /* Get the list of deleted documents that are either in the
4356  cache or were headed there but were deleted before the add
4357  thread got to them. */
4358 
4359  if (error == DB_SUCCESS && ib_vector_size(cache->deleted_doc_ids) > 0) {
4360 
4361  error = fts_sync_add_deleted_cache(
4362  sync, cache->deleted_doc_ids);
4363  }
4364 
4365  /* We need to do this within the deleted lock since fts_delete() can
4366  attempt to add a deleted doc id to the cache deleted id array. Set
4367  the shutdown flag to FALSE, signifying that we don't want to release
4368  all resources. */
4369  fts_cache_clear(cache, FALSE);
4370  fts_cache_init(cache);
4371  rw_lock_x_unlock(&cache->lock);
4372 
4373  if (error == DB_SUCCESS) {
4374 
4375  fts_sql_commit(trx);
4376 
4377  } else if (error != DB_SUCCESS) {
4378 
4379  fts_sql_rollback(trx);
4380 
4381  ut_print_timestamp(stderr);
4382  fprintf(stderr, " InnoDB: Error: (%s) during SYNC.\n",
4383  ut_strerr(error));
4384  }
4385 
4386  if (fts_enable_diag_print && elapsed_time) {
4387  ib_logf(IB_LOG_LEVEL_INFO,
4388  "SYNC for table %s: SYNC time : %lu secs: "
4389  "elapsed %lf ins/sec",
4390  sync->table->name,
4391  (ulong) (ut_time() - sync->start_time),
4392  (double) n_nodes/ (double) elapsed_time);
4393  }
4394 
4396 
4397  return(error);
4398 }
4399 
4400 /*********************************************************************/
4402 static
4403 void
4404 fts_sync_rollback(
4405 /*==============*/
4406  fts_sync_t* sync)
4407 {
4408  trx_t* trx = sync->trx;
4409  fts_cache_t* cache = sync->table->fts->cache;
4410 
4411  rw_lock_x_unlock(&cache->lock);
4412 
4413  fts_sql_rollback(trx);
4415 }
4416 
4417 /****************************************************************/
4421 static
4422 dberr_t
4423 fts_sync(
4424 /*=====*/
4425  fts_sync_t* sync)
4426 {
4427  ulint i;
4428  dberr_t error = DB_SUCCESS;
4429  fts_cache_t* cache = sync->table->fts->cache;
4430 
4431  rw_lock_x_lock(&cache->lock);
4432 
4433  fts_sync_begin(sync);
4434 
4435  for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
4436  fts_index_cache_t* index_cache;
4437 
4438  index_cache = static_cast<fts_index_cache_t*>(
4439  ib_vector_get(cache->indexes, i));
4440 
4441  error = fts_sync_index(sync, index_cache);
4442 
4443  if (error != DB_SUCCESS && !sync->interrupted) {
4444 
4445  break;
4446  }
4447  }
4448 
4449  DBUG_EXECUTE_IF("fts_instrument_sync_interrupted",
4450  sync->interrupted = true;
4451  );
4452 
4453  if (error == DB_SUCCESS && !sync->interrupted) {
4454  error = fts_sync_commit(sync);
4455  } else {
4456  fts_sync_rollback(sync);
4457  }
4458 
4459  /* We need to check whether an optimize is required, for that
4460  we make copies of the two variables that control the trigger. These
4461  variables can change behind our back and we don't want to hold the
4462  lock for longer than is needed. */
4463  mutex_enter(&cache->deleted_lock);
4464 
4465  cache->added = 0;
4466  cache->deleted = 0;
4467 
4468  mutex_exit(&cache->deleted_lock);
4469 
4470  return(error);
4471 }
4472 
4473 /****************************************************************/
4476 UNIV_INTERN
4477 void
4479 /*===========*/
4480  dict_table_t* table)
4481 {
4482  ut_ad(table->fts);
4483 
4484  if (table->fts->cache) {
4485  fts_sync(table->fts->cache->sync);
4486  }
4487 }
4488 
4489 /********************************************************************
4490 Process next token from document starting at the given position, i.e., add
4491 the token's start position to the token's list of positions.
4492 @return number of characters handled in this call */
4493 static
4494 ulint
4495 fts_process_token(
4496 /*==============*/
4497  fts_doc_t* doc, /* in/out: document to
4498  tokenize */
4499  fts_doc_t* result, /* out: if provided, save
4500  result here */
4501  ulint start_pos,
4502  ulint add_pos)
4504 {
4505  ulint ret;
4506  fts_string_t str;
4507  ulint offset = 0;
4508  fts_doc_t* result_doc;
4509  byte buf[FTS_MAX_WORD_LEN + 1];
4510 
4511  str.f_str = buf;
4512 
4513  /* Determine where to save the result. */
4514  result_doc = (result) ? result : doc;
4515 
4516  /* The length of a string in characters is set here only. */
4517 
4519  doc->charset, doc->text.f_str + start_pos,
4520  doc->text.f_str + doc->text.f_len, &str, &offset);
4521 
4522  /* Ignore string whose character number is less than
4523  "fts_min_token_size" or more than "fts_max_token_size" */
4524 
4525  if (str.f_n_char >= fts_min_token_size
4526  && str.f_n_char <= fts_max_token_size) {
4527 
4528  mem_heap_t* heap;
4529  fts_string_t t_str;
4530  fts_token_t* token;
4531  ib_rbt_bound_t parent;
4532  ulint newlen;
4533 
4534  heap = static_cast<mem_heap_t*>(result_doc->self_heap->arg);
4535 
4536  t_str.f_n_char = str.f_n_char;
4537 
4538  t_str.f_len = str.f_len * doc->charset->casedn_multiply + 1;
4539 
4540  t_str.f_str = static_cast<byte*>(
4541  mem_heap_alloc(heap, t_str.f_len));
4542 
4543  newlen = innobase_fts_casedn_str(
4544  doc->charset, (char*) str.f_str, str.f_len,
4545  (char*) t_str.f_str, t_str.f_len);
4546 
4547  t_str.f_len = newlen;
4548 
4549  /* Add the word to the document statistics. If the word
4550  hasn't been seen before we create a new entry for it. */
4551  if (rbt_search(result_doc->tokens, &parent, &t_str) != 0) {
4552  fts_token_t new_token;
4553 
4554  new_token.text.f_len = newlen;
4555  new_token.text.f_str = t_str.f_str;
4556  new_token.text.f_n_char = t_str.f_n_char;
4557 
4558  new_token.positions = ib_vector_create(
4559  result_doc->self_heap, sizeof(ulint), 32);
4560 
4561  ut_a(new_token.text.f_n_char >= fts_min_token_size);
4562  ut_a(new_token.text.f_n_char <= fts_max_token_size);
4563 
4564  parent.last = rbt_add_node(
4565  result_doc->tokens, &parent, &new_token);
4566 
4567  ut_ad(rbt_validate(result_doc->tokens));
4568  }
4569 
4570 #ifdef FTS_CHARSET_DEBUG
4571  offset += start_pos + add_pos;
4572 #endif /* FTS_CHARSET_DEBUG */
4573 
4574  offset += start_pos + ret - str.f_len + add_pos;
4575 
4576  token = rbt_value(fts_token_t, parent.last);
4577  ib_vector_push(token->positions, &offset);
4578  }
4579 
4580  return(ret);
4581 }
4582 
4583 /******************************************************************/
4585 UNIV_INTERN
4586 void
4588 /*==================*/
4589  fts_doc_t* doc, /* in/out: document to
4590  tokenize */
4591  fts_doc_t* result) /* out: if provided, save
4592  the result token here */
4593 {
4594  ulint inc;
4595 
4596  ut_a(!doc->tokens);
4597  ut_a(doc->charset);
4598 
4599  doc->tokens = rbt_create_arg_cmp(
4600  sizeof(fts_token_t), innobase_fts_text_cmp, doc->charset);
4601 
4602  for (ulint i = 0; i < doc->text.f_len; i += inc) {
4603  inc = fts_process_token(doc, result, i, 0);
4604  ut_a(inc > 0);
4605  }
4606 }
4607 
4608 /******************************************************************/
4610 UNIV_INTERN
4611 void
4613 /*=======================*/
4614  fts_doc_t* doc,
4616  ulint add_pos,
4618  fts_doc_t* result)
4620 {
4621  ulint inc;
4622 
4623  ut_a(doc->tokens);
4624 
4625  for (ulint i = 0; i < doc->text.f_len; i += inc) {
4626  inc = fts_process_token(doc, result, i, add_pos);
4627  ut_a(inc > 0);
4628  }
4629 }
4630 
4631 /********************************************************************
4632 Create the vector of fts_get_doc_t instances. */
4633 UNIV_INTERN
4634 ib_vector_t*
4636 /*================*/
4637  /* out: vector of
4638  fts_get_doc_t instances */
4639  fts_cache_t* cache)
4640 {
4641  ulint i;
4642  ib_vector_t* get_docs;
4643 
4644 #ifdef UNIV_SYNC_DEBUG
4645  ut_ad(rw_lock_own(&cache->init_lock, RW_LOCK_EX));
4646 #endif
4647  /* We need one instance of fts_get_doc_t per index. */
4648  get_docs = ib_vector_create(
4649  cache->self_heap, sizeof(fts_get_doc_t), 4);
4650 
4651  /* Create the get_doc instance, we need one of these
4652  per FTS index. */
4653  for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
4654 
4655  dict_index_t** index;
4656  fts_get_doc_t* get_doc;
4657 
4658  index = static_cast<dict_index_t**>(
4659  ib_vector_get(cache->indexes, i));
4660 
4661  get_doc = static_cast<fts_get_doc_t*>(
4662  ib_vector_push(get_docs, NULL));
4663 
4664  memset(get_doc, 0x0, sizeof(*get_doc));
4665 
4666  get_doc->index_cache = fts_get_index_cache(cache, *index);
4667  get_doc->cache = cache;
4668 
4669  /* Must find the index cache. */
4670  ut_a(get_doc->index_cache != NULL);
4671  }
4672 
4673  return(get_docs);
4674 }
4675 
4676 /********************************************************************
4677 Release any resources held by the fts_get_doc_t instances. */
4678 static
4679 void
4680 fts_get_docs_clear(
4681 /*===============*/
4682  ib_vector_t* get_docs)
4683 {
4684  ulint i;
4685 
4686  /* Release the get doc graphs if any. */
4687  for (i = 0; i < ib_vector_size(get_docs); ++i) {
4688 
4689  fts_get_doc_t* get_doc = static_cast<fts_get_doc_t*>(
4690  ib_vector_get(get_docs, i));
4691 
4692  if (get_doc->get_document_graph != NULL) {
4693 
4694  ut_a(get_doc->index_cache);
4695 
4696  fts_que_graph_free(get_doc->get_document_graph);
4697  get_doc->get_document_graph = NULL;
4698  }
4699  }
4700 }
4701 
4702 /*********************************************************************/
4705 UNIV_INTERN
4706 doc_id_t
4708 /*============*/
4709  const dict_table_t* table)
4710 {
4711  doc_id_t max_doc_id = 0;
4712 
4713  rw_lock_x_lock(&table->fts->cache->lock);
4714 
4715  /* Return if the table is already initialized for DOC ID */
4716  if (table->fts->cache->first_doc_id != FTS_NULL_DOC_ID) {
4717  rw_lock_x_unlock(&table->fts->cache->lock);
4718  return(0);
4719  }
4720 
4721  DEBUG_SYNC_C("fts_initialize_doc_id");
4722 
4723  /* Then compare this value with the ID value stored in the CONFIG
4724  table. The larger one will be our new initial Doc ID */
4725  fts_cmp_set_sync_doc_id(table, 0, FALSE, &max_doc_id);
4726 
4727  /* If DICT_TF2_FTS_ADD_DOC_ID is set, we are in the process of
4728  creating index (and add doc id column. No need to recovery
4729  documents */
4730  if (!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_ADD_DOC_ID)) {
4731  fts_init_index((dict_table_t*) table, TRUE);
4732  }
4733 
4734  table->fts->fts_status |= ADDED_TABLE_SYNCED;
4735 
4736  table->fts->cache->first_doc_id = max_doc_id;
4737 
4738  rw_lock_x_unlock(&table->fts->cache->lock);
4739 
4740  ut_ad(max_doc_id > 0);
4741 
4742  return(max_doc_id);
4743 }
4744 
4745 #ifdef FTS_MULT_INDEX
4746 /*********************************************************************/
4749 static
4750 ibool
4751 fts_is_index_updated(
4752 /*=================*/
4753  const ib_vector_t* fts_indexes,
4754  const fts_get_doc_t* get_doc)
4756 {
4757  ulint i;
4758  dict_index_t* index = get_doc->index_cache->index;
4759 
4760  for (i = 0; i < ib_vector_size(fts_indexes); ++i) {
4761  const dict_index_t* updated_fts_index;
4762 
4763  updated_fts_index = static_cast<const dict_index_t*>(
4764  ib_vector_getp_const(fts_indexes, i));
4765 
4766  ut_a(updated_fts_index != NULL);
4767 
4768  if (updated_fts_index == index) {
4769  return(TRUE);
4770  }
4771  }
4772 
4773  return(FALSE);
4774 }
4775 #endif
4776 
4777 /*********************************************************************/
4780 UNIV_INTERN
4781 ulint
4783 /*===============*/
4784  fts_table_t* fts_table)
4785 {
4786  trx_t* trx;
4787  pars_info_t* info;
4788  que_t* graph;
4789  dberr_t error;
4790  ulint count = 0;
4791 
4793 
4794  trx->op_info = "fetching FT table rows count";
4795 
4796  info = pars_info_create();
4797 
4798  pars_info_bind_function(info, "my_func", fts_read_ulint, &count);
4799 
4800  graph = fts_parse_sql(
4801  fts_table,
4802  info,
4803  "DECLARE FUNCTION my_func;\n"
4804  "DECLARE CURSOR c IS"
4805  " SELECT COUNT(*) "
4806  " FROM \"%s\";\n"
4807  "BEGIN\n"
4808  "\n"
4809  "OPEN c;\n"
4810  "WHILE 1 = 1 LOOP\n"
4811  " FETCH c INTO my_func();\n"
4812  " IF c % NOTFOUND THEN\n"
4813  " EXIT;\n"
4814  " END IF;\n"
4815  "END LOOP;\n"
4816  "CLOSE c;");
4817 
4818  for (;;) {
4819  error = fts_eval_sql(trx, graph);
4820 
4821  if (error == DB_SUCCESS) {
4822  fts_sql_commit(trx);
4823 
4824  break; /* Exit the loop. */
4825  } else {
4826  fts_sql_rollback(trx);
4827 
4828  ut_print_timestamp(stderr);
4829 
4830  if (error == DB_LOCK_WAIT_TIMEOUT) {
4831  fprintf(stderr, " InnoDB: Warning: lock wait "
4832  "timeout reading FTS table. "
4833  "Retrying!\n");
4834 
4835  trx->error_state = DB_SUCCESS;
4836  } else {
4837  fprintf(stderr, " InnoDB: Error: (%s) "
4838  "while reading FTS table.\n",
4839  ut_strerr(error));
4840 
4841  break; /* Exit the loop. */
4842  }
4843  }
4844  }
4845 
4846  fts_que_graph_free(graph);
4847 
4849 
4850  return(count);
4851 }
4852 
4853 #ifdef FTS_CACHE_SIZE_DEBUG
4854 /*********************************************************************/
4856 static
4857 void
4858 fts_update_max_cache_size(
4859 /*======================*/
4860  fts_sync_t* sync)
4861 {
4862  trx_t* trx;
4864 
4866 
4867  FTS_INIT_FTS_TABLE(&fts_table, "CONFIG", FTS_COMMON_TABLE, sync->table);
4868 
4869  /* The size returned is in bytes. */
4870  sync->max_cache_size = fts_get_max_cache_size(trx, &fts_table);
4871 
4872  fts_sql_commit(trx);
4873 
4875 }
4876 #endif /* FTS_CACHE_SIZE_DEBUG */
4877 
4878 /*********************************************************************/
4880 UNIV_INLINE
4881 void
4882 fts_trx_table_rows_free(
4883 /*====================*/
4884  ib_rbt_t* rows)
4885 {
4886  const ib_rbt_node_t* node;
4887 
4888  for (node = rbt_first(rows); node; node = rbt_first(rows)) {
4889  fts_trx_row_t* row;
4890 
4891  row = rbt_value(fts_trx_row_t, node);
4892 
4893  if (row->fts_indexes != NULL) {
4894  /* This vector shouldn't be using the
4895  heap allocator. */
4896  ut_a(row->fts_indexes->allocator->arg == NULL);
4897 
4898  ib_vector_free(row->fts_indexes);
4899  row->fts_indexes = NULL;
4900  }
4901 
4902  ut_free(rbt_remove_node(rows, node));
4903  }
4904 
4905  ut_a(rbt_empty(rows));
4906  rbt_free(rows);
4907 }
4908 
4909 /*********************************************************************/
4911 UNIV_INLINE
4912 void
4913 fts_savepoint_free(
4914 /*===============*/
4915  fts_savepoint_t* savepoint)
4916 {
4917  const ib_rbt_node_t* node;
4918  ib_rbt_t* tables = savepoint->tables;
4919 
4920  /* Nothing to free! */
4921  if (tables == NULL) {
4922  return;
4923  }
4924 
4925  for (node = rbt_first(tables); node; node = rbt_first(tables)) {
4926  fts_trx_table_t* ftt;
4927  fts_trx_table_t** fttp;
4928 
4929  fttp = rbt_value(fts_trx_table_t*, node);
4930  ftt = *fttp;
4931 
4932  /* This can be NULL if a savepoint was released. */
4933  if (ftt->rows != NULL) {
4934  fts_trx_table_rows_free(ftt->rows);
4935  ftt->rows = NULL;
4936  }
4937 
4938  /* This can be NULL if a savepoint was released. */
4939  if (ftt->added_doc_ids != NULL) {
4941  ftt->added_doc_ids = NULL;
4942  }
4943 
4944  /* The default savepoint name must be NULL. */
4945  if (ftt->docs_added_graph) {
4946  fts_que_graph_free(ftt->docs_added_graph);
4947  }
4948 
4949  /* NOTE: We are responsible for free'ing the node */
4950  ut_free(rbt_remove_node(tables, node));
4951  }
4952 
4953  ut_a(rbt_empty(tables));
4954  rbt_free(tables);
4955  savepoint->tables = NULL;
4956 }
4957 
4958 /*********************************************************************/
4960 UNIV_INTERN
4961 void
4962 fts_trx_free(
4963 /*=========*/
4964  fts_trx_t* fts_trx) /* in, own: FTS trx */
4965 {
4966  ulint i;
4967 
4968  for (i = 0; i < ib_vector_size(fts_trx->savepoints); ++i) {
4969  fts_savepoint_t* savepoint;
4970 
4971  savepoint = static_cast<fts_savepoint_t*>(
4972  ib_vector_get(fts_trx->savepoints, i));
4973 
4974  /* The default savepoint name must be NULL. */
4975  if (i == 0) {
4976  ut_a(savepoint->name == NULL);
4977  }
4978 
4979  fts_savepoint_free(savepoint);
4980  }
4981 
4982  for (i = 0; i < ib_vector_size(fts_trx->last_stmt); ++i) {
4983  fts_savepoint_t* savepoint;
4984 
4985  savepoint = static_cast<fts_savepoint_t*>(
4986  ib_vector_get(fts_trx->last_stmt, i));
4987 
4988  /* The default savepoint name must be NULL. */
4989  if (i == 0) {
4990  ut_a(savepoint->name == NULL);
4991  }
4992 
4993  fts_savepoint_free(savepoint);
4994  }
4995 
4996  if (fts_trx->heap) {
4997  mem_heap_free(fts_trx->heap);
4998  }
4999 }
5000 
5001 /*********************************************************************/
5004 UNIV_INTERN
5005 doc_id_t
5007 /*====================*/
5008  dict_table_t* table,
5009  dtuple_t* row)
5011 {
5012  dfield_t* field;
5013  doc_id_t doc_id = 0;
5014 
5015  ut_a(table->fts->doc_col != ULINT_UNDEFINED);
5016 
5017  field = dtuple_get_nth_field(row, table->fts->doc_col);
5018 
5019  ut_a(dfield_get_len(field) == sizeof(doc_id));
5020  ut_a(dfield_get_type(field)->mtype == DATA_INT);
5021 
5022  doc_id = fts_read_doc_id(
5023  static_cast<const byte*>(dfield_get_data(field)));
5024 
5025  return(doc_id);
5026 }
5027 
5028 /*********************************************************************/
5031 UNIV_INTERN
5032 doc_id_t
5034 /*====================*/
5035  dict_table_t* table,
5036  const rec_t* rec,
5037  mem_heap_t* heap)
5038 {
5039  ulint len;
5040  const byte* data;
5041  ulint col_no;
5042  doc_id_t doc_id = 0;
5043  dict_index_t* clust_index;
5044  ulint offsets_[REC_OFFS_NORMAL_SIZE];
5045  ulint* offsets = offsets_;
5046  mem_heap_t* my_heap = heap;
5047 
5048  ut_a(table->fts->doc_col != ULINT_UNDEFINED);
5049 
5050  clust_index = dict_table_get_first_index(table);
5051 
5052  rec_offs_init(offsets_);
5053 
5054  offsets = rec_get_offsets(
5055  rec, clust_index, offsets, ULINT_UNDEFINED, &my_heap);
5056 
5057  col_no = dict_col_get_clust_pos(
5058  &table->cols[table->fts->doc_col], clust_index);
5059  ut_ad(col_no != ULINT_UNDEFINED);
5060 
5061  data = rec_get_nth_field(rec, offsets, col_no, &len);
5062 
5063  ut_a(len == 8);
5064  ut_ad(8 == sizeof(doc_id));
5065  doc_id = static_cast<doc_id_t>(mach_read_from_8(data));
5066 
5067  if (my_heap && !heap) {
5068  mem_heap_free(my_heap);
5069  }
5070 
5071  return(doc_id);
5072 }
5073 
5074 /*********************************************************************/
5077 UNIV_INTERN
5080 /*=================*/
5081  const fts_cache_t* cache,
5082  const dict_index_t* index)
5083 {
5084  /* We cast away the const because our internal function, takes
5085  non-const cache arg and returns a non-const pointer. */
5086  return(static_cast<fts_index_cache_t*>(
5087  fts_get_index_cache((fts_cache_t*) cache, index)));
5088 }
5089 
5090 /*********************************************************************/
5093 UNIV_INTERN
5094 const ib_vector_t*
5096 /*================*/
5097  const fts_index_cache_t*index_cache,
5098  const fts_string_t* text)
5099 {
5100  ib_rbt_bound_t parent;
5101  const ib_vector_t* nodes = NULL;
5102 #ifdef UNIV_SYNC_DEBUG
5103  dict_table_t* table = index_cache->index->table;
5104  fts_cache_t* cache = table->fts->cache;
5105 
5106  ut_ad(rw_lock_own((rw_lock_t*) &cache->lock, RW_LOCK_EX));
5107 #endif
5108 
5109  /* Lookup the word in the rb tree */
5110  if (rbt_search(index_cache->words, &parent, text) == 0) {
5111  const fts_tokenizer_word_t* word;
5112 
5113  word = rbt_value(fts_tokenizer_word_t, parent.last);
5114 
5115  nodes = word->nodes;
5116  }
5117 
5118  return(nodes);
5119 }
5120 
5121 /*********************************************************************/
5124 UNIV_INTERN
5125 ibool
5127 /*========================*/
5128  const fts_cache_t* cache,
5129  doc_id_t doc_id)
5130 {
5131  ulint i;
5132 
5133 #ifdef UNIV_SYNC_DEBUG
5134  ut_ad(mutex_own(&cache->deleted_lock));
5135 #endif
5136 
5137  for (i = 0; i < ib_vector_size(cache->deleted_doc_ids); ++i) {
5138  const fts_update_t* update;
5139 
5140  update = static_cast<const fts_update_t*>(
5141  ib_vector_get_const(cache->deleted_doc_ids, i));
5142 
5143  if (doc_id == update->doc_id) {
5144 
5145  return(TRUE);
5146  }
5147  }
5148 
5149  return(FALSE);
5150 }
5151 
5152 /*********************************************************************/
5154 UNIV_INTERN
5155 void
5157 /*=============================*/
5158  const fts_cache_t* cache,
5159  ib_vector_t* vector)
5160 {
5161  ulint i;
5162 
5163  mutex_enter((ib_mutex_t*) &cache->deleted_lock);
5164 
5165  for (i = 0; i < ib_vector_size(cache->deleted_doc_ids); ++i) {
5166  fts_update_t* update;
5167 
5168  update = static_cast<fts_update_t*>(
5169  ib_vector_get(cache->deleted_doc_ids, i));
5170 
5171  ib_vector_push(vector, &update->doc_id);
5172  }
5173 
5174  mutex_exit((ib_mutex_t*) &cache->deleted_lock);
5175 }
5176 
5177 /*********************************************************************/
5182 UNIV_INTERN
5183 ibool
5185 /*====================================*/
5186  dict_table_t* table,
5188  ulint max_wait)
5191 {
5192  ulint count = 0;
5193  ibool done = FALSE;
5194 
5195  ut_a(max_wait == 0 || max_wait >= FTS_MAX_BACKGROUND_THREAD_WAIT);
5196 
5197  for (;;) {
5198  fts_t* fts = table->fts;
5199 
5200  mutex_enter(&fts->bg_threads_mutex);
5201 
5202  if (fts->fts_status & BG_THREAD_READY) {
5203 
5204  done = TRUE;
5205  }
5206 
5207  mutex_exit(&fts->bg_threads_mutex);
5208 
5209  if (!done) {
5211 
5212  if (max_wait > 0) {
5213 
5214  max_wait -= FTS_MAX_BACKGROUND_THREAD_WAIT;
5215 
5216  /* We ignore the residual value. */
5217  if (max_wait < FTS_MAX_BACKGROUND_THREAD_WAIT) {
5218  break;
5219  }
5220  }
5221 
5222  ++count;
5223  } else {
5224  break;
5225  }
5226 
5227  if (count >= FTS_BACKGROUND_THREAD_WAIT_COUNT) {
5228  ut_print_timestamp(stderr);
5229  fprintf(stderr, " InnoDB: Error the background thread "
5230  "for the FTS table %s refuses to start\n",
5231  table->name);
5232 
5233  count = 0;
5234  }
5235  }
5236 
5237  return(done);
5238 }
5239 
5240 /*********************************************************************/
5242 UNIV_INTERN
5243 void
5245 /*==================*/
5246  dict_table_t* table,
5247  mem_heap_t* heap)
5248 {
5250  table, heap,
5252  DATA_INT,
5254  DATA_NOT_NULL | DATA_UNSIGNED
5255  | DATA_BINARY_TYPE | DATA_FTS_DOC_ID, 0),
5256  sizeof(doc_id_t));
5257  DICT_TF2_FLAG_SET(table, DICT_TF2_FTS_HAS_DOC_ID);
5258 }
5259 
5260 /*********************************************************************/
5263 UNIV_INTERN
5264 doc_id_t
5266 /*==============*/
5267  dict_table_t* table,
5268  upd_field_t* ufield,
5269  doc_id_t* next_doc_id)
5270 {
5271  doc_id_t doc_id;
5272  dberr_t error = DB_SUCCESS;
5273 
5274  if (*next_doc_id) {
5275  doc_id = *next_doc_id;
5276  } else {
5277  /* Get the new document id that will be added. */
5278  error = fts_get_next_doc_id(table, &doc_id);
5279  }
5280 
5281  if (error == DB_SUCCESS) {
5282  dict_index_t* clust_index;
5283 
5284  ufield->exp = NULL;
5285 
5286  ufield->new_val.len = sizeof(doc_id);
5287 
5288  clust_index = dict_table_get_first_index(table);
5289 
5290  ufield->field_no = dict_col_get_clust_pos(
5291  &table->cols[table->fts->doc_col], clust_index);
5292 
5293  /* It is possible we update record that has
5294  not yet be sync-ed from last crash. */
5295 
5296  /* Convert to storage byte order. */
5297  ut_a(doc_id != FTS_NULL_DOC_ID);
5298  fts_write_doc_id((byte*) next_doc_id, doc_id);
5299 
5300  ufield->new_val.data = next_doc_id;
5301  }
5302 
5303  return(doc_id);
5304 }
5305 
5306 /*********************************************************************/
5310 UNIV_INTERN
5311 ibool
5312 fts_dict_table_has_fts_index(
5313 /*=========================*/
5314  dict_table_t* table)
5315 {
5316  return(dict_table_has_fts_index(table));
5317 }
5318 
5319 /*********************************************************************/
5322 UNIV_INTERN
5323 fts_t*
5324 fts_create(
5325 /*=======*/
5326  dict_table_t* table)
5327 {
5328  fts_t* fts;
5329  ib_alloc_t* heap_alloc;
5330  mem_heap_t* heap;
5331 
5332  ut_a(!table->fts);
5333 
5334  heap = mem_heap_create(512);
5335 
5336  fts = static_cast<fts_t*>(mem_heap_alloc(heap, sizeof(*fts)));
5337 
5338  memset(fts, 0x0, sizeof(*fts));
5339 
5340  fts->fts_heap = heap;
5341 
5342  fts->doc_col = ULINT_UNDEFINED;
5343 
5344  mutex_create(
5345  fts_bg_threads_mutex_key, &fts->bg_threads_mutex,
5346  SYNC_FTS_BG_THREADS);
5347 
5348  heap_alloc = ib_heap_allocator_create(heap);
5349  fts->indexes = ib_vector_create(heap_alloc, sizeof(dict_index_t*), 4);
5351 
5352  return(fts);
5353 }
5354 
5355 /*********************************************************************/
5357 UNIV_INTERN
5358 void
5359 fts_free(
5360 /*=====*/
5361  dict_table_t* table)
5362 {
5363  fts_t* fts = table->fts;
5364 
5365  mutex_free(&fts->bg_threads_mutex);
5366 
5367  ut_ad(!fts->add_wq);
5368 
5369  if (fts->cache) {
5370  fts_cache_clear(fts->cache, TRUE);
5371  fts_cache_destroy(fts->cache);
5372  fts->cache = NULL;
5373  }
5374 
5375  mem_heap_free(fts->fts_heap);
5376 
5377  table->fts = NULL;
5378 }
5379 
5380 /*********************************************************************/
5382 UNIV_INTERN
5383 void
5385 /*===============*/
5386  dict_table_t* table,
5387  fts_t* fts)
5389 {
5390  mutex_enter(&fts->bg_threads_mutex);
5391 
5392  fts->fts_status |= BG_THREAD_STOP;
5393 
5394  mutex_exit(&fts->bg_threads_mutex);
5395 
5396 }
5397 
5398 /*********************************************************************/
5400 UNIV_INTERN
5401 void
5402 fts_shutdown(
5403 /*=========*/
5404  dict_table_t* table,
5405  fts_t* fts)
5406 {
5407  mutex_enter(&fts->bg_threads_mutex);
5408 
5409  ut_a(fts->fts_status & BG_THREAD_STOP);
5410 
5411  dict_table_wait_for_bg_threads_to_exit(table, 20000);
5412 
5413  mutex_exit(&fts->bg_threads_mutex);
5414 }
5415 
5416 /*********************************************************************/
5418 UNIV_INLINE
5419 void
5420 fts_savepoint_copy(
5421 /*===============*/
5422  const fts_savepoint_t* src,
5423  fts_savepoint_t* dst)
5424 {
5425  const ib_rbt_node_t* node;
5426  const ib_rbt_t* tables;
5427 
5428  tables = src->tables;
5429 
5430  for (node = rbt_first(tables); node; node = rbt_next(tables, node)) {
5431 
5432  fts_trx_table_t* ftt_dst;
5433  const fts_trx_table_t** ftt_src;
5434 
5435  ftt_src = rbt_value(const fts_trx_table_t*, node);
5436 
5437  ftt_dst = fts_trx_table_clone(*ftt_src);
5438 
5439  rbt_insert(dst->tables, &ftt_dst, &ftt_dst);
5440  }
5441 }
5442 
5443 /*********************************************************************/
5445 UNIV_INTERN
5446 void
5448 /*===============*/
5449  trx_t* trx,
5450  const char* name)
5451 {
5452  mem_heap_t* heap;
5453  fts_trx_t* fts_trx;
5454  fts_savepoint_t* savepoint;
5455  fts_savepoint_t* last_savepoint;
5456 
5457  ut_a(name != NULL);
5458 
5459  fts_trx = trx->fts_trx;
5460  heap = fts_trx->heap;
5461 
5462  /* The implied savepoint must exist. */
5463  ut_a(ib_vector_size(fts_trx->savepoints) > 0);
5464 
5465  last_savepoint = static_cast<fts_savepoint_t*>(
5466  ib_vector_last(fts_trx->savepoints));
5467  savepoint = fts_savepoint_create(fts_trx->savepoints, name, heap);
5468 
5469  if (last_savepoint->tables != NULL) {
5470  fts_savepoint_copy(last_savepoint, savepoint);
5471  }
5472 }
5473 
5474 /*********************************************************************/
5477 UNIV_INLINE
5478 ulint
5479 fts_savepoint_lookup(
5480 /*==================*/
5481  ib_vector_t* savepoints,
5482  const char* name)
5483 {
5484  ulint i;
5485 
5486  ut_a(ib_vector_size(savepoints) > 0);
5487 
5488  for (i = 1; i < ib_vector_size(savepoints); ++i) {
5489  fts_savepoint_t* savepoint;
5490 
5491  savepoint = static_cast<fts_savepoint_t*>(
5492  ib_vector_get(savepoints, i));
5493 
5494  if (strcmp(name, savepoint->name) == 0) {
5495  return(i);
5496  }
5497  }
5498 
5499  return(ULINT_UNDEFINED);
5500 }
5501 
5502 /*********************************************************************/
5506 UNIV_INTERN
5507 void
5509 /*==================*/
5510  trx_t* trx,
5511  const char* name)
5512 {
5513  ulint i;
5514  ib_vector_t* savepoints;
5515  ulint top_of_stack = 0;
5516 
5517  ut_a(name != NULL);
5518 
5519  savepoints = trx->fts_trx->savepoints;
5520 
5521  ut_a(ib_vector_size(savepoints) > 0);
5522 
5523  /* Skip the implied savepoint (first element). */
5524  for (i = 1; i < ib_vector_size(savepoints); ++i) {
5525  fts_savepoint_t* savepoint;
5526 
5527  savepoint = static_cast<fts_savepoint_t*>(
5528  ib_vector_get(savepoints, i));
5529 
5530  /* Even though we release the resources that are part
5531  of the savepoint, we don't (always) actually delete the
5532  entry. We simply set the savepoint name to NULL. Therefore
5533  we have to skip deleted/released entries. */
5534  if (savepoint->name != NULL
5535  && strcmp(name, savepoint->name) == 0) {
5536  break;
5537 
5538  /* Track the previous savepoint instance that will
5539  be at the top of the stack after the release. */
5540  } else if (savepoint->name != NULL) {
5541  /* We need to delete all entries
5542  greater than this element. */
5543  top_of_stack = i;
5544  }
5545  }
5546 
5547  /* Only if we found and element to release. */
5548  if (i < ib_vector_size(savepoints)) {
5549 
5550  ut_a(top_of_stack < ib_vector_size(savepoints));
5551 
5552  /* Skip the implied savepoint. */
5553  for (i = ib_vector_size(savepoints) - 1;
5554  i > top_of_stack;
5555  --i) {
5556 
5557  fts_savepoint_t* savepoint;
5558 
5559  savepoint = static_cast<fts_savepoint_t*>(
5560  ib_vector_get(savepoints, i));
5561 
5562  /* Skip savepoints that were released earlier. */
5563  if (savepoint->name != NULL) {
5564  savepoint->name = NULL;
5565  fts_savepoint_free(savepoint);
5566  }
5567 
5568  ib_vector_pop(savepoints);
5569  }
5570 
5571  /* Make sure we don't delete the implied savepoint. */
5572  ut_a(ib_vector_size(savepoints) > 0);
5573 
5574  /* This must hold. */
5575  ut_a(ib_vector_size(savepoints) == (top_of_stack + 1));
5576  }
5577 }
5578 
5579 /**********************************************************************/
5581 UNIV_INTERN
5582 void
5584 /*===========================*/
5585  trx_t* trx)
5586 {
5587 
5588  fts_trx_t* fts_trx;
5589  fts_savepoint_t* savepoint;
5590 
5591  fts_trx = trx->fts_trx;
5592 
5593  savepoint = static_cast<fts_savepoint_t*>(
5594  ib_vector_pop(fts_trx->last_stmt));
5595  fts_savepoint_free(savepoint);
5596 
5597  ut_ad(ib_vector_is_empty(fts_trx->last_stmt));
5598  savepoint = fts_savepoint_create(fts_trx->last_stmt, NULL, NULL);
5599 }
5600 
5601 /********************************************************************
5602 Undo the Doc ID add/delete operations in last stmt */
5603 static
5604 void
5605 fts_undo_last_stmt(
5606 /*===============*/
5607  fts_trx_table_t* s_ftt,
5608  fts_trx_table_t* l_ftt)
5609 {
5610  ib_rbt_t* s_rows;
5611  ib_rbt_t* l_rows;
5612  const ib_rbt_node_t* node;
5613 
5614  l_rows = l_ftt->rows;
5615  s_rows = s_ftt->rows;
5616 
5617  for (node = rbt_first(l_rows);
5618  node;
5619  node = rbt_next(l_rows, node)) {
5620  fts_trx_row_t* l_row = rbt_value(fts_trx_row_t, node);
5621  ib_rbt_bound_t parent;
5622 
5623  rbt_search(s_rows, &parent, &(l_row->doc_id));
5624 
5625  if (parent.result == 0) {
5626  fts_trx_row_t* s_row = rbt_value(
5627  fts_trx_row_t, parent.last);
5628 
5629  switch (l_row->state) {
5630  case FTS_INSERT:
5631  ut_free(rbt_remove_node(s_rows, parent.last));
5632  break;
5633 
5634  case FTS_DELETE:
5635  if (s_row->state == FTS_NOTHING) {
5636  s_row->state = FTS_INSERT;
5637  } else if (s_row->state == FTS_DELETE) {
5639  s_rows, parent.last));
5640  }
5641  break;
5642 
5643  /* FIXME: Check if FTS_MODIFY need to be addressed */
5644  case FTS_MODIFY:
5645  case FTS_NOTHING:
5646  break;
5647  default:
5648  ut_error;
5649  }
5650  }
5651  }
5652 }
5653 
5654 /**********************************************************************/
5657 UNIV_INTERN
5658 void
5660 /*=============================*/
5661  trx_t* trx)
5662 {
5663  ib_vector_t* savepoints;
5664  fts_savepoint_t* savepoint;
5665  fts_savepoint_t* last_stmt;
5666  fts_trx_t* fts_trx;
5667  ib_rbt_bound_t parent;
5668  const ib_rbt_node_t* node;
5669  ib_rbt_t* l_tables;
5670  ib_rbt_t* s_tables;
5671 
5672  fts_trx = trx->fts_trx;
5673  savepoints = fts_trx->savepoints;
5674 
5675  savepoint = static_cast<fts_savepoint_t*>(ib_vector_last(savepoints));
5676  last_stmt = static_cast<fts_savepoint_t*>(
5677  ib_vector_last(fts_trx->last_stmt));
5678 
5679  l_tables = last_stmt->tables;
5680  s_tables = savepoint->tables;
5681 
5682  for (node = rbt_first(l_tables);
5683  node;
5684  node = rbt_next(l_tables, node)) {
5685 
5686  fts_trx_table_t** l_ftt;
5687 
5688  l_ftt = rbt_value(fts_trx_table_t*, node);
5689 
5691  s_tables, &parent, &(*l_ftt)->table->id,
5692  fts_trx_table_id_cmp, NULL);
5693 
5694  if (parent.result == 0) {
5695  fts_trx_table_t** s_ftt;
5696 
5697  s_ftt = rbt_value(fts_trx_table_t*, parent.last);
5698 
5699  fts_undo_last_stmt(*s_ftt, *l_ftt);
5700  }
5701  }
5702 }
5703 
5704 /**********************************************************************/
5707 UNIV_INTERN
5708 void
5710 /*===================*/
5711  trx_t* trx,
5712  const char* name)
5713 {
5714  ulint i;
5715  ib_vector_t* savepoints;
5716 
5717  ut_a(name != NULL);
5718 
5719  savepoints = trx->fts_trx->savepoints;
5720 
5721  /* We pop all savepoints from the the top of the stack up to
5722  and including the instance that was found. */
5723  i = fts_savepoint_lookup(savepoints, name);
5724 
5725  if (i != ULINT_UNDEFINED) {
5726  fts_savepoint_t* savepoint;
5727 
5728  ut_a(i > 0);
5729 
5730  while (ib_vector_size(savepoints) > i) {
5731  fts_savepoint_t* savepoint;
5732 
5733  savepoint = static_cast<fts_savepoint_t*>(
5734  ib_vector_pop(savepoints));
5735 
5736  if (savepoint->name != NULL) {
5737  /* Since name was allocated on the heap, the
5738  memory will be released when the transaction
5739  completes. */
5740  savepoint->name = NULL;
5741 
5742  fts_savepoint_free(savepoint);
5743  }
5744  }
5745 
5746  /* Pop all a elements from the top of the stack that may
5747  have been released. We have to be careful that we don't
5748  delete the implied savepoint. */
5749 
5750  for (savepoint = static_cast<fts_savepoint_t*>(
5751  ib_vector_last(savepoints));
5752  ib_vector_size(savepoints) > 1
5753  && savepoint->name == NULL;
5754  savepoint = static_cast<fts_savepoint_t*>(
5755  ib_vector_last(savepoints))) {
5756 
5757  ib_vector_pop(savepoints);
5758  }
5759 
5760  /* Make sure we don't delete the implied savepoint. */
5761  ut_a(ib_vector_size(savepoints) > 0);
5762  }
5763 }
5764 
5765 /**********************************************************************/
5768 static
5769 ibool
5770 fts_is_aux_table_name(
5771 /*==================*/
5772  fts_aux_table_t*table,
5773  const char* name,
5774  ulint len)
5775 {
5776  const char* ptr;
5777  char* end;
5778  char my_name[MAX_FULL_NAME_LEN + 1];
5779 
5780  ut_ad(len <= MAX_FULL_NAME_LEN);
5781  ut_memcpy(my_name, name, len);
5782  my_name[len] = 0;
5783  end = my_name + len;
5784 
5785  ptr = static_cast<const char*>(memchr(my_name, '/', len));
5786 
5787  if (ptr != NULL) {
5788  /* We will start the match after the '/' */
5789  ++ptr;
5790  len = end - ptr;
5791  }
5792 
5793  /* All auxiliary tables are prefixed with "FTS_" and the name
5794  length will be at the very least greater than 20 bytes. */
5795  if (ptr != NULL && len > 20 && strncmp(ptr, "FTS_", 4) == 0) {
5796  ulint i;
5797 
5798  /* Skip the prefix. */
5799  ptr += 4;
5800  len -= 4;
5801 
5802  /* Try and read the table id. */
5803  if (!fts_read_object_id(&table->parent_id, ptr)) {
5804  return(FALSE);
5805  }
5806 
5807  /* Skip the table id. */
5808  ptr = static_cast<const char*>(memchr(ptr, '_', len));
5809 
5810  if (ptr == NULL) {
5811  return(FALSE);
5812  }
5813 
5814  /* Skip the underscore. */
5815  ++ptr;
5816  ut_a(end > ptr);
5817  len = end - ptr;
5818 
5819  /* First search the common table suffix array. */
5820  for (i = 0; fts_common_tables[i] != NULL; ++i) {
5821 
5822  if (strncmp(ptr, fts_common_tables[i], len) == 0) {
5823  return(TRUE);
5824  }
5825  }
5826 
5827  /* Try and read the index id. */
5828  if (!fts_read_object_id(&table->index_id, ptr)) {
5829  return(FALSE);
5830  }
5831 
5832  /* Skip the table id. */
5833  ptr = static_cast<const char*>(memchr(ptr, '_', len));
5834 
5835  if (ptr == NULL) {
5836  return(FALSE);
5837  }
5838 
5839  /* Skip the underscore. */
5840  ++ptr;
5841  ut_a(end > ptr);
5842  len = end - ptr;
5843 
5844  /* Search the FT index specific array. */
5845  for (i = 0; fts_index_selector[i].value; ++i) {
5846 
5847  if (strncmp(ptr, fts_get_suffix(i), len) == 0) {
5848  return(TRUE);
5849  }
5850  }
5851 
5852  /* Other FT index specific table(s). */
5853  if (strncmp(ptr, "DOC_ID", len) == 0) {
5854  return(TRUE);
5855  }
5856  }
5857 
5858  return(FALSE);
5859 }
5860 
5861 /**********************************************************************/
5864 static
5865 ibool
5866 fts_read_tables(
5867 /*============*/
5868  void* row,
5869  void* user_arg)
5870 {
5871  int i;
5873  mem_heap_t* heap;
5874  ibool done = FALSE;
5875  ib_vector_t* tables = static_cast<ib_vector_t*>(user_arg);
5876  sel_node_t* sel_node = static_cast<sel_node_t*>(row);
5877  que_node_t* exp = sel_node->select_list;
5878 
5879  /* Must be a heap allocated vector. */
5880  ut_a(tables->allocator->arg != NULL);
5881 
5882  /* We will use this heap for allocating strings. */
5883  heap = static_cast<mem_heap_t*>(tables->allocator->arg);
5884  table = static_cast<fts_aux_table_t*>(ib_vector_push(tables, NULL));
5885 
5886  memset(table, 0x0, sizeof(*table));
5887 
5888  /* Iterate over the columns and read the values. */
5889  for (i = 0; exp && !done; exp = que_node_get_next(exp), ++i) {
5890 
5891  dfield_t* dfield = que_node_get_val(exp);
5892  void* data = dfield_get_data(dfield);
5893  ulint len = dfield_get_len(dfield);
5894 
5895  ut_a(len != UNIV_SQL_NULL);
5896 
5897  /* Note: The column numbers below must match the SELECT */
5898  switch (i) {
5899  case 0: /* NAME */
5900 
5901  if (!fts_is_aux_table_name(
5902  table, static_cast<const char*>(data), len)) {
5903  ib_vector_pop(tables);
5904  done = TRUE;
5905  break;
5906  }
5907 
5908  table->name = static_cast<char*>(
5909  mem_heap_alloc(heap, len + 1));
5910  memcpy(table->name, data, len);
5911  table->name[len] = 0;
5912  break;
5913 
5914  case 1: /* ID */
5915  ut_a(len == 8);
5916  table->id = mach_read_from_8(
5917  static_cast<const byte*>(data));
5918  break;
5919 
5920  default:
5921  ut_error;
5922  }
5923  }
5924 
5925  return(TRUE);
5926 }
5927 
5928 /**********************************************************************/
5932 static __attribute__((nonnull))
5933 void
5934 fts_check_and_drop_orphaned_tables(
5935 /*===============================*/
5936  trx_t* trx,
5937  ib_vector_t* tables)
5938 {
5939  for (ulint i = 0; i < ib_vector_size(tables); ++i) {
5941  fts_aux_table_t* aux_table;
5942  bool drop = false;
5943 
5944  aux_table = static_cast<fts_aux_table_t*>(
5945  ib_vector_get(tables, i));
5946 
5947  table = dict_table_open_on_id(
5948  aux_table->parent_id, TRUE, DICT_TABLE_OP_NORMAL);
5949 
5950  if (table == NULL || table->fts == NULL) {
5951 
5952  drop = true;
5953 
5954  } else if (aux_table->index_id != 0) {
5955  index_id_t id;
5956  fts_t* fts;
5957 
5958  drop = true;
5959  fts = table->fts;
5960  id = aux_table->index_id;
5961 
5962  /* Search for the FT index in the table's list. */
5963  for (ulint j = 0;
5964  j < ib_vector_size(fts->indexes);
5965  ++j) {
5966 
5967  const dict_index_t* index;
5968 
5969  index = static_cast<const dict_index_t*>(
5970  ib_vector_getp_const(fts->indexes, j));
5971 
5972  if (index->id == id) {
5973 
5974  drop = false;
5975  break;
5976  }
5977  }
5978  }
5979 
5980  if (table) {
5981  dict_table_close(table, TRUE, FALSE);
5982  }
5983 
5984  if (drop) {
5985 
5986  ib_logf(IB_LOG_LEVEL_WARN,
5987  "Parent table of FTS auxiliary table %s not "
5988  "found.", aux_table->name);
5989 
5990  dberr_t err = fts_drop_table(trx, aux_table->name);
5991 
5992  if (err == DB_FAIL) {
5993  char* path;
5994 
5995  path = fil_make_ibd_name(
5996  aux_table->name, false);
5997 
5998  os_file_delete_if_exists(innodb_file_data_key,
5999  path);
6000 
6001  mem_free(path);
6002  }
6003  }
6004  }
6005 }
6006 
6007 /**********************************************************************/
6010 UNIV_INTERN
6011 void
6013 /*==========================*/
6014 {
6015  trx_t* trx;
6016  pars_info_t* info;
6017  mem_heap_t* heap;
6018  que_t* graph;
6019  ib_vector_t* tables;
6020  ib_alloc_t* heap_alloc;
6021  space_name_list_t space_name_list;
6022  dberr_t error = DB_SUCCESS;
6023 
6024  /* Note: We have to free the memory after we are done with the list. */
6025  error = fil_get_space_names(space_name_list);
6026 
6027  if (error == DB_OUT_OF_MEMORY) {
6028  ib_logf(IB_LOG_LEVEL_ERROR, "Out of memory");
6029  ut_error;
6030  }
6031 
6032  heap = mem_heap_create(1024);
6033  heap_alloc = ib_heap_allocator_create(heap);
6034 
6035  /* We store the table ids of all the FTS indexes that were found. */
6036  tables = ib_vector_create(heap_alloc, sizeof(fts_aux_table_t), 128);
6037 
6038  /* Get the list of all known .ibd files and check for orphaned
6039  FTS auxiliary files in that list. We need to remove them because
6040  users can't map them back to table names and this will create
6041  unnecessary clutter. */
6042 
6043  for (space_name_list_t::iterator it = space_name_list.begin();
6044  it != space_name_list.end();
6045  ++it) {
6046 
6047  fts_aux_table_t* fts_aux_table;
6048 
6049  fts_aux_table = static_cast<fts_aux_table_t*>(
6050  ib_vector_push(tables, NULL));
6051 
6052  memset(fts_aux_table, 0x0, sizeof(*fts_aux_table));
6053 
6054  if (!fts_is_aux_table_name(fts_aux_table, *it, strlen(*it))) {
6055  ib_vector_pop(tables);
6056  } else {
6057  ulint len = strlen(*it);
6058 
6059  fts_aux_table->id = fil_get_space_id_for_table(*it);
6060 
6061  /* We got this list from fil0fil.cc. The tablespace
6062  with this name must exist. */
6063  ut_a(fts_aux_table->id != ULINT_UNDEFINED);
6064 
6065  fts_aux_table->name = static_cast<char*>(
6066  mem_heap_dup(heap, *it, len + 1));
6067 
6068  fts_aux_table->name[len] = 0;
6069  }
6070  }
6071 
6073  trx->op_info = "dropping orphaned FTS tables";
6074  row_mysql_lock_data_dictionary(trx);
6075 
6076  info = pars_info_create();
6077 
6078  pars_info_bind_function(info, "my_func", fts_read_tables, tables);
6079 
6081  NULL,
6082  info,
6083  "DECLARE FUNCTION my_func;\n"
6084  "DECLARE CURSOR c IS"
6085  " SELECT NAME, ID "
6086  " FROM SYS_TABLES;\n"
6087  "BEGIN\n"
6088  "\n"
6089  "OPEN c;\n"
6090  "WHILE 1 = 1 LOOP\n"
6091  " FETCH c INTO my_func();\n"
6092  " IF c % NOTFOUND THEN\n"
6093  " EXIT;\n"
6094  " END IF;\n"
6095  "END LOOP;\n"
6096  "CLOSE c;");
6097 
6098  for (;;) {
6099  error = fts_eval_sql(trx, graph);
6100 
6101  if (error == DB_SUCCESS) {
6102  fts_check_and_drop_orphaned_tables(trx, tables);
6103  fts_sql_commit(trx);
6104  break; /* Exit the loop. */
6105  } else {
6106  ib_vector_reset(tables);
6107 
6108  fts_sql_rollback(trx);
6109 
6110  ut_print_timestamp(stderr);
6111 
6112  if (error == DB_LOCK_WAIT_TIMEOUT) {
6113  ib_logf(IB_LOG_LEVEL_WARN,
6114  "lock wait timeout reading SYS_TABLES. "
6115  "Retrying!");
6116 
6117  trx->error_state = DB_SUCCESS;
6118  } else {
6119  ib_logf(IB_LOG_LEVEL_ERROR,
6120  "(%s) while reading SYS_TABLES.",
6121  ut_strerr(error));
6122 
6123  break; /* Exit the loop. */
6124  }
6125  }
6126  }
6127 
6128  que_graph_free(graph);
6129 
6131 
6133 
6134  if (heap != NULL) {
6135  mem_heap_free(heap);
6136  }
6137 
6139  for (space_name_list_t::iterator it = space_name_list.begin();
6140  it != space_name_list.end();
6141  ++it) {
6142 
6143  delete[] *it;
6144  }
6145 }
6146 
6147 /**********************************************************************/
6151 UNIV_INTERN
6152 CHARSET_INFO*
6154 /*=====================*/
6155  const char* stopword_table_name)
6157 {
6159  dict_col_t* col = NULL;
6160 
6161  if (!stopword_table_name) {
6162  return(NULL);
6163  }
6164 
6165  table = dict_table_get_low(stopword_table_name);
6166 
6167  if (!table) {
6168  fprintf(stderr,
6169  "InnoDB: user stopword table %s does not exist.\n",
6170  stopword_table_name);
6171 
6172  return(NULL);
6173  } else {
6174  const char* col_name;
6175 
6176  col_name = dict_table_get_col_name(table, 0);
6177 
6178  if (ut_strcmp(col_name, "value")) {
6179  fprintf(stderr,
6180  "InnoDB: invalid column name for stopword "
6181  "table %s. Its first column must be named as "
6182  "'value'.\n", stopword_table_name);
6183 
6184  return(NULL);
6185  }
6186 
6187  col = dict_table_get_nth_col(table, 0);
6188 
6189  if (col->mtype != DATA_VARCHAR
6190  && col->mtype != DATA_VARMYSQL) {
6191  fprintf(stderr,
6192  "InnoDB: invalid column type for stopword "
6193  "table %s. Its first column must be of "
6194  "varchar type\n", stopword_table_name);
6195 
6196  return(NULL);
6197  }
6198  }
6199 
6200  ut_ad(col);
6201 
6202  return(innobase_get_fts_charset(
6203  static_cast<int>(col->prtype & DATA_MYSQL_TYPE_MASK),
6204  static_cast<ulint>(dtype_get_charset_coll(col->prtype))));
6205 }
6206 
6207 /**********************************************************************/
6213 UNIV_INTERN
6214 ibool
6216 /*==============*/
6217  const dict_table_t*
6218  table,
6219  trx_t* trx,
6220  const char* global_stopword_table,
6222  const char* session_stopword_table,
6224  ibool stopword_is_on,
6226  ibool reload)
6228 {
6230  fts_string_t str;
6231  dberr_t error = DB_SUCCESS;
6232  ulint use_stopword;
6233  fts_cache_t* cache;
6234  const char* stopword_to_use = NULL;
6235  ibool new_trx = FALSE;
6236  byte str_buffer[MAX_FULL_NAME_LEN + 1];
6237 
6238  FTS_INIT_FTS_TABLE(&fts_table, "CONFIG", FTS_COMMON_TABLE, table);
6239 
6240  cache = table->fts->cache;
6241 
6242  if (!reload && !(cache->stopword_info.status
6243  & STOPWORD_NOT_INIT)) {
6244  return(TRUE);
6245  }
6246 
6247  if (!trx) {
6249  trx->op_info = "upload FTS stopword";
6250  new_trx = TRUE;
6251  }
6252 
6253  /* First check whether stopword filtering is turned off */
6254  if (reload) {
6255  error = fts_config_get_ulint(
6256  trx, &fts_table, FTS_USE_STOPWORD, &use_stopword);
6257  } else {
6258  use_stopword = (ulint) stopword_is_on;
6259 
6260  error = fts_config_set_ulint(
6261  trx, &fts_table, FTS_USE_STOPWORD, use_stopword);
6262  }
6263 
6264  if (error != DB_SUCCESS) {
6265  goto cleanup;
6266  }
6267 
6268  /* If stopword is turned off, no need to continue to load the
6269  stopword into cache, but still need to do initialization */
6270  if (!use_stopword) {
6271  cache->stopword_info.status = STOPWORD_OFF;
6272  goto cleanup;
6273  }
6274 
6275  if (reload) {
6276  /* Fetch the stopword table name from FTS config
6277  table */
6278  str.f_n_char = 0;
6279  str.f_str = str_buffer;
6280  str.f_len = sizeof(str_buffer) - 1;
6281 
6282  error = fts_config_get_value(
6283  trx, &fts_table, FTS_STOPWORD_TABLE_NAME, &str);
6284 
6285  if (error != DB_SUCCESS) {
6286  goto cleanup;
6287  }
6288 
6289  if (strlen((char*) str.f_str) > 0) {
6290  stopword_to_use = (const char*) str.f_str;
6291  }
6292  } else {
6293  stopword_to_use = (session_stopword_table)
6294  ? session_stopword_table : global_stopword_table;
6295  }
6296 
6297  if (stopword_to_use
6298  && fts_load_user_stopword(table->fts, stopword_to_use,
6299  &cache->stopword_info)) {
6300  /* Save the stopword table name to the configure
6301  table */
6302  if (!reload) {
6303  str.f_n_char = 0;
6304  str.f_str = (byte*) stopword_to_use;
6305  str.f_len = ut_strlen(stopword_to_use);
6306 
6307  error = fts_config_set_value(
6308  trx, &fts_table, FTS_STOPWORD_TABLE_NAME, &str);
6309  }
6310  } else {
6311  /* Load system default stopword list */
6312  fts_load_default_stopword(&cache->stopword_info);
6313  }
6314 
6315 cleanup:
6316  if (new_trx) {
6317  if (error == DB_SUCCESS) {
6318  fts_sql_commit(trx);
6319  } else {
6320  fts_sql_rollback(trx);
6321  }
6322 
6324  }
6325 
6326  if (!cache->stopword_info.cached_stopword) {
6329  }
6330 
6331  return(error == DB_SUCCESS);
6332 }
6333 
6334 /**********************************************************************/
6338 static
6339 ibool
6340 fts_init_get_doc_id(
6341 /*================*/
6342  void* row,
6343  void* user_arg)
6344 {
6345  doc_id_t doc_id = FTS_NULL_DOC_ID;
6346  sel_node_t* node = static_cast<sel_node_t*>(row);
6347  que_node_t* exp = node->select_list;
6348  fts_cache_t* cache = static_cast<fts_cache_t*>(user_arg);
6349 
6351 
6352  /* Copy each indexed column content into doc->text.f_str */
6353  if (exp) {
6354  dfield_t* dfield = que_node_get_val(exp);
6355  dtype_t* type = dfield_get_type(dfield);
6356  void* data = dfield_get_data(dfield);
6357 
6358  ut_a(dtype_get_mtype(type) == DATA_INT);
6359 
6360  doc_id = static_cast<doc_id_t>(mach_read_from_8(
6361  static_cast<const byte*>(data)));
6362 
6363  if (doc_id >= cache->next_doc_id) {
6364  cache->next_doc_id = doc_id + 1;
6365  }
6366  }
6367 
6368  return(TRUE);
6369 }
6370 
6371 /**********************************************************************/
6376 static
6377 ibool
6378 fts_init_recover_doc(
6379 /*=================*/
6380  void* row,
6381  void* user_arg)
6382 {
6383 
6384  fts_doc_t doc;
6385  ulint doc_len = 0;
6386  ulint field_no = 0;
6387  fts_get_doc_t* get_doc = static_cast<fts_get_doc_t*>(user_arg);
6388  doc_id_t doc_id = FTS_NULL_DOC_ID;
6389  sel_node_t* node = static_cast<sel_node_t*>(row);
6390  que_node_t* exp = node->select_list;
6391  fts_cache_t* cache = get_doc->cache;
6392 
6393  fts_doc_init(&doc);
6394  doc.found = TRUE;
6395 
6396  ut_ad(cache);
6397 
6398  /* Copy each indexed column content into doc->text.f_str */
6399  while (exp) {
6400  dfield_t* dfield = que_node_get_val(exp);
6401  ulint len = dfield_get_len(dfield);
6402 
6403  if (field_no == 0) {
6404  dtype_t* type = dfield_get_type(dfield);
6405  void* data = dfield_get_data(dfield);
6406 
6407  ut_a(dtype_get_mtype(type) == DATA_INT);
6408 
6409  doc_id = static_cast<doc_id_t>(mach_read_from_8(
6410  static_cast<const byte*>(data)));
6411 
6412  field_no++;
6413  exp = que_node_get_next(exp);
6414  continue;
6415  }
6416 
6417  if (len == UNIV_SQL_NULL) {
6418  exp = que_node_get_next(exp);
6419  continue;
6420  }
6421 
6422  ut_ad(get_doc);
6423 
6424  if (!get_doc->index_cache->charset) {
6425  ulint prtype = dfield->type.prtype;
6426 
6427  get_doc->index_cache->charset =
6429  (int)(prtype & DATA_MYSQL_TYPE_MASK),
6430  (uint) dtype_get_charset_coll(prtype));
6431  }
6432 
6433  doc.charset = get_doc->index_cache->charset;
6434 
6435  if (dfield_is_ext(dfield)) {
6436  dict_table_t* table = cache->sync->table;
6437  ulint zip_size = dict_table_zip_size(table);
6438 
6440  &doc.text.f_len,
6441  static_cast<byte*>(dfield_get_data(dfield)),
6442  zip_size, len,
6443  static_cast<mem_heap_t*>(doc.self_heap->arg));
6444  } else {
6445  doc.text.f_str = static_cast<byte*>(
6446  dfield_get_data(dfield));
6447 
6448  doc.text.f_len = len;
6449  }
6450 
6451  if (field_no == 1) {
6452  fts_tokenize_document(&doc, NULL);
6453  } else {
6454  fts_tokenize_document_next(&doc, doc_len, NULL);
6455  }
6456 
6457  exp = que_node_get_next(exp);
6458 
6459  doc_len += (exp) ? len + 1 : len;
6460 
6461  field_no++;
6462  }
6463 
6464  fts_cache_add_doc(cache, get_doc->index_cache, doc_id, doc.tokens);
6465 
6466  fts_doc_free(&doc);
6467 
6468  cache->added++;
6469 
6470  if (doc_id >= cache->next_doc_id) {
6471  cache->next_doc_id = doc_id + 1;
6472  }
6473 
6474  return(TRUE);
6475 }
6476 
6477 /**********************************************************************/
6483 UNIV_INTERN
6484 ibool
6486 /*===========*/
6487  dict_table_t* table,
6488  ibool has_cache_lock)
6490 {
6492  doc_id_t start_doc;
6493  fts_get_doc_t* get_doc = NULL;
6494  fts_cache_t* cache = table->fts->cache;
6495  bool need_init = false;
6496 
6497  ut_ad(!mutex_own(&dict_sys->mutex));
6498 
6499  /* First check cache->get_docs is initialized */
6500  if (!has_cache_lock) {
6501  rw_lock_x_lock(&cache->lock);
6502  }
6503 
6504  rw_lock_x_lock(&cache->init_lock);
6505  if (cache->get_docs == NULL) {
6506  cache->get_docs = fts_get_docs_create(cache);
6507  }
6508  rw_lock_x_unlock(&cache->init_lock);
6509 
6510  if (table->fts->fts_status & ADDED_TABLE_SYNCED) {
6511  goto func_exit;
6512  }
6513 
6514  need_init = true;
6515 
6516  start_doc = cache->synced_doc_id;
6517 
6518  if (!start_doc) {
6519  fts_cmp_set_sync_doc_id(table, 0, TRUE, &start_doc);
6520  cache->synced_doc_id = start_doc;
6521  }
6522 
6523  /* No FTS index, this is the case when previous FTS index
6524  dropped, and we re-initialize the Doc ID system for subsequent
6525  insertion */
6526  if (ib_vector_is_empty(cache->get_docs)) {
6528 
6529  ut_a(index);
6530 
6531  fts_doc_fetch_by_doc_id(NULL, start_doc, index,
6532  FTS_FETCH_DOC_BY_ID_LARGE,
6533  fts_init_get_doc_id, cache);
6534  } else {
6535  if (table->fts->cache->stopword_info.status
6536  & STOPWORD_NOT_INIT) {
6537  fts_load_stopword(table, NULL, NULL, NULL, TRUE, TRUE);
6538  }
6539 
6540  for (ulint i = 0; i < ib_vector_size(cache->get_docs); ++i) {
6541  get_doc = static_cast<fts_get_doc_t*>(
6542  ib_vector_get(cache->get_docs, i));
6543 
6544  index = get_doc->index_cache->index;
6545 
6546  fts_doc_fetch_by_doc_id(NULL, start_doc, index,
6547  FTS_FETCH_DOC_BY_ID_LARGE,
6548  fts_init_recover_doc, get_doc);
6549  }
6550  }
6551 
6552  table->fts->fts_status |= ADDED_TABLE_SYNCED;
6553 
6554  fts_get_docs_clear(cache->get_docs);
6555 
6556 func_exit:
6557  if (!has_cache_lock) {
6558  rw_lock_x_unlock(&cache->lock);
6559  }
6560 
6561  if (need_init) {
6562  mutex_enter(&dict_sys->mutex);
6563  /* Register the table with the optimize thread. */
6564  fts_optimize_add_table(table);
6565  mutex_exit(&dict_sys->mutex);
6566  }
6567 
6568  return(TRUE);
6569 }