CRM-14811 - FullText - Use InnoDB FTS when available.
authorTim Otten <totten@civicrm.org>
Sat, 7 Jun 2014 02:26:47 +0000 (19:26 -0700)
committerTim Otten <totten@civicrm.org>
Sat, 7 Jun 2014 03:11:27 +0000 (20:11 -0700)
CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php
CRM/Contact/Form/Search/Custom/FullText/Activity.php
CRM/Contact/Form/Search/Custom/FullText/Case.php
CRM/Contact/Form/Search/Custom/FullText/Contact.php
CRM/Contact/Form/Search/Custom/FullText/Contribution.php
CRM/Contact/Form/Search/Custom/FullText/Membership.php
CRM/Contact/Form/Search/Custom/FullText/Participant.php
CRM/Core/InnoDBIndexer.php

index df216da965d8165943362944f3685cc454028a1d..372c5f5746af3e46ed57ff98e4284cdc96b1e24d 100644 (file)
@@ -141,7 +141,8 @@ $sqlStatement
           }
         }
         else {
-          $clauses = array();
+          $fullTextFields = array(); // array (string $sqlColumnName)
+          $clauses = array(); // array (string $sqlExpression)
 
           foreach ($tableValues['fields'] as $fieldName => $fieldType) {
             if ($fieldType == 'Int') {
@@ -150,10 +151,14 @@ $sqlStatement
               }
             }
             else {
-              $clauses[] = "$fieldName LIKE {$this->toSqlWildCard($queryText)}";
+              $fullTextFields[] = $fieldName;
             }
           }
 
+          if (!empty($fullTextFields)) {
+            $clauses[] = $this->matchText($tableName, $fullTextFields, $queryText);
+          }
+
           if (empty($clauses)) {
             continue;
           }
@@ -191,6 +196,51 @@ GROUP BY {$tableValues['id']}
     return CRM_Core_DAO::singleValueQuery($rowCount);
   }
 
+  /**
+   * Create a SQL expression for matching against a list of
+   * text columns.
+   *
+   * @param string $table eg "civicrm_note" or "civicrm_note mynote"
+   * @param array|string $fullTextFields list of field names
+   * @param string $queryText
+   * @return array
+   */
+  public function matchText($table, $fullTextFields, $queryText) {
+    if (strpos($table, ' ') === FALSE) {
+      $tableName = $tableAlias = $table;
+    } else {
+      list ($tableName, $tableAlias) = explode(' ', $table);
+    }
+    if (is_scalar($fullTextFields)) {
+      $fullTextFields = array($fullTextFields);
+    }
+
+    $clauses = array();
+    if (CRM_Core_InnoDBIndexer::singleton()->hasDeclaredIndex($tableName, $fullTextFields)) {
+      $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
+
+      $prefixedFieldNames = array();
+      foreach ($fullTextFields as $fieldName) {
+        $prefixedFieldNames[] = "$tableAlias.$fieldName";
+      }
+
+      $clauses[] = sprintf("MATCH (%s) AGAINST ('%s')",
+        implode(',', $prefixedFieldNames),
+        $strtolower(CRM_Core_DAO::escapeString($queryText))
+      );
+    }
+    else {
+      //CRM_Core_Session::setStatus(ts('Cannot use FTS for %1 (%2)', array(
+      //  1 => $table,
+      //  2 => implode(', ', $fullTextFields),
+      //)));
+      foreach ($fullTextFields as $fieldName) {
+        $clauses[] = "$tableAlias.$fieldName LIKE {$this->toSqlWildCard($queryText)}";
+      }
+    }
+    return implode(' OR ', $clauses);
+  }
+
   /**
    * Format text to include wild card characters at beginning and end
    *
index 0e2d90ec50b26f3a46c424004ba12dbda5a11f3e..7b981c46ba382b90f92e562944da6cdb31ca6eeb 100644 (file)
@@ -56,6 +56,8 @@ class CRM_Contact_Form_Search_Custom_FullText_Activity extends CRM_Contact_Form_
    * @return int the total number of matches
    */
   function fillActivityIDs($queryText, $entityIDTableName, $limit) {
+    // Note: For available full-text indices, see CRM_Core_InnoDBIndexer
+
     $contactSQL = array();
 
     $contactSQL[] = "
@@ -66,10 +68,11 @@ INNER JOIN civicrm_contact c ON cat.contact_id = c.id
 LEFT  JOIN civicrm_email e ON cat.contact_id = e.contact_id
 LEFT  JOIN civicrm_option_group og ON og.name = 'activity_type'
 LEFT  JOIN civicrm_option_value ov ON ( ov.option_group_id = og.id )
-WHERE      ( (c.sort_name LIKE {$this->toSqlWildCard($queryText)} OR c.display_name LIKE {$this->toSqlWildCard($queryText)}) OR
-             ( e.email LIKE {$this->toSqlWildCard($queryText)}    AND
-               ca.activity_type_id = ov.value AND
-               ov.name IN ('Inbound Email', 'Email') ) )
+WHERE      (
+             ({$this->matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)})
+             OR
+             ({$this->matchText('civicrm_email e', 'email', $queryText)} AND ca.activity_type_id = ov.value AND ov.name IN ('Inbound Email', 'Email') )
+           )
 AND        (ca.is_deleted = 0 OR ca.is_deleted IS NULL)
 AND        (c.is_deleted = 0 OR c.is_deleted IS NULL)
 ";
