/** * Creates a database query condition for a given search filter. * * Used as a helper method in createDbQuery(). * * @param \Drupal\search_api\Query\ConditionGroupInterface $conditions * The conditions for which a condition should be created. * @param array $fields * Internal information about the index's fields. * @param \Drupal\Core\Database\Query\SelectInterface $db_query * The database query to which the condition will be added. * @param \Drupal\search_api\IndexInterface $index * The index we're searching on. * * @return \Drupal\Core\Database\Query\ConditionInterface|null * The condition to set on the query, or NULL if none is necessary. * * @throws \Drupal\search_api\SearchApiException * Thrown if an unknown field was used in the filter. */ protected function createDbCondition(ConditionGroupInterface $conditions, array $fields, SelectInterface $db_query, IndexInterface $index) { $db_condition = new Condition($conditions->getConjunction()); $db_info = $this->getIndexDbInfo($index); // Store whether a JOIN already occurred for a field, so we don't JOIN // repeatedly for OR filters. $first_join = array(); // Store the table aliases for the fields in this condition group. $tables = array(); foreach ($conditions->getConditions() as $condition) { if ($condition instanceof ConditionGroupInterface) { $sub_condition = $this->createDbCondition($condition, $fields, $db_query, $index); if ($sub_condition) { $db_condition->condition($sub_condition); } } else { $field = $condition->getField(); $operator = $condition->getOperator(); $value = $condition->getValue(); $not_equals = $operator == '<>' || $operator == '!='; // We don't index the datasource explicitly, so this needs a bit of // magic. // @todo Index the datasource explicitly so this doesn't need magic. if ($field === 'search_api_datasource') { if (empty($tables[NULL])) { $table = array('table' => $db_info['index_table']); $tables[NULL] = $this->getTableAlias($table, $db_query); } $operator = $not_equals ? 'NOT LIKE' : 'LIKE'; $prefix = Utility::createCombinedId($value, ''); $db_condition->condition($tables[NULL] . '.item_id', $this->database->escapeLike($prefix) . '%', $operator); continue; } if (!isset($fields[$field])) { throw new SearchApiException(new FormattableMarkup('Unknown field in filter clause: @field.', array('@field' => $field))); } $field_info = $fields[$field]; // For NULL values, we can just use the single-values table, since we // only need to know if there's any value at all for that field. if ($value === NULL || empty($field_info['multi-valued'])) { if (empty($tables[NULL])) { $table = array('table' => $db_info['index_table']); $tables[NULL] = $this->getTableAlias($table, $db_query); } $column = $tables[NULL] . '.' . $field_info['column']; if ($value === NULL) { $method = $not_equals ? 'isNotNull' : 'isNull'; $db_condition->{$method}($column); } else { $db_condition->condition($column, $value, $operator); } continue; } if (Utility::isTextType($field_info['type'])) { $keys = $this->prepareKeys($value); if (!isset($keys)) { continue; } $query = $this->createKeysQuery($keys, array($field => $field_info), $fields, $index); // We only want the item IDs, so we use the keys query as a nested // query. $query = $this->database->select($query, 't')->fields('t', array('item_id')); $db_condition->condition('t.item_id', $query, $not_equals ? 'NOT IN' : 'IN'); } else { $new_join = $conditions->getConjunction() == 'AND' || empty($first_join[$field]); if ($new_join || empty($tables[$field])) { $tables[$field] = $this->getTableAlias($field_info, $db_query, $new_join); $first_join[$field] = TRUE; } $column = $tables[$field] . '.' . 'value'; if ($not_equals) { // The situation is more complicated for multi-valued fields, since // we must make sure that results are excluded if ANY of the field's // values equals the one given in this condition. $query = $this->database->select($field_info['table'], 't')->fields('t', array('item_id'))->condition('value', $value); $db_condition->condition('t.item_id', $query, 'NOT IN'); } else { $db_condition->condition($column, $value, $operator); } } } } return $db_condition->count() ? $db_condition : NULL; }
/** * Transforms a query filter into a flat array of Solr filter queries, using * the field names in $field_names. */ protected function createFilterQueries(ConditionGroupInterface $conditions, array $solr_fields, array $index_fields) { $or = $conditions->getConjunction() == 'OR'; $fq = array(); foreach ($conditions->getConditions() as $condition) { if ($condition instanceof ConditionInterface) { $field = $condition->getField(); if (!isset($index_fields[$field])) { throw new SearchApiException(t('Filter term on unknown or unindexed field @field.', array('@field' => $field))); } $value = $condition->getValue(); if ($value !== '') { $fq[] = $this->createFilterQuery($solr_fields[$field], $value, $condition->getOperator(), $index_fields[$field]); } } else { $q = $this->createFilterQueries($condition, $solr_fields, $index_fields); if ($conditions->getConjunction() != $condition->getConjunction()) { // $or == TRUE means the nested filter has conjunction AND, and vice versa $sep = $or ? ' ' : ' OR '; $fq[] = count($q) == 1 ? reset($q) : '((' . implode(')' . $sep . '(', $q) . '))'; } else { $fq = array_merge($fq, $q); } } } return $or && count($fq) > 1 ? array('((' . implode(') OR (', $fq) . '))') : $fq; }