[Commits] 5811bbd8417: Adding Full Text Search support to partitions.

jacob.mathew at mariadb.com jacob.mathew at mariadb.com
Tue Aug 8 16:58:55 EEST 2017


revision-id: 5811bbd841736a2e5dae57de17c97ceb4ac646da (mariadb-10.2.3-85-g5811bbd8417)
parent(s): f40ab24b89715984e5c3c0d71f5412d121d0dfe4
author: Jacob Mathew
committer: Jacob Mathew
timestamp: 2017-08-03 16:52:39 -0700
message:

Adding Full Text Search support to partitions.

Contains Spiral patches:
- Spiral Patch 007: 007_mariadb-10.2.0.partition_fulltext.diff  MDEV-7705
- Spiral Patch 038: 038_mariadb-10.2.0.partition_fulltext2.diff MDEV-7734

- This commit has the following differences compared to the original patches:
  - Added necessary full text search cleanup at the storage engine layer
    that was omitted in the original patch.
  - Added test case.

---
 sql/ha_partition.cc                                | 628 ++++++++++++++++++++-
 sql/ha_partition.h                                 |  34 +-
 sql/handler.h                                      |   5 +-
 storage/innobase/handler/ha_innodb.cc              |   2 +
 storage/spider/ha_spider.cc                        |   1 +
 .../spider/include/partition_fulltext_deinit.inc   |  23 +
 .../spider/include/partition_fulltext_init.inc     |  72 +++
 .../mysql-test/spider/r/partition_fulltext.result  | 126 +++++
 .../mysql-test/spider/t/partition_fulltext.test    | 223 ++++++++
 storage/xtradb/handler/ha_innodb.cc                |   2 +
 10 files changed, 1080 insertions(+), 36 deletions(-)

diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc
index f9fe27c9fd8..eef625a5de0 100644
--- a/sql/ha_partition.cc
+++ b/sql/ha_partition.cc
@@ -74,7 +74,6 @@
                                        HA_REC_NOT_IN_SEQ | \
                                        HA_CAN_REPAIR)
 #define PARTITION_DISABLED_TABLE_FLAGS (HA_CAN_GEOMETRY | \
-                                        HA_CAN_FULLTEXT | \
                                         HA_DUPLICATE_POS | \
                                         HA_CAN_INSERT_DELAYED | \
                                         HA_READ_BEFORE_WRITE_REMOVAL)