@@ -81,7 +84,7 @@ INNER JOIN civicrm_tag t ON et.tag_id = t.id
 INNER JOIN civicrm_activity ca ON et.entity_id = ca.id
 WHERE      et.entity_table = 'civicrm_activity'
 AND        et.tag_id       = t.id
-AND        t.name LIKE {$this->toSqlWildCard($queryText)}
+AND        ({$this->matchText('civicrm_tag t', 'name', $queryText)})
 AND        (ca.is_deleted = 0 OR ca.is_deleted IS NULL)
 GROUP BY   et.entity_id
 ";
@@ -89,7 +92,7 @@ GROUP BY   et.entity_id
     $contactSQL[] = "
 SELECT distinct ca.id
 FROM   civicrm_activity ca
-WHERE  (ca.subject LIKE  {$this->toSqlWildCard($queryText)} OR ca.details LIKE  {$this->toSqlWildCard($queryText)})
+WHERE  ({$this->matchText('civicrm_activity ca', array('subject', 'details'), $queryText)})
 AND    (ca.is_deleted = 0 OR ca.is_deleted IS NULL)
 ";
 
index a99ac6cb665ac8c8ee10a7305ecdda2a2aed36fb..98fc5bdeca547f2df59fcc885085a8d560ef21af 100644 (file)
@@ -57,6 +57,8 @@ class CRM_Contact_Form_Search_Custom_FullText_Case extends CRM_Contact_Form_Sear
    * @return int the total number of matches
    */
   function fillCaseIDs($queryText, $entityIDTableName, $limit) {
+    // Note: For available full-text indices, see CRM_Core_InnoDBIndexer
+
     $contactSQL = array();
 
     $contactSQL[] = "
@@ -64,7 +66,7 @@ SELECT    distinct cc.id
 FROM      civicrm_case cc
 LEFT JOIN civicrm_case_contact ccc ON cc.id = ccc.case_id
 LEFT JOIN civicrm_contact c ON ccc.contact_id = c.id
-WHERE     (c.sort_name LIKE {$this->toSqlWildCard($queryText)} OR c.display_name LIKE {$this->toSqlWildCard($queryText)})
+WHERE     ({$this->matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)})
           AND (cc.is_deleted = 0 OR cc.is_deleted IS NULL)
 ";
 
@@ -85,7 +87,7 @@ FROM       civicrm_entity_tag et
 INNER JOIN civicrm_tag t ON et.tag_id = t.id
 WHERE      et.entity_table = 'civicrm_case'
 AND        et.tag_id       = t.id
-AND        t.name LIKE {$this->toSqlWildCard($queryText)}
+AND        ({$this->matchText('civicrm_tag t', 'name', $queryText)})
 GROUP BY   et.entity_id
 ";
 
