You are here

protected function SearchApiDbService::getFacets in Search API Database Search 7

Computes facets for a search query.

Parameters

SearchApiQueryInterface $query: The search query for which facets should be computed.

SelectQueryInterface $db_query: A database select query which returns all results of that search query.

Return value

array An array of facets, as specified by the search_api_facets feature.

1 call to SearchApiDbService::getFacets()
SearchApiDbService::search in ./service.inc
Executes a search on the server represented by this object.

File

./service.inc, line 1924
Contains SearchApiDbService.

Class

SearchApiDbService
Indexes and searches items using the database.

Code

protected function getFacets(SearchApiQueryInterface $query, SelectQueryInterface $db_query) {
  try {

    // Add a tag to the database query to identify it as a facet base query.
    $db_query
      ->addTag('search_api_db_facets_base');

    // Store the results of the query in a temporary table to run facet
    // queries on it afterwards.
    $table = $this
      ->getTemporaryResultsTable($db_query);
    if (!$table) {
      return array();
    }
    $fields = $this
      ->getFieldInfo($query
      ->getIndex());
    $ret = array();
    foreach ($query
      ->getOption('search_api_facets') as $key => $facet) {
      if (empty($fields[$facet['field']])) {
        $this->warnings[] = t('Unknown facet field @field.', array(
          '@field' => $facet['field'],
        ));
        continue;
      }
      $field = $fields[$facet['field']];
      if (empty($facet['operator']) || $facet['operator'] != 'or') {

        // All the AND facets can use the main query.
        $select = $this->connection
          ->select($table, 't');
      }
      else {

        // For OR facets, we need to build a different base query that
        // excludes the facet filters applied to the facet.
        $or_query = clone $query;
        $filters =& $or_query
          ->getFilter()
          ->getFilters();
        $tag = 'facet:' . $facet['field'];
        foreach ($filters as $filter_id => $filter) {
          if ($filter instanceof SearchApiQueryFilterInterface && $filter
            ->hasTag($tag)) {
            unset($filters[$filter_id]);
          }
        }
        $or_db_query = $this
          ->createDbQuery($or_query, $fields);
        $select = $this->connection
          ->select($or_db_query, 't');
      }

      // Add tags and metadata.
      $select
        ->addTag('search_api_db_facet');
      $select
        ->addMetaData('search_api_query', $query);
      $select
        ->addMetaData('search_api_db_fields', $fields);
      $select
        ->addMetaData('search_api_db_facet', $facet);

      // If "Include missing facet" is disabled, we use an INNER JOIN and add
      // IS NOT NULL for shared tables.
      $is_text_type = search_api_is_text_type($field['type']);
      $alias = $this
        ->getTableAlias($field, $select, TRUE, $facet['missing'] ? 'leftJoin' : 'innerJoin');
      $select
        ->addField($alias, $field['column'], 'value');
      if ($is_text_type) {
        $select
          ->condition("{$alias}.field_name", $this
          ->getTextFieldName($facet['field']));
      }
      if (!$facet['missing'] && !$is_text_type) {
        $select
          ->isNotNull($alias . '.' . $field['column']);
      }
      $select
        ->addExpression('COUNT(DISTINCT t.item_id)', 'num');
      $select
        ->groupBy('value');
      $select
        ->orderBy('num', 'DESC');
      $limit = $facet['limit'];
      if ((int) $limit > 0) {
        $select
          ->range(0, $limit);
      }
      if ($facet['min_count'] > 1) {
        $select
          ->having('COUNT(DISTINCT t.item_id) >= :count', array(
          ':count' => $facet['min_count'],
        ));
      }
      $terms = array();
      $values = array();
      $has_missing = FALSE;
      foreach ($select
        ->execute() as $row) {
        $terms[] = array(
          'count' => $row->num,
          'filter' => isset($row->value) ? '"' . $row->value . '"' : '!',
        );
        if (isset($row->value)) {
          $values[] = $row->value;
        }
        else {
          $has_missing = TRUE;
        }
      }

      // If 'Minimum facet count' is set to 0 in the display options for this
      // facet, we need to retrieve all facets, even ones that aren't matched
      // in our search result set above. Here we SELECT all DISTINCT facets,
      // and add in those facets that weren't added above.
      if ($facet['min_count'] < 1) {
        $select = $this->connection
          ->select($field['table'], 't');
        $select
          ->addField('t', $field['column'], 'value');
        $select
          ->distinct();
        if ($values) {
          $select
            ->condition($field['column'], $values, 'NOT IN');
        }
        if ($is_text_type) {
          $select
            ->condition('t.field_name', $this
            ->getTextFieldName($facet['field']));
        }
        else {
          $select
            ->isNotNull($field['column']);
        }

        // Add tags and metadata.
        $select
          ->addTag('search_api_db_facet_all');
        $select
          ->addMetaData('search_api_query', $query);
        $select
          ->addMetaData('search_api_db_fields', $fields);
        $select
          ->addMetaData('search_api_db_facet', $facet);
        foreach ($select
          ->execute() as $row) {
          $terms[] = array(
            'count' => 0,
            'filter' => '"' . $row->value . '"',
          );
        }
        if ($facet['missing'] && !$has_missing) {
          $terms[] = array(
            'count' => 0,
            'filter' => '!',
          );
        }
      }
      $ret[$key] = $terms;
    }
    return $ret;
  } catch (PDOException $e) {
    watchdog_exception('search_api_db', $e, '%type while trying to calculate facets: !message in %function (line %line of %file).');
    return array();
  }
}