@@ -255,6 +254,8 @@ ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share)
   , bulk_access_info_exec_tgt(NULL)
 {
   DBUG_ENTER("ha_partition::ha_partition(table)");
+  ft_first = NULL;
+  ft_current = NULL;
   init_alloc_root(&m_mem_root, 512, 512, MYF(0));
   init_handler_variables();
   DBUG_VOID_RETURN;
@@ -285,6 +286,8 @@ ha_partition::ha_partition(handlerton *hton, partition_info *part_info)
 {
   DBUG_ENTER("ha_partition::ha_partition(part_info)");
   DBUG_ASSERT(part_info);
+  ft_first = NULL;
+  ft_current = NULL;
   init_alloc_root(&m_mem_root, 512, 512, MYF(0));
   init_handler_variables();
   m_part_info= part_info;
@@ -320,6 +323,8 @@ ha_partition::ha_partition(handlerton *hton, TABLE_SHARE *share,
   , bulk_access_info_exec_tgt(NULL)
 {
   DBUG_ENTER("ha_partition::ha_partition(clone)");
+  ft_first = NULL;
+  ft_current = NULL;
   init_alloc_root(&m_mem_root, 512, 512, MYF(0));
   init_handler_variables();
   m_part_info= part_info_arg;
@@ -3773,6 +3778,22 @@ int ha_partition::close(void)
     } while (bulk_access_info_first);
   }
 
+  if (ft_first)
+  {
+    st_partition_ft_info *tmp_ft_info;
+    do
+    {
+      tmp_ft_info = ft_first->next;
+      my_free(ft_first);
+      ft_first = tmp_ft_info;
+    } while (ft_first);
+  }
+
+  destroy_record_priority_queue();
+  free_partition_bitmaps();
+  DBUG_ASSERT(m_part_info);
+  bitmap_free(&m_mrr_used_partitions);
+
   /* Free active mrr_ranges */
   for (i= 0; i < m_tot_parts; i++)
   {
@@ -6038,31 +6059,6 @@ int ha_partition::pre_read_range_first(const key_range *start_key,
 
 
 /**
-  Return the next record from the FT result set during an ordered index
-  pre-scan
-
-  SYNOPSIS
-    pre_ft_read()
-    use_parallel        Is it a parallel search
-
-  RETURN VALUE
-    >0                  Error code
-    0                   Success
-*/
-
-int ha_partition::pre_ft_read(bool use_parallel)
-{
-  int error;
-  DBUG_ENTER("ha_partition::pre_ft_read");
-  m_pre_calling= TRUE;
-  m_pre_call_use_parallel= use_parallel;
-  error = ft_read(table->record[0]);
-  m_pre_calling= FALSE;
-  DBUG_RETURN(error);
-}
-
-
-/**
   Read next row during the pre-scan of an entire table
   (scan in random row order)
 
@@ -7121,6 +7117,584 @@ int ha_partition::multi_range_read_explain_info(uint mrr_mode, char *str,
 }
 
 
+/**
+  Find and retrieve the Full Text Search relevance ranking for a search string
+  in a full text index.
+
+  @param  handler           Full Text Search handler
+  @param  record            Search string
+  @param  length            Length of the search string
+
+  @retval                   Relevance value
+*/
+
+float partition_ft_find_relevance(FT_INFO *handler,
+                                  uchar *record, uint length)
+{
+  st_partition_ft_info *info = (st_partition_ft_info *)handler;
+  return info->file->ft_find_relevance(handler, record, length);
+}
+
+
+/**
+  Find and retrieve the Full Text Search relevance ranking for a search string
+  in a full text index.
+
+  @param  handler           Full Text Search handler
+  @param  record            Search string
+  @param  length            Length of the search string
+
+  @retval                   Relevance value
+*/
+
+float ha_partition::ft_find_relevance(FT_INFO *handler,
+                                      uchar *record, uint length)
+{
+  st_partition_ft_info *info = (st_partition_ft_info *)handler;
+  FT_INFO *m_handler= info->part_ft_info[m_last_part];
+  DBUG_ENTER("ha_partition::ft_find_relevance");
+
+  if (m_handler &&
+      m_handler->please &&
+      m_handler->please->find_relevance)
+    DBUG_RETURN(m_handler->please->find_relevance(m_handler,
+                                                  record, length));
+  DBUG_RETURN((float)-1.0);
+}
+
+
+/**
+  Retrieve the Full Text Search relevance ranking for the current
+  full text search.
+
+  @param  handler           Full Text Search handler
+
+  @retval                   Relevance value
+*/
+
+float partition_ft_get_relevance(FT_INFO *handler)
+{
+  st_partition_ft_info *info = (st_partition_ft_info *)handler;
+  return info->file->ft_get_relevance(handler);
+}
+
+
+/**
+  Retrieve the Full Text Search relevance ranking for the current
+  full text search.
+
+  @param  handler           Full Text Search handler
+
+  @retval                   Relevance value
+*/
+
+float ha_partition::ft_get_relevance(FT_INFO *handler)
+{
+  st_partition_ft_info *info = (st_partition_ft_info *)handler;
+  FT_INFO *m_handler= info->part_ft_info[m_last_part];
+  DBUG_ENTER("ha_partition::ft_get_relevance");
+
+  if (m_handler &&
+      m_handler->please &&
+      m_handler->please->get_relevance)
+    DBUG_RETURN(m_handler->please->get_relevance(m_handler));
+  DBUG_RETURN((float)-1.0);
+}
+
+
+/**
+  Free the memory for a full text search handler.
+
+  @param  handler           Full Text Search handler
+*/
+
+void partition_ft_close_search(FT_INFO *handler)
+{
+  st_partition_ft_info *info = (st_partition_ft_info *)handler;
+  info->file->ft_close_search(handler);
+}
+
+
+/**
+  Free the memory for a full text search handler.
+
+  @param  handler           Full Text Search handler
+*/
+
+void ha_partition::ft_close_search(FT_INFO *handler)
+{
+  uint i;
+  st_partition_ft_info *info = (st_partition_ft_info *)handler;
+  DBUG_ENTER("ha_partition::ft_close_search");
+
+  for (i= 0; i < m_tot_parts; i++)
+  {
+    FT_INFO *m_handler= info->part_ft_info[i];
+    if (m_handler &&
+        m_handler->please &&
+        m_handler->please->close_search)
+      m_handler->please->close_search(m_handler);
+  }
+  DBUG_VOID_RETURN;
+}
+
+
+/* Partition Full Text search function table */
+_ft_vft partition_ft_vft =
+{
+  NULL, // partition_ft_read_next
+  partition_ft_find_relevance,
+  partition_ft_close_search,
+  partition_ft_get_relevance,
+  NULL  // partition_ft_reinit_search
+};
+
+
+/**
+  Initialize a full text search.
+*/
+
+int ha_partition::ft_init()
+{
+  int error;
+  uint i= 0;
+  uint32 part_id;
+  DBUG_ENTER("ha_partition::ft_init");
+  DBUG_PRINT("info", ("partition this=%p", this));
+
+  /*
+    For operations that may need to change data, we may need to extend
+    read_set.
+  */
+  if (get_lock_type() == F_WRLCK)
+  {
+    /*
+      If write_set contains any of the fields used in partition and
+      subpartition expression, we need to set all bits in read_set because
+      the row may need to be inserted in a different [sub]partition. In
+      other words update_row() can be converted into write_row(), which
+      requires a complete record.
+    */
+    if (bitmap_is_overlapping(&m_part_info->full_part_field_set,
+                              table->write_set))
+      bitmap_set_all(table->read_set);
+    else
+    {
+      /*
+        Some handlers only read fields as specified by the bitmap for the
+        read set. For partitioned handlers we always require that the
+        fields of the partition functions are read such that we can
+        calculate the partition id to place updated and deleted records.
+      */
+      bitmap_union(table->read_set, &m_part_info->full_part_field_set);
+    }
+  }
+
+  /* Now we see what the index of our first important partition is */
+  DBUG_PRINT("info", ("m_part_info->read_partitions: 0x%lx",
+             (long) m_part_info->read_partitions.bitmap));
+  part_id= bitmap_get_first_set(&(m_part_info->read_partitions));
+  DBUG_PRINT("info", ("m_part_spec.start_part %d", part_id));
+
+  if (MY_BIT_NONE == part_id)
+  {
+    error= 0;
+    goto err1;
+  }
+
+  DBUG_PRINT("info", ("ft_init on partition %d", part_id));
+  /*
+    ft_end() is needed for partitioning to reset internal data if scan
+    is already in use
+  */
+  ft_end();
+  late_extra_cache(part_id);
+  m_index_scan_type = partition_ft_read;
+  for (i= part_id; i < m_tot_parts; i++)
+  {
+    if (bitmap_is_set(&(m_part_info->read_partitions), i))
+    {
+      if ((error= m_file[i]->ft_init()))
+        goto err2;
+    }
+  }
+  m_scan_value= 1;
+  m_part_spec.start_part= part_id;
+  m_part_spec.end_part= m_tot_parts - 1;
+  m_ft_init_and_first = TRUE;
+  DBUG_PRINT("info", ("m_scan_value=%d", m_scan_value));
+  DBUG_RETURN(0);
+
+err2:
+  late_extra_no_cache(part_id);
+  while ((int)--i >= (int)part_id)
+  {
+    if (bitmap_is_set(&(m_part_info->read_partitions), i))
+      m_file[i]->ft_end();
+  }
+err1:
+  m_scan_value= 2;
+  m_part_spec.start_part= NO_CURRENT_PART_ID;
+  DBUG_RETURN(error);
+}
+
+
+/**
+  Initialize a full text search during a bulk access request.
+*/
+
+int ha_partition::pre_ft_init()
+{
+  int error;
+  uint i= 0;
+  uint32 part_id;
+  DBUG_ENTER("ha_partition::pre_ft_init");
+  DBUG_PRINT("info", ("partition this=%p", this));
+
+  /*
+    For operations that may need to change data, we may need to extend
+    read_set.
+  */
+  if (get_lock_type() == F_WRLCK)
+  {
+    /*
+      If write_set contains any of the fields used in partition and
+      subpartition expression, we need to set all bits in read_set because
+      the row may need to be inserted in a different [sub]partition. In
+      other words update_row() can be converted into write_row(), which
+      requires a complete record.
+    */
+    if (bitmap_is_overlapping(&m_part_info->full_part_field_set,
+                              table->write_set))
+      bitmap_set_all(table->read_set);
+    else
+    {
+      /*
+        Some handlers only read fields as specified by the bitmap for the
+        read set. For partitioned handlers we always require that the
+        fields of the partition functions are read such that we can
+        calculate the partition id to place updated and deleted records.
+      */
+      bitmap_union(table->read_set, &m_part_info->full_part_field_set);
+    }
+  }
+
+  /* Now we see what the index of our first important partition is */
+  DBUG_PRINT("info", ("m_part_info->read_partitions: 0x%lx",
+             (long) m_part_info->read_partitions.bitmap));
+  part_id= bitmap_get_first_set(&(m_part_info->read_partitions));
+  DBUG_PRINT("info", ("m_part_spec.start_part %d", part_id));
+
+  if (MY_BIT_NONE == part_id)
+  {
+    error= 0;
+    goto err1;
+  }
+
+  DBUG_PRINT("info", ("ft_init on partition %d", part_id));
+  /*
+    pre_ft_end() is needed for partitioning to reset internal data if scan
+    is already in use
+  */
+  if ((error= pre_ft_end()))
+    goto err1;
+  late_extra_cache(part_id);
+  m_index_scan_type = partition_ft_read;
+  for (i= part_id; i < m_tot_parts; i++)
+  {
+    if (bitmap_is_set(&(m_part_info->read_partitions), i))
+    {
+      if ((error= m_file[i]->pre_ft_init()))
+        goto err2;
+      bitmap_set_bit(&bulk_access_exec_bitmap, i);
+    }
+  }
+  m_scan_value= 1;
+  m_part_spec.start_part= part_id;
+  m_part_spec.end_part= m_tot_parts - 1;
+  m_ft_init_and_first = TRUE;
+  DBUG_PRINT("info", ("m_scan_value=%d", m_scan_value));
+  DBUG_RETURN(0);
+
+err2:
+  late_extra_no_cache(part_id);
+  while ((int)--i >= (int)part_id)
+  {
+    if (bitmap_is_set(&(m_part_info->read_partitions), i))
+      m_file[i]->pre_ft_end();
+  }
+err1:
+  m_scan_value= 2;
+  m_part_spec.start_part= NO_CURRENT_PART_ID;
+  DBUG_RETURN(error);
+}
+
+
+/**
+  Terminate a full text search.
+*/
+
+void ha_partition::ft_end()
+{
+  handler **file;
+  DBUG_ENTER("ha_partition::ft_end");
+  DBUG_PRINT("info", ("partition this=%p", this));
+
+  switch (m_scan_value) {
+  case 2:                                       // Error
+    break;
+  case 1:                                       // Table scan
+    if (NO_CURRENT_PART_ID != m_part_spec.start_part)
+      late_extra_no_cache(m_part_spec.start_part);
+    file= m_file;
+    do
+    {
+      if (bitmap_is_set(&(m_part_info->read_partitions), (file - m_file)))
+        (*file)->ft_end();
+    } while (*(++file));
+    break;
+  }
+  m_scan_value= 2;
+  m_part_spec.start_part= NO_CURRENT_PART_ID;
+  ha_ft_end();
+  DBUG_VOID_RETURN;
+}
+
+
+/**
+  Terminate a full text search during a bulk access request.
+*/
+
+int ha_partition::pre_ft_end()
+{
+  int error = 0, tmp_error;
+  handler **file;
+  DBUG_ENTER("ha_partition::pre_ft_end");
+  DBUG_PRINT("info", ("partition this=%p", this));
+
+  switch (m_scan_value) {
+  case 2:                                       // Error
+    break;
+  case 1:                                       // Table scan
+    if (NO_CURRENT_PART_ID != m_part_spec.start_part)
+      late_extra_no_cache(m_part_spec.start_part);
+    file= m_file;
+    do
+    {
+      if (bitmap_is_set(&(m_part_info->read_partitions), (file - m_file)))
+      {
+        if ((tmp_error = (*file)->pre_ft_end()))
+          error = tmp_error;
+      }
+    } while (*(++file));
+    break;
+  }
+  m_scan_value= 2;
+  m_part_spec.start_part= NO_CURRENT_PART_ID;
+  DBUG_RETURN(error);
+}
+
+
+/**
+  Initialize a full text search using the extended API.
+
+  @param  flags             Search flags
+  @param  inx               Key number
+  @param  key               Key value
+
+  @return FT_INFO structure if successful
+          NULL              otherwise
+*/
+
+FT_INFO *ha_partition::ft_init_ext(uint flags, uint inx, String *key)
+{
+  FT_INFO *ft_handler;
+  handler **file;
+  st_partition_ft_info *ft_target;
+  FT_INFO **tmp_ft_info;
+  DBUG_ENTER("ha_partition::ft_init_ext");
+
+  if (ft_current)
+  {
+    ft_target = ft_current->next;
+    if (!ft_target)
+    {
+      if (!(ft_target = (st_partition_ft_info *)
+                        my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
+                                        &ft_target,
+                                        sizeof(st_partition_ft_info),
+                                        &tmp_ft_info,
+                                        sizeof(FT_INFO *) * m_tot_parts,
+                                        NullS)))
+      {
+        my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
+        DBUG_RETURN(NULL);
+      }
+      ft_target->part_ft_info = tmp_ft_info;
+      ft_current->next = ft_target;
+    }
+  }
+  else
+  {
+    ft_target = ft_first;
+    if (!ft_target)
+    {
+      if (!(ft_target = (st_partition_ft_info *)
+                        my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
+                                        &ft_target,
+                                        sizeof(st_partition_ft_info),
+                                        &tmp_ft_info,
+                                        sizeof(FT_INFO *) * m_tot_parts,
+                                        NullS)))
+      {
+        my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR));
+        DBUG_RETURN(NULL);
+      }
+      ft_target->part_ft_info = tmp_ft_info;
+      ft_first = ft_target;
+    }
+  }
+  ft_current = ft_target;
+  file= m_file;
+  do
+  {
+    if (bitmap_is_set(&(m_part_info->read_partitions), (file - m_file)))
+    {
+      if ((ft_handler= (*file)->ft_init_ext(flags, inx, key)))
+        (*file)->ft_handler= ft_handler;
+      else
+        (*file)->ft_handler= NULL;
+      ft_target->part_ft_info[file - m_file]= ft_handler;
+    }
+    else
+    {
+      (*file)->ft_handler= NULL;
+      ft_target->part_ft_info[file - m_file]= NULL;
+    }
+  } while (*(++file));
+  ft_target->please= &partition_ft_vft;
+  ft_target->file= this;
+  DBUG_RETURN((FT_INFO*)ft_target);
+}
+
+
+/**
+  Return the next record from the FT result set during an ordered index
+  pre-scan
+
+  @param  use_parallel      Is it a parallel search
+
+  @return >0                Error code
+          0                 Success
+*/
+
+int ha_partition::pre_ft_read(bool use_parallel)
+{
+  int error;
+  DBUG_ENTER("ha_partition::pre_ft_read");
+  DBUG_PRINT("info", ("partition this=%p", this));
+  m_pre_calling= TRUE;
+  m_pre_call_use_parallel= use_parallel;
+  error = ft_read(table->record[0]);
+  m_pre_calling= FALSE;
+  DBUG_RETURN(error);
+}
+
+
+/**
+  Return the first or next record in a full text search.
+
+  @param  buf               Buffer where the record should be returned
+
+  @return >0                Error code
+          0                 Success
+*/
+
+int ha_partition::ft_read(uchar *buf)
+{
+  handler *file;
+  int result= HA_ERR_END_OF_FILE, error;
+  uint part_id= m_part_spec.start_part;
+  DBUG_ENTER("ha_partition::ft_read");
+  DBUG_PRINT("info", ("partition this=%p", this));
+  DBUG_PRINT("info", ("part_id=%u", part_id));
+
+  if (NO_CURRENT_PART_ID == part_id)
+  {
+    /*
+      The original set of partitions to scan was empty and thus we report
+      the result here.
+    */
+    DBUG_PRINT("info", ("NO_CURRENT_PART_ID"));
+    goto end;
+  }
+
+  DBUG_ASSERT(m_scan_value == 1);
+
+  if (m_ft_init_and_first)
+  {
+    m_ft_init_and_first = FALSE;
+    if (!bulk_access_executing)
+    {
+      if (check_parallel_search())
+        error= handle_pre_scan(FALSE, TRUE);
+      else
+        error = handle_pre_scan(FALSE, FALSE);
+      if (m_pre_calling || error)
+        DBUG_RETURN(error);
+    }
+  }
+
+  file= m_file[part_id];
+
+  while (TRUE)
+  {
+    result= file->ft_read(buf);
+    if (!result)
+    {
+      m_last_part= part_id;
+      m_part_spec.start_part= part_id;
+      table->status= 0;
+      DBUG_RETURN(0);
+    }
+
+    /*
+      if we get here, then the current partition ft_next returned failure
+    */
+    if (result == HA_ERR_RECORD_DELETED)
+      continue;                               // Probably MyISAM
+
+    if (result != HA_ERR_END_OF_FILE)
+      goto end_dont_reset_start_part;         // Return error
+
+    /* End current partition */
+    late_extra_no_cache(part_id);
+    DBUG_PRINT("info", ("ft_end on partition %d", part_id));
+
+    /* Shift to next partition */
+    while (++part_id < m_tot_parts &&
+           !bitmap_is_set(&(m_part_info->read_partitions), part_id))
+      ;
+    if (part_id >= m_tot_parts)
+    {
+      result= HA_ERR_END_OF_FILE;
+      break;
+    }
+    m_last_part= part_id;
+    m_part_spec.start_part= part_id;
+    file= m_file[part_id];
+    DBUG_PRINT("info", ("ft_init on partition %d", part_id));
+    late_extra_cache(part_id);
+  }
+
+end:
+  m_part_spec.start_part= NO_CURRENT_PART_ID;
+end_dont_reset_start_part:
+  table->status= STATUS_NOT_FOUND;
+  DBUG_RETURN(result);
+}
+
+
 /*
   Common routine to set up index scans
 
@@ -7152,7 +7726,7 @@ int ha_partition::partition_scan_set_up(uchar * buf, bool idx_read_flag)
   DBUG_ENTER("ha_partition::partition_scan_set_up");
 
   if (idx_read_flag)
-    get_partition_set(table,buf,active_index,&m_start_key,&m_part_spec);
+    get_partition_set(table, buf, active_index, &m_start_key, &m_part_spec);
   else
   {
     m_part_spec.start_part= 0;
diff --git a/sql/ha_partition.h b/sql/ha_partition.h
index 0159e78030a..c00b86b008a 100644
--- a/sql/ha_partition.h
+++ b/sql/ha_partition.h
@@ -31,6 +31,18 @@ enum partition_keywords
 #define PARTITION_BYTES_IN_POS 2
 
 
+class ha_partition;
+
+/* Partition Full Text Search info */
+struct st_partition_ft_info
+{
+  struct _ft_vft        *please;
+  st_partition_ft_info  *next;
+  ha_partition          *file;
+  FT_INFO               **part_ft_info;
+};
+
+
 typedef struct st_partition_bulk_access_info
 {
   uint                          sequence_num;
@@ -1032,7 +1044,7 @@ class ha_partition :public handler
     special file for handling names of partitions, engine types.
     HA_REC_NOT_IN_SEQ is always set for partition handler since we cannot
     guarantee that the records will be returned in sequence.
-    HA_CAN_FULLTEXT, HA_DUPLICATE_POS,
+    HA_DUPLICATE_POS,
     HA_CAN_INSERT_DELAYED, HA_PRIMARY_KEY_REQUIRED_FOR_POSITION is disabled
     until further investigated.
   */
@@ -1245,14 +1257,20 @@ class ha_partition :public handler
     -------------------------------------------------------------------------
     MODULE fulltext index
     -------------------------------------------------------------------------
-    Fulltext stuff not yet.
-    -------------------------------------------------------------------------
-    virtual int ft_init() { return HA_ERR_WRONG_COMMAND; }
-    virtual FT_INFO *ft_init_ext(uint flags,uint inx,const uchar *key,
-    uint keylen)
-    { return NULL; }
-    virtual int ft_read(uchar *buf) { return HA_ERR_WRONG_COMMAND; }
   */
+    st_partition_ft_info *ft_first;
+    st_partition_ft_info *ft_current;
+    bool m_ft_init_and_first;
+    virtual float ft_find_relevance(FT_INFO *handler,
+                                    uchar *record, uint length);
+    virtual float ft_get_relevance(FT_INFO *handler);
+    void ft_close_search(FT_INFO *handler);
+    virtual int ft_init();
+    virtual int pre_ft_init();
+    virtual void ft_end();
+    virtual int pre_ft_end();
+    virtual FT_INFO *ft_init_ext(uint flags, uint inx, String *key);
+    virtual int ft_read(uchar *buf);
 
   /*
      -------------------------------------------------------------------------
diff --git a/sql/handler.h b/sql/handler.h
index ffaef39f329..77db208a9cc 100644
--- a/sql/handler.h
+++ b/sql/handler.h
@@ -3266,7 +3266,9 @@ class handler :public Sql_alloc
   int compare_key(key_range *range);
   int compare_key2(key_range *range);
   virtual int ft_init() { return HA_ERR_WRONG_COMMAND; }
-  void ft_end() { ft_handler=NULL; }
+  virtual int pre_ft_init() { return HA_ERR_WRONG_COMMAND; }
+  virtual void ft_end() { ha_ft_end(); }
+  virtual int pre_ft_end() { return 0; }
   virtual FT_INFO *ft_init_ext(uint flags, uint inx,String *key)
     { return NULL; }
 private:
@@ -3289,6 +3291,7 @@ class handler :public Sql_alloc
 
   /* Same as above, but with statistics */
   inline int ha_ft_read(uchar *buf);
+  inline void ha_ft_end() { ft_handler=NULL; }
   int ha_rnd_next(uchar *buf);
   int ha_rnd_pos(uchar *buf, uchar *pos);
   inline int ha_rnd_pos_by_record(uchar *buf);
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index 93f5c936915..292f23d41c0 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -11005,6 +11005,8 @@ ha_innobase::ft_end()
 	ib::info() << "ft_end()";
 
 	rnd_end();
+
+  ha_ft_end();
 }
 #ifdef WITH_WSREP
 extern dict_index_t*
