You are here

protected function FuzzySearchService::createKeysQuery in Fuzzy Search 7

Helper method for creating a SELECT query for given search keys.

Return value

SelectQueryInterface A SELECT query returning item_id and score (or only item_id, if $keys['#negation'] is set).

2 calls to FuzzySearchService::createKeysQuery()
FuzzySearchService::createFilterCondition in includes/service.inc
Helper method for creating a condition for filtering search results.
FuzzySearchService::search in includes/service.inc
Executes a search on the server represented by this object.

File

includes/service.inc, line 948

Class

FuzzySearchService
Search service class using the database for storing index information.

Code

protected function createKeysQuery($keys, array $fields, array $all_fields, array $fuzzy) {
  if (!is_array($keys)) {
    $keys = array(
      '#conjunction' => 'AND',
      0 => $keys,
    );
  }
  $or = db_or();
  $and = db_and();
  $neg = !empty($keys['#negation']);
  $conj = $keys['#conjunction'];
  $words = array();
  $nested = array();
  $negated = array();
  $db_query = NULL;
  $mul_words = FALSE;

  // Whether the query will nest UNIONed subqueries (FALSE)
  // or just leave them that way (TRUE).
  $not_nested = FALSE;
  foreach ($keys as $i => $key) {
    if (!element_child($i)) {
      continue;
    }
    if (is_scalar($key)) {
      $words[] = $key;
    }
    elseif (empty($key['#negation'])) {
      if ($neg) {

        // If this query is negated, we also only need item_ids from
        // subqueries.
        $key['#negation'] = TRUE;
      }
      $nested[] = $key;
    }
    else {
      $negated[] = $key;
    }
  }
  $subs = count($words) + count($nested);
  $not_nested = $subs <= 1 && count($fields) == 1 || $neg && $conj == 'OR' && !$negated;
  if ($words) {
    if (count($words) > 1) {
      $mul_words = TRUE;
      foreach ($words as $word) {
        $ngrams = fuzzysearch_parse_word($word, $fuzzy);
        foreach ($ngrams['ngrams'] as $ngram) {
          $and = db_and();
          $and
            ->condition('ngram', $ngram)
            ->condition('completeness', $ngrams['comp_min'], '>=')
            ->condition('completeness', $ngrams['comp_max'], '<=');
          $or
            ->condition($and);
        }
      }
    }
    else {
      $word = array_shift($words);
    }
    foreach ($fields as $field) {
      $table = $field['table'];
      $query = db_select($table, 't', $this->queryOptions);
      if ($neg) {
        $query
          ->fields('t', array(
          'item_id',
        ));
      }
      elseif ($not_nested) {
        $query
          ->fields('t', array(
          'item_id',
          'score',
        ));
      }
      else {
        $query
          ->fields('t');
      }
      if ($mul_words) {
        $query
          ->condition($or);
      }
      else {
        $ngrams = fuzzysearch_parse_word($word, $fuzzy);
        foreach ($ngrams['ngrams'] as $ngram) {
          $and = db_and();
          $and
            ->condition('ngram', $ngram)
            ->condition('completeness', $ngrams['comp_min'], '>=')
            ->condition('completeness', $ngrams['comp_max'], '<=');
          $or
            ->condition($and);
        }
        $query
          ->condition($or);
      }
      if (!isset($db_query)) {
        $db_query = $query;
      }
      elseif ($not_nested) {
        $db_query
          ->union($query, 'UNION');
      }
      else {
        $db_query
          ->union($query, 'UNION ALL');
      }

      // Clone the query for search excerpting.
      fuzzysearch_static_search_query(clone $db_query);

      // Group by word and then entity.
      $query
        ->groupBy('t.word_id');
      $query
        ->groupBY('t.item_id');
      $query
        ->addExpression('SUM(t.completeness)', 'percent');
      $query
        ->having('SUM(t.completeness) >= :value1', array(
        ':value1' => $fuzzy['min_completeness'],
      ));
    }
  }
  if ($nested) {
    $word = '';
    foreach ($nested as $k) {
      $query = $this
        ->createKeysQuery($k, $fields, $all_fields);
      if (!$neg) {
        $word .= ' ';
        $var = ':word' . strlen($word);
        $query
          ->addExpression($var, 'word', array(
          $var => $word,
        ));
      }
      if (!isset($db_query)) {
        $db_query = $query;
      }
      elseif ($not_nested) {
        $db_query
          ->union($query, 'UNION');
      }
      else {
        $db_query
          ->union($query, 'UNION ALL');
      }
    }
  }
  if (isset($db_query) && !$not_nested) {
    $db_query = db_select($db_query, 't', $this->queryOptions);
    $db_query
      ->addField('t', 'item_id', 'item_id');
    if (!$neg) {
      $db_query
        ->addExpression('SUM(t.score)', 'score');
      $db_query
        ->addExpression('SUM(percent)', 'percent');
      $db_query
        ->groupBy('t.item_id');
    }
    if ($conj == 'AND' && $subs > 1) {
      $var = ':subs' . (int) $subs;
      if (!$db_query
        ->getGroupBy()) {
        $db_query
          ->groupBy('t.item_id');
      }

      // @codingStandardsIgnoreStart
      //        if ($mul_words) {
      //          $db_query->having('COUNT(DISTINCT t.word) >= ' . $var, array($var => $subs));
      //        }
      //        else {
      //          $db_query->having('COUNT(DISTINCT t.word) >= ' . $var, array($var => $subs));
      //        }
      // @codingStandardsIgnoreEnd
    }
  }
  if ($negated) {
    if (!isset($db_query) || $conj == 'OR') {
      if (isset($all_fields['search_api_language'])) {

        // We use this table because all items should be contained exactly
        // once.
        $table = $all_fields['search_api_language']['table'];
      }
      else {
        $distinct = TRUE;
        foreach ($all_fields as $field) {
          $table = $field['table'];
          if (!search_api_is_list_type($field['type']) && !search_api_is_text_type($field['type'])) {
            unset($distinct);
            break;
          }
        }
      }
      if (isset($db_query)) {

        // We are in a rather bizarre case where the keys are something like
        // "a OR (NOT b)".
        $old_query = $db_query;
      }
      $db_query = db_select($table, 't', $this->queryOptions);
      $db_query
        ->addField('t', 'item_id', 'item_id');
      if (!$neg) {
        $db_query
          ->addExpression(':score', 'score', array(
          ':score' => 1,
        ));
      }
      if (isset($distinct)) {
        $db_query
          ->distinct();
      }
    }
    if ($conj == 'AND') {
      foreach ($negated as $k) {
        $db_query
          ->condition('t.item_id', $this
          ->createKeysQuery($k, $fields, $all_fields), 'NOT IN');
      }
    }
    else {
      $or = db_or();
      foreach ($negated as $k) {
        $or
          ->condition('t.item_id', $this
          ->createKeysQuery($k, $fields, $all_fields), 'NOT IN');
      }
      if (isset($old_query)) {
        $or
          ->condition('t.item_id', $old_query, 'NOT IN');
      }
      $db_query
        ->condition($or);
    }
  }
  return $db_query;
}