public function SearchApiSolrService::search in Search API Solr 7
Executes a search on the server represented by this object.
Parameters
$query: The SearchApiQueryInterface object to execute.
Return value
array An associative array containing the search results, as required by SearchApiQueryInterface::execute().
Throws
SearchApiException If an error prevented the search from completing.
Overrides SearchApiServiceInterface::search
File
- includes/
service.inc, line 887
Class
- SearchApiSolrService
- Search service class using Solr server.
Code
public function search(SearchApiQueryInterface $query) {
$time_method_called = microtime(TRUE);
// Reset request handler.
$this->request_handler = NULL;
// Get field information.
$index = $query
->getIndex();
$index_id = $this
->getIndexId($index->machine_name);
$fields = $this
->getFieldNames($index);
// Get Solr connection.
$this
->connect();
$version = $this->solr
->getSolrVersion();
// Extract keys.
$keys = $query
->getKeys();
if (is_array($keys)) {
$keys = $this
->flattenKeys($keys);
}
// Set searched fields.
$options = $query
->getOptions();
$search_fields = $this
->getQueryFields($query);
// Get the index fields to be able to retrieve boosts.
$index_fields = $index
->getFields() + array(
'search_api_relevance' => array(
'type' => 'decimal',
'indexed' => TRUE,
),
'search_api_id' => array(
'type' => 'integer',
'indexed' => TRUE,
),
);
$qf = array();
foreach ($search_fields as $f) {
$boost = isset($index_fields[$f]['boost']) ? '^' . $index_fields[$f]['boost'] : '';
$qf[] = $fields[$f] . $boost;
}
// Extract filters.
$filter = $query
->getFilter();
$fq = $this
->createFilterQueries($filter, $fields, $index->options['fields']);
$fq[] = 'index_id:' . call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $index_id);
if (!empty($this->options['site_hash'])) {
// We don't need to escape the site hash, as that consists only of
// alphanumeric characters.
$fq[] = 'hash:' . search_api_solr_site_hash();
}
// Extract sort.
$sort = array();
foreach ($query
->getSort() as $field => $order) {
$f = $fields[$field];
if (substr($f, 0, 3) == 'ss_') {
$f = 'sort_' . substr($f, 3);
}
// The default Solr schema provides a virtual field named "random_SEED"
// that can be used to randomly sort the results; the field is available
// only at query-time.
if ($field == 'search_api_random') {
$params = $query
->getOption('search_api_random_sort', array());
// Random seed: getting the value from parameters or computing a new one.
$seed = !empty($params['seed']) ? $params['seed'] : mt_rand();
$f = 'random_' . $seed;
}
$order = strtolower($order);
$sort[$field] = "{$f} {$order}";
}
// Get facet fields.
$facets = $query
->getOption('search_api_facets', array());
$facet_params = $this
->getFacetParams($facets, $fields, $fq);
// Handle highlighting.
$highlight_params = $this
->getHighlightParams($query);
// Handle More Like This query.
$mlt = $query
->getOption('search_api_mlt');
if ($mlt) {
$mlt_params['qt'] = 'mlt';
// The fields to look for similarities in.
$mlt_fl = array();
// Solr 4 (before 4.6) has a bug which results in numeric fields not being
// supported in MLT queries.
$mlt_no_numeric_fields = FALSE;
if ($version == 4) {
$system_info = $this->solr
->getSystemInfo();
$mlt_no_numeric_fields = !isset($system_info->lucene->{'solr-spec-version'}) || version_compare($system_info->lucene->{'solr-spec-version'}, '4.6.0', '<');
}
foreach ($mlt['fields'] as $f) {
// Date fields don't seem to be supported at all.
if ($fields[$f][0] === 'd' || $mlt_no_numeric_fields && in_array($fields[$f][0], array(
'i',
'f',
))) {
continue;
}
$mlt_fl[] = $fields[$f];
// For non-text fields, set minimum word length to 0.
if (isset($index->options['fields'][$f]['type']) && !search_api_is_text_type($index->options['fields'][$f]['type'])) {
$mlt_params['f.' . $fields[$f] . '.mlt.minwl'] = 0;
}
}
$mlt_params['mlt.fl'] = implode(',', $mlt_fl);
$id = $this
->createId($index_id, $mlt['id']);
$id = call_user_func(array(
$this
->getConnectionClass(),
'phrase',
), $id);
$keys = 'id:' . $id;
// In (early versions of) Solr 5, facets aren't supported with MLT.
if ($version >= 5) {
$facet_params = array();
}
}
// Handle spatial filters.
if ($spatials = $query
->getOption('search_api_location')) {
foreach ($spatials as $i => $spatial) {
// Spatial options all need a field to do anything.
if (!isset($spatial['field'])) {
continue;
}
unset($radius);
$field = $fields[$spatial['field']];
$escaped_field = call_user_func(array(
$this
->getConnectionClass(),
'escapeFieldName',
), $field);
// If proper bbox coordinates were given use them to filter.
if (isset($spatial['bbox'])) {
if ($version >= 4) {
$bbox = $spatial['bbox'];
$fq[] = $escaped_field . ':[' . (double) $bbox['bottom'] . ',' . (double) $bbox['left'] . ' TO ' . (double) $bbox['top'] . ',' . (double) $bbox['right'] . ']';
}
else {
$warnings[] = t('Filtering by a bounding box is not supported in Solr versions below 4.');
}
}
// Everything other than a bounding box filter requires a point, so stop
// here (for this option) if "lat" and "lon" aren't both set.
if (!isset($spatial['lat']) || !isset($spatial['lon'])) {
continue;
}
$point = (double) $spatial['lat'] . ',' . (double) $spatial['lon'];
// Prepare the filter settings.
if (isset($spatial['radius'])) {
$radius = (double) $spatial['radius'];
}
$spatial_method = 'geofilt';
if (isset($spatial['method']) && in_array($spatial['method'], array(
'geofilt',
'bbox',
))) {
$spatial_method = $spatial['method'];
}
// Change the fq facet ranges to the correct fq.
foreach ($fq as $key => $value) {
// If the fq consists only of a filter on this field, replace it with
// a range.
$preg_field = preg_quote($escaped_field, '/');
if (preg_match('/^(?:\\{!tag[^}]+\\})?' . $preg_field . ':\\["?(\\*|\\d+(?:\\.\\d+)?)"? TO "?(\\*|\\d+(?:\\.\\d+)?)"?\\]$/', $value, $m)) {
unset($fq[$key]);
if ($m[1] && is_numeric($m[1])) {
$min_radius = isset($min_radius) ? max($min_radius, $m[1]) : $m[1];
}
if (is_numeric($m[2])) {
// Make the radius tighter accordingly.
$radius = isset($radius) ? min($radius, $m[2]) : $m[2];
}
}
}
// If either a radius was given in the option, or a filter was
// encountered, set a filter for the lowest value. If a lower boundary
// was set (too), we can only set a filter for that if the field name
// doesn't contains any colons.
if (isset($min_radius) && strpos($field, ':') === FALSE) {
$upper = isset($radius) ? " u={$radius}" : '';
$fq[] = "{!frange l={$min_radius}{$upper}}geodist({$field},{$point})";
}
elseif (isset($radius)) {
$fq[] = "{!{$spatial_method} pt={$point} sfield={$field} d={$radius}}";
}
// Change sort on the field, if set (and not already changed).
if (isset($sort[$spatial['field']]) && substr($sort[$spatial['field']], 0, strlen($field)) === $field) {
if (strpos($field, ':') === FALSE) {
$sort[$spatial['field']] = str_replace($field, "geodist({$field},{$point})", $sort[$spatial['field']]);
}
else {
$link = l(t('edit server'), 'admin/config/search/search_api/server/' . $this->server->machine_name . '/edit');
watchdog('search_api_solr', 'Location sort on field @field had to be ignored because unclean field identifiers are used.', array(
'@field' => $spatial['field'],
), WATCHDOG_WARNING, $link);
}
}
// Add parameters to fetch distance, if requested.
if (!empty($spatial['distance']) && $version >= 4) {
if (strpos($field, ':') === FALSE) {
// Add pseudofield with the distance to the result items.
$location_fields[] = '_' . $field . '_distance_:geodist(' . $field . ',' . $point . ')';
}
else {
$link = l(t('edit server'), 'admin/config/search/search_api/server/' . $this->server->machine_name . '/edit');
watchdog('search_api_solr', "Location distance information can't be added because unclean field identifiers are used.", array(), WATCHDOG_WARNING, $link);
}
}
// Change the facet parameters for spatial fields to return distance
// facets.
if (!empty($facets)) {
if (!empty($facet_params['facet.field'])) {
$facet_params['facet.field'] = array_diff($facet_params['facet.field'], array(
$field,
));
}
foreach ($facets as $delta => $facet) {
if ($facet['field'] != $spatial['field']) {
continue;
}
$steps = $facet['limit'] > 0 ? $facet['limit'] : 5;
$step = (isset($radius) ? $radius : 100) / $steps;
for ($k = $steps - 1; $k > 0; --$k) {
$distance = $step * $k;
$key = "spatial-{$delta}-{$distance}";
$facet_params['facet.query'][] = "{!{$spatial_method} pt={$point} sfield={$field} d={$distance} key={$key}}";
}
foreach (array(
'limit',
'mincount',
'missing',
) as $setting) {
unset($facet_params["f.{$field}.facet.{$setting}"]);
}
}
}
}
}
// Normal sorting on location fields isn't possible.
foreach ($sort as $field => $sort_param) {
if (substr($sort_param, 0, 3) === 'loc') {
unset($sort[$field]);
}
}
// Handle field collapsing / grouping.
$grouping = $query
->getOption('search_api_grouping');
if (!empty($grouping['use_grouping'])) {
$group_params['group'] = 'true';
// We always want the number of groups returned so that we get pagers done
// right.
$group_params['group.ngroups'] = 'true';
if (!empty($grouping['truncate'])) {
$group_params['group.truncate'] = 'true';
}
if (!empty($grouping['group_facet'])) {
$group_params['group.facet'] = 'true';
}
foreach ($grouping['fields'] as $collapse_field) {
$type = $index_fields[$collapse_field]['type'];
// Only single-valued fields are supported.
if ($version < 4) {
// For Solr 3.x, only string and boolean fields are supported.
if (search_api_is_list_type($type) || !search_api_is_text_type($type, array(
'string',
'boolean',
'uri',
))) {
$warnings[] = t('Grouping is not supported for field @field. ' . 'Only single-valued fields of type "String", "Boolean" or "URI" are supported.', array(
'@field' => $index_fields[$collapse_field]['name'],
));
continue;
}
}
else {
if (search_api_is_list_type($type) || search_api_is_text_type($type)) {
$warnings[] = t('Grouping is not supported for field @field. ' . 'Only single-valued fields not indexed as "Fulltext" are supported.', array(
'@field' => $index_fields[$collapse_field]['name'],
));
continue;
}
}
$group_params['group.field'][] = $fields[$collapse_field];
}
if (empty($group_params['group.field'])) {
unset($group_params);
}
else {
if (!empty($grouping['group_sort'])) {
foreach ($grouping['group_sort'] as $group_sort_field => $order) {
if (isset($fields[$group_sort_field])) {
$f = $fields[$group_sort_field];
if (substr($f, 0, 3) == 'ss_') {
$f = 'sort_' . substr($f, 3);
}
// The default Solr schema provides a virtual field named
// "random_SEED" that can be used to randomly sort the results;
// the field is available only at query-time.
if ($group_sort_field == 'search_api_random') {
$params = $query
->getOption('search_api_random_sort', array());
// Random seed: getting the value from parameters or computing a
// new one.
$seed = !empty($params['seed']) ? $params['seed'] : mt_rand();
$f = 'random_' . $seed;
}
$order = strtolower($order);
$group_params['group.sort'][] = $f . ' ' . $order;
}
}
if (!empty($group_params['group.sort'])) {
$group_params['group.sort'] = implode(', ', $group_params['group.sort']);
}
}
if (!empty($grouping['group_limit']) && $grouping['group_limit'] != 1) {
$group_params['group.limit'] = $grouping['group_limit'];
}
}
}
// Set defaults.
if (!$keys) {
$keys = NULL;
}
// Collect parameters.
$params = array(
'fl' => 'item_id,score',
'qf' => $qf,
'fq' => $fq,
);
if (isset($options['offset'])) {
$params['start'] = $options['offset'];
}
$params['rows'] = isset($options['limit']) ? $options['limit'] : 1000000;
if ($sort) {
$params['sort'] = implode(', ', $sort);
}
if (!empty($facet_params['facet.field'])) {
$params += $facet_params;
}
if (!empty($highlight_params)) {
$params += $highlight_params;
}
if (!empty($options['search_api_spellcheck'])) {
$params['spellcheck'] = 'true';
}
if (!empty($mlt_params['mlt.fl'])) {
$params += $mlt_params;
}
if (!empty($group_params)) {
$params += $group_params;
}
if (!empty($this->options['retrieve_data'])) {
$params['fl'] = '*,score';
}
if (!empty($location_fields)) {
$params['fl'] .= ',' . implode(',', $location_fields);
}
// Retrieve http method from server options.
$http_method = !empty($this->options['http_method']) ? $this->options['http_method'] : 'AUTO';
$call_args = array(
'query' => &$keys,
'params' => &$params,
'http_method' => &$http_method,
);
if ($this->request_handler) {
$this
->setRequestHandler($this->request_handler, $call_args);
}
try {
// Send search request.
$time_processing_done = microtime(TRUE);
drupal_alter('search_api_solr_query', $call_args, $query);
$this
->preQuery($call_args, $query);
$response = $this->solr
->search($keys, $params, $http_method);
$time_query_done = microtime(TRUE);
// Extract results.
$results = $this
->extractResults($query, $response);
// Add warnings, if present.
if (!empty($warnings)) {
$results['warnings'] = isset($results['warnings']) ? array_merge($warnings, $results['warnings']) : $warnings;
}
// Extract facets.
if ($facets = $this
->extractFacets($query, $response)) {
$results['search_api_facets'] = $facets;
}
drupal_alter('search_api_solr_search_results', $results, $query, $response);
$this
->postQuery($results, $query, $response);
// Compute performance.
$time_end = microtime(TRUE);
$results['performance'] = array(
'complete' => $time_end - $time_method_called,
'preprocessing' => $time_processing_done - $time_method_called,
'execution' => $time_query_done - $time_processing_done,
'postprocessing' => $time_end - $time_query_done,
);
return $results;
} catch (SearchApiException $e) {
throw new SearchApiException(t('An error occurred while trying to search with Solr: @msg.', array(
'@msg' => $e
->getMessage(),
)));
}
}