You are here

protected function Database::createDbCondition in Search API 8

Creates a database query condition for a given search filter.

Used as a helper method in createDbQuery().

Parameters

\Drupal\search_api\Query\ConditionGroupInterface $conditions: The conditions for which a condition should be created.

array $fields: Internal information about the index's fields.

\Drupal\Core\Database\Query\SelectInterface $db_query: The database query to which the condition will be added.

\Drupal\search_api\IndexInterface $index: The index we're searching on.

Return value

\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 or operator was used in one of the contained conditions.

1 call to Database::createDbCondition()
Database::createDbQuery in modules/search_api_db/src/Plugin/search_api/backend/Database.php
Creates a database query for a search.

File

modules/search_api_db/src/Plugin/search_api/backend/Database.php, line 2212

Class

Database
Indexes and searches items using the database.

Namespace

Drupal\search_api_db\Plugin\search_api\backend

Code

protected function createDbCondition(ConditionGroupInterface $conditions, array $fields, SelectInterface $db_query, IndexInterface $index) {
  $conjunction = $conditions
    ->getConjunction();
  $db_condition = $db_query
    ->conditionGroupFactory($conjunction);
  $db_info = $this
    ->getIndexDbInfo($index);

  // Store the table aliases for the fields in this condition group.
  $tables = [];
  $wildcard_count = 0;
  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();
      $this
        ->validateOperator($operator);
      $not_equals_operators = [
        '<>',
        'NOT IN',
        'NOT BETWEEN',
      ];
      $not_equals = in_array($operator, $not_equals_operators);
      $not_between = $operator == 'NOT BETWEEN';
      if (!isset($fields[$field])) {
        throw new SearchApiException("Unknown field in filter clause: '{$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 = [
            '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);
        }
        elseif ($not_between) {
          $nested_condition = $db_query
            ->conditionGroupFactory('OR');
          $nested_condition
            ->condition($column, $value[0], '<');
          $nested_condition
            ->condition($column, $value[1], '>');
          $nested_condition
            ->isNull($column);
          $db_condition
            ->condition($nested_condition);
        }
        elseif ($not_equals) {

          // Since SQL never returns TRUE for comparison with NULL values, we
          // need to include "OR field IS NULL" explicitly for some operators.
          $nested_condition = $db_query
            ->conditionGroupFactory('OR');
          $nested_condition
            ->condition($column, $value, $operator);
          $nested_condition
            ->isNull($column);
          $db_condition
            ->condition($nested_condition);
        }
        else {
          $db_condition
            ->condition($column, $value, $operator);
        }
      }
      elseif ($this
        ->getDataTypeHelper()
        ->isTextType($field_info['type'])) {
        $tokenizer_active = $index
          ->isValidProcessor('tokenizer');
        $keys = $this
          ->prepareKeys($value, $tokenizer_active);
        if (!isset($keys)) {
          continue;
        }
        $query = $this
          ->createKeysQuery($keys, [
          $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', [
          'item_id',
        ]);
        $db_condition
          ->condition('t.item_id', $query, $not_equals ? 'NOT IN' : 'IN');
      }
      elseif ($not_equals) {

        // The situation is more complicated for negative conditions on
        // multi-valued fields, since we must make sure that results are
        // excluded if ANY of the field's values equals the one(s) given in
        // this condition. Probably the most performant way to do this is to
        // do a LEFT JOIN with a positive filter on the excluded values in the
        // ON clause and then make sure we have no value for the field.
        if ($not_between) {
          $wildcard1 = ':values_' . ++$wildcard_count;
          $wildcard2 = ':values_' . ++$wildcard_count;
          $arguments = array_combine([
            $wildcard1,
            $wildcard2,
          ], $value);
          $additional_on = "%alias.value BETWEEN {$wildcard1} AND {$wildcard2}";
        }
        else {
          $wildcard = ':values_' . ++$wildcard_count . '[]';
          $arguments = [
            $wildcard => (array) $value,
          ];
          $additional_on = "%alias.value IN ({$wildcard})";
        }
        $alias = $this
          ->getTableAlias($field_info, $db_query, TRUE, 'leftJoin', $additional_on, $arguments);
        $db_condition
          ->isNull($alias . '.value');
      }
      else {

        // We need to join the table if it hasn't been joined (for this
        // condition group) before, or if we have "AND" as the active
        // conjunction.
        if ($conjunction == 'AND' || empty($tables[$field])) {
          $tables[$field] = $this
            ->getTableAlias($field_info, $db_query, TRUE);
        }
        $column = $tables[$field] . '.value';
        $db_condition
          ->condition($column, $value, $operator);
      }
    }
  }
  return $db_condition
    ->count() ? $db_condition : NULL;
}