diff --git a/storage/spider/ha_spider.cc b/storage/spider/ha_spider.cc
index 5698edb1e20..988e082c476 100644
--- a/storage/spider/ha_spider.cc
+++ b/storage/spider/ha_spider.cc
@@ -7893,6 +7893,7 @@ void ha_spider::ft_end()
       store_error_num = index_end();
   }
   ft_init_without_index_init = FALSE;
+  ha_ft_end();
   DBUG_VOID_RETURN;
 }
 
diff --git a/storage/spider/mysql-test/spider/include/partition_fulltext_deinit.inc b/storage/spider/mysql-test/spider/include/partition_fulltext_deinit.inc
new file mode 100644
index 00000000000..c0c652d14f5
--- /dev/null
+++ b/storage/spider/mysql-test/spider/include/partition_fulltext_deinit.inc
@@ -0,0 +1,23 @@
+--let $MASTER_1_COMMENT_2_1= $MASTER_1_COMMENT_2_1_BACKUP
+--let $MASTER_1_COMMENT_2_2= $MASTER_1_COMMENT_2_2_BACKUP
+--let $CHILD2_1_DROP_TABLES= $CHILD2_1_DROP_TABLES_BACKUP
+--let $CHILD2_1_CREATE_TABLES= $CHILD2_1_CREATE_TABLES_BACKUP
+--let $CHILD2_1_SELECT_TABLES= $CHILD2_1_SELECT_TABLES_BACKUP
+--let $CHILD2_2_DROP_TABLES= $CHILD2_2_DROP_TABLES_BACKUP
+--let $CHILD2_2_CREATE_TABLES= $CHILD2_2_CREATE_TABLES_BACKUP
+--let $CHILD2_2_SELECT_TABLES= $CHILD2_2_SELECT_TABLES_BACKUP
+--let $CHILD2_3_DROP_TABLES= $CHILD2_3_DROP_TABLES_BACKUP
+--let $CHILD2_3_CREATE_TABLES= $CHILD2_3_CREATE_TABLES_BACKUP
+--let $CHILD2_3_SELECT_TABLES= $CHILD2_3_SELECT_TABLES_BACKUP
+--let $OUTPUT_CHILD_GROUP2= $OUTPUT_CHILD_GROUP2_BACKUP
+--let $USE_GENERAL_LOG= $USE_GENERAL_LOG_BACKUP
+--connection master_1
+set session join_cache_level= @old_join_cache_level;
+set session optimizer_switch= @old_optimizer_switch;
+--disable_warnings
+--disable_query_log
+--disable_result_log
+--source ../t/test_deinit.inc
+--enable_result_log
+--enable_query_log
+--enable_warnings
diff --git a/storage/spider/mysql-test/spider/include/partition_fulltext_init.inc b/storage/spider/mysql-test/spider/include/partition_fulltext_init.inc
new file mode 100644
index 00000000000..754395493af
--- /dev/null
+++ b/storage/spider/mysql-test/spider/include/partition_fulltext_init.inc
@@ -0,0 +1,72 @@
+--disable_warnings
+--disable_query_log
+--disable_result_log
+--source ../t/test_init.inc
+--enable_result_log
+--enable_query_log
+--enable_warnings
+--let $MASTER_1_COMMENT_2_1_BACKUP= $MASTER_1_COMMENT_2_1
+let $MASTER_1_COMMENT_2_1=
+  COMMENT='table "tbl_a", bka_mode "1"'
+  PARTITION BY KEY(pkey) (
+    PARTITION pt1 COMMENT='srv "s_2_1"',
+    PARTITION pt2 COMMENT='srv "s_2_2"',
+    PARTITION pt3 COMMENT='srv "s_2_3"'
+  );
+--let $CHILD2_1_DROP_TABLES_BACKUP= $CHILD2_1_DROP_TABLES
+let $CHILD2_1_DROP_TABLES=
+  DROP TABLE IF EXISTS tbl_a;
+--let $CHILD2_1_CREATE_TABLES_BACKUP= $CHILD2_1_CREATE_TABLES
+let $CHILD2_1_CREATE_TABLES=
+  CREATE TABLE tbl_a (
+    pkey int NOT NULL,
+    words text NOT NULL,
+    PRIMARY KEY (pkey),
+    FULLTEXT (words)
+  ) $CHILD2_1_ENGINE $CHILD2_1_CHARSET;
+--let $CHILD2_1_SELECT_TABLES_BACKUP= $CHILD2_1_SELECT_TABLES
+let $CHILD2_1_SELECT_TABLES=
+  SELECT pkey FROM tbl_a ORDER BY pkey;
+let $CHILD2_1_SELECT_ARGUMENT1=
+  SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %';
+--let $CHILD2_2_DROP_TABLES_BACKUP= $CHILD2_2_DROP_TABLES
+let $CHILD2_2_DROP_TABLES=
+  DROP TABLE IF EXISTS tbl_a;
+--let $CHILD2_2_CREATE_TABLES_BACKUP= $CHILD2_2_CREATE_TABLES
+let $CHILD2_2_CREATE_TABLES=
+  CREATE TABLE tbl_a (
+    pkey int NOT NULL,
+    words text NOT NULL,
+    PRIMARY KEY (pkey),
+    FULLTEXT (words)
+  ) $CHILD2_2_ENGINE $CHILD2_2_CHARSET;
+--let $CHILD2_2_SELECT_TABLES_BACKUP= $CHILD2_2_SELECT_TABLES
+let $CHILD2_2_SELECT_TABLES=
+  SELECT pkey FROM tbl_a ORDER BY pkey;
+let $CHILD2_2_SELECT_ARGUMENT1=
+  SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %';
+--let $CHILD2_3_DROP_TABLES_BACKUP= $CHILD2_3_DROP_TABLES
+let $CHILD2_3_DROP_TABLES=
+  DROP TABLE IF EXISTS tbl_a;
+--let $CHILD2_3_CREATE_TABLES_BACKUP= $CHILD2_3_CREATE_TABLES
+let $CHILD2_3_CREATE_TABLES=
+  CREATE TABLE tbl_a (
+    pkey int NOT NULL,
+    words text NOT NULL,
+    PRIMARY KEY (pkey),
+    FULLTEXT (words)
+  ) $CHILD2_3_ENGINE $CHILD2_3_CHARSET;
+--let $CHILD2_3_SELECT_TABLES_BACKUP= $CHILD2_3_SELECT_TABLES
+let $CHILD2_3_SELECT_TABLES=
+  SELECT pkey FROM tbl_a ORDER BY pkey;
+let $CHILD2_3_SELECT_ARGUMENT1=
+  SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %';
+--let $OUTPUT_CHILD_GROUP2_BACKUP= $OUTPUT_CHILD_GROUP2
+--let $OUTPUT_CHILD_GROUP2= 1
+--let $USE_GENERAL_LOG_BACKUP= $USE_GENERAL_LOG
+--let $USE_GENERAL_LOG= 1
+--connection master_1
+set @old_join_cache_level= @@join_cache_level;
+set session join_cache_level= 5;
+set @old_optimizer_switch= @@optimizer_switch;
+set session optimizer_switch= 'mrr=on';
diff --git a/storage/spider/mysql-test/spider/r/partition_fulltext.result b/storage/spider/mysql-test/spider/r/partition_fulltext.result
new file mode 100644
index 00000000000..3289473b905
--- /dev/null
+++ b/storage/spider/mysql-test/spider/r/partition_fulltext.result
@@ -0,0 +1,126 @@
+for master_1
+for child2
+child2_1
+child2_2
+child2_3
+for child3
+child3_1
+child3_2
+child3_3
+connection master_1;
+set @old_join_cache_level= @@join_cache_level;
+set session join_cache_level= 5;
+set @old_optimizer_switch= @@optimizer_switch;
+set session optimizer_switch= 'mrr=on';
+
+drop and create databases
+connection master_1;
+DROP DATABASE IF EXISTS auto_test_local;
+CREATE DATABASE auto_test_local;
+USE auto_test_local;
+connection child2_1;
+SET @old_log_output = @@global.log_output;
+SET GLOBAL log_output = 'TABLE,FILE';
+DROP DATABASE IF EXISTS auto_test_remote;
+CREATE DATABASE auto_test_remote;
+USE auto_test_remote;
+connection child2_2;
+SET @old_log_output = @@global.log_output;
+SET GLOBAL log_output = 'TABLE,FILE';
+DROP DATABASE IF EXISTS auto_test_remote2;
+CREATE DATABASE auto_test_remote2;
+USE auto_test_remote2;
+connection child2_3;
+SET @old_log_output = @@global.log_output;
+SET GLOBAL log_output = 'TABLE,FILE';
+DROP DATABASE IF EXISTS auto_test_remote3;
+CREATE DATABASE auto_test_remote3;
+USE auto_test_remote3;
+
+create table and insert
+connection child2_1;
+CHILD2_1_DROP_TABLES
+CHILD2_1_CREATE_TABLES
+TRUNCATE TABLE mysql.general_log;
+connection child2_2;
+CHILD2_2_DROP_TABLES
+CHILD2_2_CREATE_TABLES
+TRUNCATE TABLE mysql.general_log;
+connection child2_3;
+CHILD2_3_DROP_TABLES
+CHILD2_3_CREATE_TABLES
+TRUNCATE TABLE mysql.general_log;
+connection master_1;
+DROP TABLE IF EXISTS tbl_a;
+DROP TABLE IF EXISTS tbl_b;
+CREATE TABLE tbl_a (
+pkey int NOT NULL,
+words text NOT NULL,
+PRIMARY KEY (pkey),
+FULLTEXT (words)
+) MASTER_1_ENGINE MASTER_1_CHARSET MASTER_1_COMMENT_2_1
+INSERT INTO tbl_a (pkey, words) VALUES (0, 'abc'),(1, 'def'),(2, 'ghi'),(3, 'jkl'),(4, 'mno'),(5, 'pqr'),(6, 'stu'),(7, 'vwx');
+
+select test
+connection child2_1;
+TRUNCATE TABLE mysql.general_log;
+connection master_1;
+SELECT pkey, words FROM tbl_a WHERE match(words) against('+ghi' in boolean mode);
+pkey	words
+2	ghi
+connection child2_1;
+SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %';
+argument
+select match(`words`)against('+ghi' in boolean mode),`pkey`,`words` from `auto_test_remote`.`tbl_a` where match(`words`)against('+ghi' in boolean mode) and (match(`words`)against('+ghi' in boolean mode))
+SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %'
+SELECT pkey FROM tbl_a ORDER BY pkey;
+pkey
+4
+5
+connection child2_2;
+SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %';
+argument
+select match(`words`)against('+ghi' in boolean mode),`pkey`,`words` from `auto_test_remote2`.`tbl_a` where match(`words`)against('+ghi' in boolean mode) and (match(`words`)against('+ghi' in boolean mode))
+SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %'
+SELECT pkey FROM tbl_a ORDER BY pkey;
+pkey
+0
+1
+6
+7
+connection child2_3;
+SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %';
+argument
+select match(`words`)against('+ghi' in boolean mode),`pkey`,`words` from `auto_test_remote3`.`tbl_a` where match(`words`)against('+ghi' in boolean mode) and (match(`words`)against('+ghi' in boolean mode))
+SELECT argument FROM mysql.general_log WHERE argument LIKE '%select %'
+SELECT pkey FROM tbl_a ORDER BY pkey;
+pkey
+2
+3
+
+deinit
+connection master_1;
+DROP DATABASE IF EXISTS auto_test_local;
+connection child2_1;
+DROP DATABASE IF EXISTS auto_test_remote;
+SET GLOBAL log_output = @old_log_output;
+connection child2_2;
+DROP DATABASE IF EXISTS auto_test_remote2;
+SET GLOBAL log_output = @old_log_output;
+connection child2_3;
+DROP DATABASE IF EXISTS auto_test_remote3;
+SET GLOBAL log_output = @old_log_output;
+connection master_1;
+set session join_cache_level= @old_join_cache_level;
+set session optimizer_switch= @old_optimizer_switch;
+for master_1
+for child2
+child2_1
+child2_2
+child2_3
+for child3
+child3_1
+child3_2
+child3_3
+
+end of test
diff --git a/storage/spider/mysql-test/spider/t/partition_fulltext.test b/storage/spider/mysql-test/spider/t/partition_fulltext.test
new file mode 100644
index 00000000000..cd9f9b05e9d
--- /dev/null
+++ b/storage/spider/mysql-test/spider/t/partition_fulltext.test
@@ -0,0 +1,223 @@
+--source ../include/partition_fulltext_init.inc
+if (!$HAVE_PARTITION)
+{
+  --source ../include/partition_fulltext_deinit.inc
+  skip Test requires partitioning;
+}
+
+--echo
+--echo drop and create databases
+--connection master_1
+--disable_warnings
+DROP DATABASE IF EXISTS auto_test_local;
+CREATE DATABASE auto_test_local;
+USE auto_test_local;
+if ($USE_CHILD_GROUP2)
+{
+  --connection child2_1
+  if ($USE_GENERAL_LOG)
+  {
+    SET @old_log_output = @@global.log_output;
+    SET GLOBAL log_output = 'TABLE,FILE';
+  }
+  DROP DATABASE IF EXISTS auto_test_remote;
+  CREATE DATABASE auto_test_remote;
+  USE auto_test_remote;
+  --connection child2_2
+  if ($USE_GENERAL_LOG)
+  {
+    SET @old_log_output = @@global.log_output;
+    SET GLOBAL log_output = 'TABLE,FILE';
+  }
+  DROP DATABASE IF EXISTS auto_test_remote2;
+  CREATE DATABASE auto_test_remote2;
+  USE auto_test_remote2;
+  --connection child2_3
+  if ($USE_GENERAL_LOG)
+  {
+    SET @old_log_output = @@global.log_output;
+    SET GLOBAL log_output = 'TABLE,FILE';
+  }
+  DROP DATABASE IF EXISTS auto_test_remote3;
+  CREATE DATABASE auto_test_remote3;
+  USE auto_test_remote3;
+}
+--enable_warnings
+
+--echo
+--echo create table and insert
+if ($USE_CHILD_GROUP2)
+{
+  if (!$OUTPUT_CHILD_GROUP2)
+  {
+    --disable_query_log
+    --disable_result_log
+  }
+  --connection child2_1
+  if ($OUTPUT_CHILD_GROUP2)
+  {
+    --disable_query_log
+    echo CHILD2_1_DROP_TABLES;
+    echo CHILD2_1_CREATE_TABLES;
+  }
+  --disable_warnings
+  eval $CHILD2_1_DROP_TABLES;
+  --enable_warnings
+  eval $CHILD2_1_CREATE_TABLES;
+  if ($OUTPUT_CHILD_GROUP2)
+  {
+    --enable_query_log
+  }
+  if ($USE_GENERAL_LOG)
+  {
+    TRUNCATE TABLE mysql.general_log;
+  }
+  --connection child2_2
+  if ($OUTPUT_CHILD_GROUP2)
+  {
+    --disable_query_log
+    echo CHILD2_2_DROP_TABLES;
+    echo CHILD2_2_CREATE_TABLES;
+  }
+  --disable_warnings
+  eval $CHILD2_2_DROP_TABLES;
+  --enable_warnings
+  eval $CHILD2_2_CREATE_TABLES;
+  if ($OUTPUT_CHILD_GROUP2)
+  {
+    --enable_query_log
+  }
+  if ($USE_GENERAL_LOG)
+  {
+    TRUNCATE TABLE mysql.general_log;
+  }
+  --connection child2_3
+  if ($OUTPUT_CHILD_GROUP2)
+  {
+    --disable_query_log
+    echo CHILD2_3_DROP_TABLES;
+    echo CHILD2_3_CREATE_TABLES;
+  }
+  --disable_warnings
+  eval $CHILD2_3_DROP_TABLES;
+  --enable_warnings
+  eval $CHILD2_3_CREATE_TABLES;
+  if ($OUTPUT_CHILD_GROUP2)
+  {
+    --enable_query_log
+  }
+  if ($USE_GENERAL_LOG)
+  {
+    TRUNCATE TABLE mysql.general_log;
+  }
+  if (!$OUTPUT_CHILD_GROUP2)
+  {
+    --enable_query_log
+    --enable_result_log
+  }
+}
+--connection master_1
+--disable_warnings
+DROP TABLE IF EXISTS tbl_a;
+DROP TABLE IF EXISTS tbl_b;
+--enable_warnings
+--disable_query_log
+echo CREATE TABLE tbl_a (
+    pkey int NOT NULL,
+    words text NOT NULL,
+    PRIMARY KEY (pkey),
+    FULLTEXT (words)
+) MASTER_1_ENGINE MASTER_1_CHARSET MASTER_1_COMMENT_2_1;
+eval CREATE TABLE tbl_a (
+    pkey int NOT NULL,
+    words text NOT NULL,
+    PRIMARY KEY (pkey),
+    FULLTEXT (words)
+) $MASTER_1_ENGINE $MASTER_1_CHARSET $MASTER_1_COMMENT_2_1;
+--enable_query_log
+INSERT INTO tbl_a (pkey, words) VALUES (0, 'abc'),(1, 'def'),(2, 'ghi'),(3, 'jkl'),(4, 'mno'),(5, 'pqr'),(6, 'stu'),(7, 'vwx');
+
+--echo
+--echo select test
+if ($USE_CHILD_GROUP2)
+{
+  if (!$OUTPUT_CHILD_GROUP2)
+  {
+    --disable_query_log
+    --disable_result_log
+  }
+  --connection child2_1
+  if ($USE_GENERAL_LOG)
+  {
+    TRUNCATE TABLE mysql.general_log;
+  }
+  if (!$OUTPUT_CHILD_GROUP2)
+  {
+    --enable_query_log
+    --enable_result_log
+  }
+}
+--connection master_1
+SELECT pkey, words FROM tbl_a WHERE match(words) against('+ghi' in boolean mode);
+if ($USE_CHILD_GROUP2)
+{
+  if (!$OUTPUT_CHILD_GROUP2)
+  {
+    --disable_query_log
+    --disable_result_log
+  }
+  --connection child2_1
+  if ($USE_GENERAL_LOG)
+  {
+    eval $CHILD2_1_SELECT_ARGUMENT1;
+  }
+  eval $CHILD2_1_SELECT_TABLES;
+  --connection child2_2
+  if ($USE_GENERAL_LOG)
+  {
+    eval $CHILD2_2_SELECT_ARGUMENT1;
+  }
+  eval $CHILD2_2_SELECT_TABLES;
+  --connection child2_3
+  if ($USE_GENERAL_LOG)
+  {
+    eval $CHILD2_3_SELECT_ARGUMENT1;
+  }
+  eval $CHILD2_3_SELECT_TABLES;
+  if (!$OUTPUT_CHILD_GROUP2)
+  {
+    --enable_query_log
+    --enable_result_log
+  }
+}
+
+--echo
+--echo deinit
+--disable_warnings
+--connection master_1
+DROP DATABASE IF EXISTS auto_test_local;
+if ($USE_CHILD_GROUP2)
+{
+  --connection child2_1
+  DROP DATABASE IF EXISTS auto_test_remote;
+  if ($USE_GENERAL_LOG)
+  {
+    SET GLOBAL log_output = @old_log_output;
+  }
+  --connection child2_2
+  DROP DATABASE IF EXISTS auto_test_remote2;
+  if ($USE_GENERAL_LOG)
+  {
+    SET GLOBAL log_output = @old_log_output;
+  }
+  --connection child2_3
+  DROP DATABASE IF EXISTS auto_test_remote3;
+  if ($USE_GENERAL_LOG)
+  {
+    SET GLOBAL log_output = @old_log_output;
+  }
+}
+--enable_warnings
+--source ../include/partition_fulltext_deinit.inc
+--echo
+--echo end of test
diff --git a/storage/xtradb/handler/ha_innodb.cc b/storage/xtradb/handler/ha_innodb.cc
index a3f1d9c3c85..e4a0c2c4fc7 100644
--- a/storage/xtradb/handler/ha_innodb.cc
+++ b/storage/xtradb/handler/ha_innodb.cc
@@ -10824,6 +10824,8 @@ ha_innobase::ft_end()
 	fprintf(stderr, "ft_end()\n");
 
 	rnd_end();
+
+  ha_ft_end();
 }
 #ifdef WITH_WSREP
 extern dict_index_t*



More information about the commits mailing list