Problem/Motivation

Search API Solr provides a "Retrieve results directly from Solr" option that can be useful for performance.

Currently, only the itemId is passed as a select field to the query.

Proposed resolution

Add a similar server setting and pass additional fields to the select statement. Possibly any fields marked as retrievable could be a starting point. Then, in the results builder populate the Search API Item with fields and their data from Azure.

Remaining tasks

I have not had time to work on building this out but I am providing an example of how I have implemented it for my own site.

User interface changes

API changes

Data model changes

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

jacobsaw created an issue. See original summary.

jacobsaw’s picture

Example with fields returned from a search view:

/**
   * Get AI Search view display fields.
   *
   * @param \Drupal\search_api\Query\QueryInterface $query
   *   The Search API query.
   *
   * @return array[]
   *   The fields array or an empty array.
   */
  private function getSearchAiViewFields(QueryInterface $query) {
    $tags = $query->getTags();
    if (in_array('views_ai_search', $tags)) {
      /** @var \Drupal\views\Entity\View */
      $view = $this->entityTypeManager->getStorage('view')->load('ai_search');
      if ($view) {
        $display = $view->getDisplay('default');
        if ($display && isset($display['display_options']['fields'])) {
          return $display['display_options']['fields'];
        }
      }
    }
    return [];
  }

  /**
   * Set field and values for search API item.
   *
   * @param \Drupal\search_api\IndexInterface $index
   *   The search api index.
   * @param \Drupal\search_api\Item\ItemInterface $item
   *   The search api item.
   * @param \Drupal\search_api\Item\FieldInterface $field
   *   The search api field.
   * @param mixed $value
   *   The field value.
   * @param string $field_id
   *   The field id.
   */
  private function setItemField(IndexInterface $index, ItemInterface $item, FieldInterface $field, $value, $field_id) {
    $datasource_id = $field->getDatasourceId();
    $property_path = $field->getPropertyPath();
    $node_definitions = $index->getPropertyDefinitions($datasource_id);
    if (isset($node_definitions[$property_path])) {
      $search_api_field = $this->fieldsHelper
        ->createFieldFromProperty(
          $index,
          $node_definitions[$property_path],
          $datasource_id,
          $property_path,
          $field_id,
          $field->getType()
        );
      $search_api_field->setValues([$value]);
      $item->setField($field_id, $search_api_field);
    }
  }

  /**
   * Implements hook_search_api_aais_query_params_alter().
   */
  #[Hook('search_api_aais_query_params_alter')]
  public function aaisQueryParamsAlter(QueryInterface $query, &$params) {
    // Add view fields to select header to return indexed values.
    $fields = $this->getSearchAiViewFields($query);
    foreach ($fields as $field) {
      $params['select'] .= ",{$field['id']}";
    }
  }

  /**
   * Implements hook_search_api_aais_query_results_alter().
   */
  #[Hook('search_api_aais_query_results_alter')]
  public function aaisQueryResultsAlter(QueryInterface $query, ResponseDataInterface $response) {
    // Get query results and results returned from Azure.
    $result_set = $query->getResults();
    $response_results = $response->getBody();
    $fields = $this->getSearchAiViewFields($query);
    if ($result_set->getResultCount() && $fields) {
      // Get index, result items, and index fields.
      $index = $query->getIndex();
      $results = $result_set->getResultItems();
      $index_fields = $index->getFields();
      foreach ($response_results['value'] as $response_result) {
        // Try to find a matching field for each response field and populate.
        $item = $results[$response_result['itemId']];
        foreach ($fields as $field) {
          $field_id = $field['id'];
          if (isset($response_result[$field_id]) && isset($index_fields[$field_id])) {
            $index_field = $index_fields[$field_id];
            $this->setItemField($index, $item, $index_field, $response_result[$field_id], $field_id);
          }
        }
      }
    }
  }
jacobsaw’s picture

Version: 1.0.0-rc1 » 1.0.0-rc2
Assigned: Unassigned » jacobsaw

kieran.cott made their first commit to this issue’s fork.

kieran.cott’s picture

Hi @jacobsaw,

I like your idea for this as a feature, so I have taken a pass at implementing this on the `3558812-retrieve-results-directly` branch in MR 33 and rolled this up as the attached composer patch.

Interested to see what you think and whether you have any further suggestions for improvement.

kieran.cott’s picture

Status: Active » Needs review
jacobsaw’s picture

Hi Kieran,

Thanks for taking this on! Really appreciate it.

Tested and it seems to work great, I set a debug point in SearchApiFieldTrait to confirm entity loading was being skipped.

One caveat I am leaving for anyone that encounters this issue in the future, if you have entity IDs with this solution you need to set them to integer rather than string or you will encounter an error with views if some items do not have values (TypeError: round(): Argument #1 ($num) must be of type int|float, string given in round() (line 173 of /app/docroot/core/modules/views/src/Plugin/views/field/NumericField.php)). I had not noticed this until adding the patch because my workaround was skipping reference fields.

I left some very minor suggestions for improvement in an MR that I tested as well. Once you get a chance to review those, I will move this forward.

kieran.cott’s picture

Thanks, Adam.

I've reviewed and approved your suggestions in the MR, happy for you to merge those in. :)

jacobsaw’s picture

Version: 1.0.0-rc2 » 1.0.0-rc3
Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.