index 6839a596b1cc2690116f85c06b235c5140864713..0eb806b02611a82c9a4708420ca986e499be0c0c 100644 (file)
@@ -56,6 +56,8 @@ class CRM_Contact_Form_Search_Custom_FullText_Contact extends CRM_Contact_Form_S
    * @return int the total number of matches
    */
   function fillContactIDs($queryText, $entityIDTableName, $limit) {
+    // Note: For available full-text indices, see CRM_Core_InnoDBIndexer
+
     $contactSQL = array();
     $contactSQL[] = "
 SELECT     et.entity_id
@@ -63,7 +65,7 @@ FROM       civicrm_entity_tag et
 INNER JOIN civicrm_tag t ON et.tag_id = t.id
 WHERE      et.entity_table = 'civicrm_contact'
 AND        et.tag_id       = t.id
-AND        t.name LIKE {$this->toSqlWildCard($queryText)}
+AND        ({$this->matchText('civicrm_tag t', 'name', $queryText)})
 GROUP BY   et.entity_id
 ";
 
index 219ea65a85ca40a14efede2a940a3a6a2415c5c2..b4fad6dd7e8a3e0c27a30266ea9434a937906f05 100644 (file)
@@ -60,13 +60,14 @@ class CRM_Contact_Form_Search_Custom_FullText_Contribution extends CRM_Contact_F
    * @return int the total number of matches
    */
   function fillContributionIDs($queryText, $entityIDTableName, $limit) {
+    // Note: For available full-text indices, see CRM_Core_InnoDBIndexer
+
     $contactSQL = array();
     $contactSQL[] = "
 SELECT     distinct cc.id
 FROM       civicrm_contribution cc
 INNER JOIN civicrm_contact c ON cc.contact_id = c.id
-WHERE      (c.sort_name LIKE {$this->toSqlWildCard($queryText)} OR
-           c.display_name LIKE {$this->toSqlWildCard($queryText)})
+WHERE      ({$this->matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)})
 ";
     $tables = array(
       'civicrm_contribution' => array(
@@ -76,8 +77,8 @@ WHERE      (c.sort_name LIKE {$this->toSqlWildCard($queryText)} OR
           'amount_level' => NULL,
           'trxn_Id' => NULL,
           'invoice_id' => NULL,
-          'check_number' => (is_numeric($queryText)) ? 'Int' : NULL,
-          'total_amount' => (is_numeric($queryText)) ? 'Int' : NULL,
+          'check_number' => 'Int', // Odd: This is really a VARCHAR, so why are we searching like an INT?
+          'total_amount' => 'Int',
         ),
       ),
       'sql' => $contactSQL,
index 1cebe0a93f9da9540802987243d942d5bacadadf..2ec54eeb5d8d957b56c9563894c1cf5b4561a6ac 100644 (file)
@@ -60,12 +60,14 @@ class CRM_Contact_Form_Search_Custom_FullText_Membership extends CRM_Contact_For
    * @return int the total number of matches
    */
   function fillMembershipIDs($queryText, $entityIDTableName, $limit) {
+    // Note: For available full-text indices, see CRM_Core_InnoDBIndexer
+
     $contactSQL = array();
     $contactSQL[] = "
 SELECT     distinct cm.id
 FROM       civicrm_membership cm
 INNER JOIN civicrm_contact c ON cm.contact_id = c.id
-WHERE      (c.sort_name LIKE {$this->toSqlWildCard($queryText)} OR c.display_name LIKE {$this->toSqlWildCard($queryText)})
+WHERE      ({$this->matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)})
 ";
     $tables = array(
       'civicrm_membership' => array(
index b1b623ae8be9d0767fcf22c39bf5f58bc2157d90..5af52f39bbae87bb466386c5096e0bb565bb5920 100644 (file)
@@ -60,12 +60,14 @@ class CRM_Contact_Form_Search_Custom_FullText_Participant extends CRM_Contact_Fo
    * @return int the total number of matches
    */
   function fillParticipantIDs($queryText, $entityIDTableName, $limit) {
+    // Note: For available full-text indices, see CRM_Core_InnoDBIndexer
+
     $contactSQL = array();
     $contactSQL[] = "
 SELECT     distinct cp.id
 FROM       civicrm_participant cp
 INNER JOIN civicrm_contact c ON cp.contact_id = c.id
-WHERE      (c.sort_name LIKE {$this->toSqlWildCard($queryText)} OR c.display_name LIKE {$this->toSqlWildCard($queryText)})
+WHERE      ({$this->matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)})
 ";
     $tables = array(
       'civicrm_participant' => array(
@@ -73,7 +75,7 @@ WHERE      (c.sort_name LIKE {$this->toSqlWildCard($queryText)} OR c.display_nam
         'fields' => array(
           'source' => NULL,
           'fee_level' => NULL,
-          'fee_amount' => (is_numeric($queryText)) ? 'Int' : NULL,
+          'fee_amount' => 'Int',
         ),
       ),
       'sql' => $contactSQL,
index c9bc4c9af795467ce0ade63bb9cb8bfa2a8fe5bb..cce2802e5add02e4183b6f8e3fec7ddf7b0d4ec0 100644 (file)
@@ -37,21 +37,37 @@ class CRM_Core_InnoDBIndexer {
    */
   private static $singleton = NULL;
 
+  /**
+   * @param bool $fresh
+   * @return CRM_Core_InnoDBIndexer
+   */
   public static function singleton($fresh = FALSE) {
     if ($fresh || self::$singleton === NULL) {
       $indices = array(
         'civicrm_address' => array(
           array('street_address', 'city', 'postal_code')
         ),
+        'civicrm_activity' => array(
+          array('subject', 'details'),
+        ),
         'civicrm_contact' => array(
           array('sort_name', 'nick_name', 'display_name'),
         ),
+        'civicrm_contribution' => array(
+          array('source', 'amount_level', 'trxn_Id', 'invoice_id'),
+        ),
         'civicrm_email' => array(
           array('email')
         ),
+        'civicrm_membership' => array(
+          array('source'),
+        ),
         'civicrm_note' => array(
           array('subject', 'note'),
         ),
+        'civicrm_participant' => array(
+          array('source', 'fee_level'),
+        ),
         'civicrm_phone' => array(
           array('phone'),
         ),