EntityFieldQuery.php 32.5 KB
Newer Older
1 2 3
<?php

/**
4
 * @file
5
 * Definition of Drupal\entity\EntityFieldQuery.
6
 */
7

8 9 10 11
namespace Drupal\entity;

use Drupal\entity\EntityFieldQueryException;
use Drupal\Core\Database\Query\Select;
12
use Drupal\Core\Database\Query\PagerSelectExtender;
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

/**
 * Retrieves entities matching a given set of conditions.
 *
 * This class allows finding entities based on entity properties (for example,
 * node->changed), field values, and generic entity meta data (bundle,
 * entity type, entity id, and revision ID). It is not possible to query across
 * multiple entity types. For example, there is no facility to find published
 * nodes written by users created in the last hour, as this would require
 * querying both node->status and user->created.
 *
 * Normally we would not want to have public properties on the object, as that
 * allows the object's state to become inconsistent too easily. However, this
 * class's standard use case involves primarily code that does need to have
 * direct access to the collected properties in order to handle alternate
 * execution routines. We therefore use public properties for simplicity. Note
 * that code that is simply creating and running a field query should still use
30
 * the appropriate methods to add conditions on the query.
31 32 33 34 35
 *
 * Storage engines are not required to support every type of query. By default,
 * an EntityFieldQueryException will be raised if an unsupported condition is
 * specified or if the query has field conditions or sorts that are stored in
 * different field storage engines. However, this logic can be overridden in
36
 * hook_entity_query_alter().
37 38 39 40
 *
 * Also note that this query does not automatically respect entity access
 * restrictions. Node access control is performed by the SQL storage engine but
 * other storage engines might not do this.
41 42
 */
class EntityFieldQuery {
43

44 45 46
  /**
   * Indicates that both deleted and non-deleted fields should be returned.
   *
47
   * @see Drupal\entity\EntityFieldQuery::deleted()
48 49 50
   */
  const RETURN_ALL = NULL;

51 52 53 54 55 56 57 58 59 60
  /**
   * TRUE if the query has already been altered, FALSE if it hasn't.
   *
   * Used in alter hooks to check for cloned queries that have already been
   * altered prior to the clone (for example, the pager count query).
   *
   * @var boolean
   */
  public $altered = FALSE;

61 62 63 64 65
  /**
   * Associative array of entity-generic metadata conditions.
   *
   * @var array
   *
66
   * @see Drupal\entity\EntityFieldQuery::entityCondition()
67 68 69 70 71 72 73 74
   */
  public $entityConditions = array();

  /**
   * List of field conditions.
   *
   * @var array
   *
75
   * @see Drupal\entity\EntityFieldQuery::fieldCondition()
76 77 78
   */
  public $fieldConditions = array();

79 80 81 82 83 84 85 86 87 88
  /**
   * List of field meta conditions (language and delta).
   *
   * Field conditions operate on columns specified by hook_field_schema(),
   * the meta conditions operate on columns added by the system: delta
   * and language. These can not be mixed with the field conditions because
   * field columns can have any name including delta and language.
   *
   * @var array
   *
89 90
   * @see Drupal\entity\EntityFieldQuery::fieldLanguageCondition()
   * @see Drupal\entity\EntityFieldQuery::fieldDeltaCondition()
91 92 93
   */
  public $fieldMetaConditions = array();

94 95 96 97 98
  /**
   * List of property conditions.
   *
   * @var array
   *
99
   * @see Drupal\entity\EntityFieldQuery::propertyCondition()
100 101 102 103
   */
  public $propertyConditions = array();

  /**
104
   * List of order clauses.
105 106 107
   *
   * @var array
   */
108
  public $order = array();
109 110 111 112 113 114

  /**
   * The query range.
   *
   * @var array
   *
115
   * @see Drupal\entity\EntityFieldQuery::range()
116 117 118
   */
  public $range = array();

119 120 121 122 123
  /**
   * The query pager data.
   *
   * @var array
   *
124
   * @see Drupal\entity\EntityFieldQuery::pager()
125 126 127
   */
  public $pager = array();

128 129 130 131 132 133
  /**
   * Query behavior for deleted data.
   *
   * TRUE to return only deleted data, FALSE to return only non-deleted data,
   * EntityFieldQuery::RETURN_ALL to return everything.
   *
134
   * @see Drupal\entity\EntityFieldQuery::deleted()
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
   */
  public $deleted = FALSE;

  /**
   * A list of field arrays used.
   *
   * Field names passed to EntityFieldQuery::fieldCondition() and
   * EntityFieldQuery::fieldOrderBy() are run through field_info_field() before
   * stored in this array. This way, the elements of this array are field
   * arrays.
   *
   * @var array
   */
  public $fields = array();

  /**
   * TRUE if this is a count query, FALSE if it isn't.
   *
   * @var boolean
   */
  public $count = FALSE;

  /**
   * Flag indicating whether this is querying current or all revisions.
   *
   * @var int
   *
162
   * @see Drupal\entity\EntityFieldQuery::age()
163 164 165
   */
  public $age = FIELD_LOAD_CURRENT;

166 167 168 169 170
  /**
   * A list of the tags added to this query.
   *
   * @var array
   *
171
   * @see Drupal\entity\EntityFieldQuery::addTag()
172 173 174 175 176 177 178 179
   */
  public $tags = array();

  /**
   * A list of metadata added to this query.
   *
   * @var array
   *
180
   * @see Drupal\entity\EntityFieldQuery::addMetaData()
181 182 183
   */
  public $metaData = array();

184 185 186 187 188
  /**
   * The ordered results.
   *
   * @var array
   *
189
   * @see Drupal\entity\EntityFieldQuery::execute().
190 191 192 193 194 195 196 197
   */
  public $orderedResults = array();

  /**
   * The method executing the query, if it is overriding the default.
   *
   * @var string
   *
198
   * @see Drupal\entity\EntityFieldQuery::execute()
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
   */
  public $executeCallback = '';

  /**
   * Adds a condition on entity-generic metadata.
   *
   * If the overall query contains only entity conditions or ordering, or if
   * there are property conditions, then specifying the entity type is
   * mandatory. If there are field conditions or ordering but no property
   * conditions or ordering, then specifying an entity type is optional. While
   * the field storage engine might support field conditions on more than one
   * entity type, there is no way to query across multiple entity base tables by
   * default. To specify the entity type, pass in 'entity_type' for $name,
   * the type as a string for $value, and no $operator (it's disregarded).
   *
   * 'bundle', 'revision_id' and 'entity_id' have no such restrictions.
   *
216
   * Note: The "comment" entity type does not support bundle conditions.
217
   *
218 219 220 221 222 223 224 225
   * @param $name
   *   'entity_type', 'bundle', 'revision_id' or 'entity_id'.
   * @param $value
   *   The value for $name. In most cases, this is a scalar. For more complex
   *   options, it is an array. The meaning of each element in the array is
   *   dependent on $operator.
   * @param $operator
   *   Possible values:
226
   *   - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These
227 228 229 230 231 232
   *     operators expect $value to be a literal of the same type as the
   *     column.
   *   - 'IN', 'NOT IN': These operators expect $value to be an array of
   *     literals of the same type as the column.
   *   - 'BETWEEN': This operator expects $value to be an array of two literals
   *     of the same type as the column.
233 234
   *   The operator can be omitted, and will default to 'IN' if the value is an
   *   array, or to '=' otherwise.
235
   *
236
   * @return Drupal\entity\EntityFieldQuery
237 238 239 240 241 242 243 244 245 246 247 248 249
   *   The called object.
   */
  public function entityCondition($name, $value, $operator = NULL) {
    $this->entityConditions[$name] = array(
      'value' => $value,
      'operator' => $operator,
    );
    return $this;
  }

  /**
   * Adds a condition on field values.
   *
250 251 252
   * Note that entities with empty field values will be excluded from the
   * EntityFieldQuery results when using this method.
   *
253 254 255 256 257 258 259 260 261 262 263
   * @param $field
   *   Either a field name or a field array.
   * @param $column
   *   The column that should hold the value to be matched.
   * @param $value
   *   The value to test the column value against.
   * @param $operator
   *   The operator to be used to test the given value.
   * @param $delta_group
   *   An arbitrary identifier: conditions in the same group must have the same
   *   $delta_group.
264
   * @param $langcode_group
265
   *   An arbitrary identifier: conditions in the same group must have the same
266
   *   $langcode_group.
267
   *
268
   * @return Drupal\entity\EntityFieldQuery
269 270
   *   The called object.
   *
271 272
   * @see Drupal\entity\EntityFieldQuery::addFieldCondition()
   * @see Drupal\entity\EntityFieldQuery::deleted()
273
   */
274 275
  public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $langcode_group = NULL) {
    return $this->addFieldCondition($this->fieldConditions, $field, $column, $value, $operator, $delta_group, $langcode_group);
276 277 278 279 280 281 282 283 284 285 286 287 288 289
  }

  /**
   * Adds a condition on the field language column.
   *
   * @param $field
   *   Either a field name or a field array.
   * @param $value
   *   The value to test the column value against.
   * @param $operator
   *   The operator to be used to test the given value.
   * @param $delta_group
   *   An arbitrary identifier: conditions in the same group must have the same
   *   $delta_group.
290
   * @param $langcode_group
291
   *   An arbitrary identifier: conditions in the same group must have the same
292
   *   $langcode_group.
293
   *
294
   * @return Drupal\entity\EntityFieldQuery
295 296
   *   The called object.
   *
297 298
   * @see Drupal\entity\EntityFieldQuery::addFieldCondition()
   * @see Drupal\entity\EntityFieldQuery::deleted()
299
   */
300 301
  public function fieldLanguageCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $langcode_group = NULL) {
    return $this->addFieldCondition($this->fieldMetaConditions, $field, 'langcode', $value, $operator, $delta_group, $langcode_group);
302 303 304 305 306 307 308 309 310 311 312 313 314 315
  }

  /**
   * Adds a condition on the field delta column.
   *
   * @param $field
   *   Either a field name or a field array.
   * @param $value
   *   The value to test the column value against.
   * @param $operator
   *   The operator to be used to test the given value.
   * @param $delta_group
   *   An arbitrary identifier: conditions in the same group must have the same
   *   $delta_group.
316
   * @param $langcode_group
317
   *   An arbitrary identifier: conditions in the same group must have the same
318
   *   $langcode_group.
319
   *
320
   * @return Drupal\entity\EntityFieldQuery
321 322
   *   The called object.
   *
323 324
   * @see Drupal\entity\EntityFieldQuery::addFieldCondition()
   * @see Drupal\entity\EntityFieldQuery::deleted()
325
   */
326 327
  public function fieldDeltaCondition($field, $value = NULL, $operator = NULL, $delta_group = NULL, $langcode_group = NULL) {
    return $this->addFieldCondition($this->fieldMetaConditions, $field, 'delta', $value, $operator, $delta_group, $langcode_group);
328 329 330 331 332 333 334
  }

  /**
   * Adds the given condition to the proper condition array.
   *
   * @param $conditions
   *   A reference to an array of conditions.
335 336 337 338 339 340 341 342 343 344 345 346
   * @param $field
   *   Either a field name or a field array.
   * @param $column
   *   A column defined in the hook_field_schema() of this field. If this is
   *   omitted then the query will find only entities that have data in this
   *   field, using the entity and property conditions if there are any.
   * @param $value
   *   The value to test the column value against. In most cases, this is a
   *   scalar. For more complex options, it is an array. The meaning of each
   *   element in the array is dependent on $operator.
   * @param $operator
   *   Possible values:
347
   *   - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These
348 349 350 351 352 353
   *     operators expect $value to be a literal of the same type as the
   *     column.
   *   - 'IN', 'NOT IN': These operators expect $value to be an array of
   *     literals of the same type as the column.
   *   - 'BETWEEN': This operator expects $value to be an array of two literals
   *     of the same type as the column.
354 355
   *   The operator can be omitted, and will default to 'IN' if the value is an
   *   array, or to '=' otherwise.
356 357 358 359 360 361 362 363 364 365
   * @param $delta_group
   *   An arbitrary identifier: conditions in the same group must have the same
   *   $delta_group. For example, let's presume a multivalue field which has
   *   two columns, 'color' and 'shape', and for entity id 1, there are two
   *   values: red/square and blue/circle. Entity ID 1 does not have values
   *   corresponding to 'red circle', however if you pass 'red' and 'circle' as
   *   conditions, it will appear in the  results - by default queries will run
   *   against any combination of deltas. By passing the conditions with the
   *   same $delta_group it will ensure that only values attached to the same
   *   delta are matched, and entity 1 would then be excluded from the results.
366
   * @param $langcode_group
367
   *   An arbitrary identifier: conditions in the same group must have the same
368
   *   $langcode_group.
369
   *
370
   * @return Drupal\entity\EntityFieldQuery
371 372
   *   The called object.
   */
373
  protected function addFieldCondition(&$conditions, $field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $langcode_group = NULL) {
374
    if (is_scalar($field)) {
375 376 377 378 379
      $field_definition = field_info_field($field);
      if (empty($field_definition)) {
        throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field)));
      }
      $field = $field_definition;
380
    }
381
    // Ensure the same index is used for field conditions as for fields.
382 383 384
    $index = count($this->fields);
    $this->fields[$index] = $field;
    if (isset($column)) {
385
      $conditions[$index] = array(
386 387 388 389 390
        'field' => $field,
        'column' => $column,
        'value' => $value,
        'operator' => $operator,
        'delta_group' => $delta_group,
391
        'langcode_group' => $langcode_group,
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
      );
    }
    return $this;
  }

  /**
   * Adds a condition on an entity-specific property.
   *
   * An $entity_type must be specified by calling
   * EntityFieldCondition::entityCondition('entity_type', $entity_type) before
   * executing the query. Also, by default only entities stored in SQL are
   * supported; however, EntityFieldQuery::executeCallback can be set to handle
   * different entity storage.
   *
   * @param $column
   *   A column defined in the hook_schema() of the base table of the entity.
   * @param $value
   *   The value to test the field against. In most cases, this is a scalar. For
   *   more complex options, it is an array. The meaning of each element in the
   *   array is dependent on $operator.
   * @param $operator
   *   Possible values:
414
   *   - '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These
415 416 417 418 419 420 421 422 423
   *     operators expect $value to be a literal of the same type as the
   *     column.
   *   - 'IN', 'NOT IN': These operators expect $value to be an array of
   *     literals of the same type as the column.
   *   - 'BETWEEN': This operator expects $value to be an array of two literals
   *     of the same type as the column.
   *   The operator can be omitted, and will default to 'IN' if the value is an
   *   array, or to '=' otherwise.
   *
424
   * @return Drupal\entity\EntityFieldQuery
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
   *   The called object.
   */
  public function propertyCondition($column, $value, $operator = NULL) {
    $this->propertyConditions[] = array(
      'column' => $column,
      'value' => $value,
      'operator' => $operator,
    );
    return $this;
  }

  /**
   * Orders the result set by entity-generic metadata.
   *
   * If called multiple times, the query will order by each specified column in
   * the order this method is called.
   *
442 443 444
   * Note: The "comment" and "taxonomy_term" entity types don't support ordering
   * by bundle. For "taxonomy_term", propertyOrderBy('vid') can be used instead.
   *
445 446 447 448 449
   * @param $name
   *   'entity_type', 'bundle', 'revision_id' or 'entity_id'.
   * @param $direction
   *   The direction to sort. Legal values are "ASC" and "DESC".
   *
450
   * @return Drupal\entity\EntityFieldQuery
451 452
   *   The called object.
   */
453
  public function entityOrderBy($name, $direction = 'ASC') {
454 455 456 457 458
    $this->order[] = array(
      'type' => 'entity',
      'specifier' => $name,
      'direction' => $direction,
    );
459 460 461 462 463 464 465
    return $this;
  }

  /**
   * Orders the result set by a given field column.
   *
   * If called multiple times, the query will order by each specified column in
466 467 468
   * the order this method is called. Note that entities with empty field
   * values will be excluded from the EntityFieldQuery results when using this
   * method.
469 470 471 472 473 474 475 476 477
   *
   * @param $field
   *   Either a field name or a field array.
   * @param $column
   *   A column defined in the hook_field_schema() of this field. entity_id and
   *   bundle can also be used.
   * @param $direction
   *   The direction to sort. Legal values are "ASC" and "DESC".
   *
478
   * @return Drupal\entity\EntityFieldQuery
479 480
   *   The called object.
   */
481
  public function fieldOrderBy($field, $column, $direction = 'ASC') {
482
    if (is_scalar($field)) {
483 484 485 486 487
      $field_definition = field_info_field($field);
      if (empty($field_definition)) {
        throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field)));
      }
      $field = $field_definition;
488
    }
489
    // Save the index used for the new field, for later use in field storage.
490 491
    $index = count($this->fields);
    $this->fields[$index] = $field;
492 493 494 495 496 497 498
    $this->order[] = array(
      'type' => 'field',
      'specifier' => array(
        'field' => $field,
        'index' => $index,
        'column' => $column,
      ),
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
      'direction' => $direction,
    );
    return $this;
  }

  /**
   * Orders the result set by an entity-specific property.
   *
   * An $entity_type must be specified by calling
   * EntityFieldCondition::entityCondition('entity_type', $entity_type) before
   * executing the query.
   *
   * If called multiple times, the query will order by each specified column in
   * the order this method is called.
   *
   * @param $column
   *   The column on which to order.
   * @param $direction
   *   The direction to sort. Legal values are "ASC" and "DESC".
   *
519
   * @return Drupal\entity\EntityFieldQuery
520 521
   *   The called object.
   */
522
  public function propertyOrderBy($column, $direction = 'ASC') {
523 524 525
    $this->order[] = array(
      'type' => 'property',
      'specifier' => $column,
526 527 528 529 530 531 532 533
      'direction' => $direction,
    );
    return $this;
  }

  /**
   * Sets the query to be a count query only.
   *
534
   * @return Drupal\entity\EntityFieldQuery
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
   *   The called object.
   */
  public function count() {
    $this->count = TRUE;
    return $this;
  }

  /**
   * Restricts a query to a given range in the result set.
   *
   * @param $start
   *   The first entity from the result set to return. If NULL, removes any
   *   range directives that are set.
   * @param $length
   *   The number of entities to return from the result set.
   *
551
   * @return Drupal\entity\EntityFieldQuery
552 553 554 555 556 557 558 559 560 561
   *   The called object.
   */
  public function range($start = NULL, $length = NULL) {
    $this->range = array(
      'start' => $start,
      'length' => $length,
    );
    return $this;
  }

562
  /**
563
   * Enables a pager for the query.
564 565 566 567 568 569 570 571
   *
   * @param $limit
   *   An integer specifying the number of elements per page.  If passed a false
   *   value (FALSE, 0, NULL), the pager is disabled.
   * @param $element
   *   An optional integer to distinguish between multiple pagers on one page.
   *   If not provided, one is automatically calculated.
   *
572
   * @return Drupal\entity\EntityFieldQuery
573 574 575 576
   *   The called object.
   */
  public function pager($limit = 10, $element = NULL) {
    if (!isset($element)) {
577
      $element = PagerSelectExtender::$maxElement++;
578
    }
579 580
    elseif ($element >= PagerSelectExtender::$maxElement) {
      PagerSelectExtender::$maxElement = $element + 1;
581 582 583 584 585 586 587 588 589 590
    }

    $this->pager = array(
      'limit' => $limit,
      'element' => $element,
    );
    return $this;
  }

  /**
591
   * Enables sortable tables for this query.
592 593
   *
   * @param $headers
594 595
   *   An EFQ Header array based on which the order clause is added to the
   *   query.
596
   *
597
   * @return Drupal\entity\EntityFieldQuery
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
   *   The called object.
   */
  public function tableSort(&$headers) {
    // If 'field' is not initialized, the header columns aren't clickable
    foreach ($headers as $key =>$header) {
      if (is_array($header) && isset($header['specifier'])) {
        $headers[$key]['field'] = '';
      }
    }

    $order = tablesort_get_order($headers);
    $direction = tablesort_get_sort($headers);
    foreach ($headers as $header) {
      if (is_array($header) && ($header['data'] == $order['name'])) {
        if ($header['type'] == 'field') {
          $this->fieldOrderBy($header['specifier']['field'], $header['specifier']['column'], $direction);
        }
        else {
          $header['direction'] = $direction;
          $this->order[] = $header;
        }
      }
    }

    return $this;
  }

625 626 627 628 629 630 631
  /**
   * Filters on the data being deleted.
   *
   * @param $deleted
   *   TRUE to only return deleted data, FALSE to return non-deleted data,
   *   EntityFieldQuery::RETURN_ALL to return everything. Defaults to FALSE.
   *
632
   * @return Drupal\entity\EntityFieldQuery
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
   *   The called object.
   */
  public function deleted($deleted = TRUE) {
    $this->deleted = $deleted;
    return $this;
  }

  /**
   * Queries the current or every revision.
   *
   * Note that this only affects field conditions. Property conditions always
   * apply to the current revision.
   * @TODO: Once revision tables have been cleaned up, revisit this.
   *
   * @param $age
   *   - FIELD_LOAD_CURRENT (default): Query the most recent revisions for all
   *     entities. The results will be keyed by entity type and entity ID.
   *   - FIELD_LOAD_REVISION: Query all revisions. The results will be keyed by
   *     entity type and entity revision ID.
   *
653
   * @return Drupal\entity\EntityFieldQuery
654 655 656 657 658 659 660
   *   The called object.
   */
  public function age($age) {
    $this->age = $age;
    return $this;
  }

661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
  /**
   * Adds a tag to the query.
   *
   * Tags are strings that mark a query so that hook_query_alter() and
   * hook_query_TAG_alter() implementations may decide if they wish to alter
   * the query. A query may have any number of tags, and they must be valid PHP
   * identifiers (composed of letters, numbers, and underscores). For example,
   * queries involving nodes that will be displayed for a user need to add the
   * tag 'node_access', so that the node module can add access restrictions to
   * the query.
   *
   * If an entity field query has tags, it must also have an entity type
   * specified, because the alter hook will need the entity base table.
   *
   * @param string $tag
   *   The tag to add.
   *
678
   * @return Drupal\entity\EntityFieldQuery
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
   *   The called object.
   */
  public function addTag($tag) {
    $this->tags[$tag] = $tag;
    return $this;
  }

  /**
   * Adds additional metadata to the query.
   *
   * Sometimes a query may need to provide additional contextual data for the
   * alter hook. The alter hook implementations may then use that information
   * to decide if and how to take action.
   *
   * @param $key
   *   The unique identifier for this piece of metadata. Must be a string that
   *   follows the same rules as any other PHP identifier.
   * @param $object
   *   The additional data to add to the query. May be any valid PHP variable.
   *
699
   * @return Drupal\entity\EntityFieldQuery
700 701 702 703 704 705 706
   *   The called object.
   */
  public function addMetaData($key, $object) {
    $this->metaData[$key] = $object;
    return $this;
  }

707 708 709 710
  /**
   * Executes the query.
   *
   * After executing the query, $this->orderedResults will contain a list of
711
   * the same entity ids in the order returned by the query. This is only
712 713 714 715 716
   * relevant if there are multiple entity types in the returned value and
   * a field ordering was requested. In every other case, the returned value
   * contains everything necessary for processing.
   *
   * @return
717
   *   Either a number if count() was called or an array of associative arrays
718
   *   of the entity ids. The outer array keys are entity types, and the inner
719 720 721 722
   *   array keys are the relevant ID. (In most cases this will be the entity
   *   ID. The only exception is when age=FIELD_LOAD_REVISION is used and field
   *   conditions or sorts are present -- in this case, the key will be the
   *   revision ID.) The entity type will only exist in the outer array if
723 724 725
   *   results were found. The inner array values consist of an object with the
   *   entity_id, revision_id and bundle properties. To traverse the returned
   *   array:
726 727 728 729 730 731 732
   *   @code
   *     foreach ($query->execute() as $entity_type => $entities) {
   *       foreach ($entities as $entity_id => $entity) {
   *   @endcode
   *   Note if the entity type is known, then the following snippet will load
   *   the entities found:
   *   @code
733
   *     $result = $query->execute();
734
   *     if (!empty($result[$my_type])) {
735
   *       $entities = entity_load_multiple($my_type, array_keys($result[$my_type]));
736
   *     }
737 738 739
   *   @endcode
   */
  public function execute() {
740
    // Give a chance to other modules to alter the query.
741
    drupal_alter('entity_query', $this);
742 743 744 745
    $this->altered = TRUE;

    // Initialize the pager.
    $this->initializePager();
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761

    // Execute the query using the correct callback.
    $result = call_user_func($this->queryCallback(), $this);

    return $result;
  }

  /**
   * Determines the query callback to use for this entity query.
   *
   * @return
   *   A callback that can be used with call_user_func().
   */
  public function queryCallback() {
    // Use the override from $this->executeCallback. It can be set either
    // while building the query, or using hook_entity_query_alter().
762
    if (function_exists($this->executeCallback)) {
763
      return $this->executeCallback;
764 765 766 767
    }
    // If there are no field conditions and sorts, and no execute callback
    // then we default to querying entity tables in SQL.
    if (empty($this->fields)) {
768
      return array($this, 'propertyQuery');
769 770 771 772 773 774 775 776 777 778
    }
    // If no override, find the storage engine to be used.
    foreach ($this->fields as $field) {
      if (!isset($storage)) {
        $storage = $field['storage']['module'];
      }
      elseif ($storage != $field['storage']['module']) {
        throw new EntityFieldQueryException(t("Can't handle more than one field storage engine"));
      }
    }
779 780 781
    if ($storage) {
      // Use hook_field_storage_query() from the field storage.
      return $storage . '_field_storage_query';
782
    }
783 784
    else {
      throw new EntityFieldQueryException(t("Field storage engine not found."));
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
    }
  }

  /**
   * Queries entity tables in SQL for property conditions and sorts.
   *
   * This method is only used if there are no field conditions and sorts.
   *
   * @return
   *   See EntityFieldQuery::execute().
   */
  protected function propertyQuery() {
    if (empty($this->entityConditions['entity_type'])) {
      throw new EntityFieldQueryException(t('For this query an entity type must be specified.'));
    }
    $entity_type = $this->entityConditions['entity_type']['value'];
    $entity_info = entity_get_info($entity_type);
    if (empty($entity_info['base table'])) {
      throw new EntityFieldQueryException(t('Entity %entity has no base table.', array('%entity' => $entity_type)));
    }
    $base_table = $entity_info['base table'];
806
    $base_table_schema = drupal_get_schema($base_table);
807 808
    $select_query = db_select($base_table);
    $select_query->addExpression(':entity_type', 'entity_type', array(':entity_type' => $entity_type));
809 810
    // Process the property conditions.
    foreach ($this->propertyConditions as $property_condition) {
811
      $this->addCondition($select_query, $base_table . '.' . $property_condition['column'], $property_condition);
812
    }
813 814 815 816 817 818
    // Process the four possible entity condition.
    // The id field is always present in entity keys.
    $sql_field = $entity_info['entity keys']['id'];
    $id_map['entity_id'] = $sql_field;
    $select_query->addField($base_table, $sql_field, 'entity_id');
    if (isset($this->entityConditions['entity_id'])) {
819
      $this->addCondition($select_query, $base_table . '.' . $sql_field, $this->entityConditions['entity_id']);
820 821 822 823 824 825 826
    }

    // If there is a revision key defined, use it.
    if (!empty($entity_info['entity keys']['revision'])) {
      $sql_field = $entity_info['entity keys']['revision'];
      $select_query->addField($base_table, $sql_field, 'revision_id');
      if (isset($this->entityConditions['revision_id'])) {
827
        $this->addCondition($select_query, $base_table . '.' . $sql_field, $this->entityConditions['revision_id']);
828 829 830 831 832 833 834 835 836 837 838 839
      }
    }
    else {
      $sql_field = 'revision_id';
      $select_query->addExpression('NULL', 'revision_id');
    }
    $id_map['revision_id'] = $sql_field;

    // Handle bundles.
    if (!empty($entity_info['entity keys']['bundle'])) {
      $sql_field = $entity_info['entity keys']['bundle'];
      $having = FALSE;
840 841 842 843

      if (!empty($base_table_schema['fields'][$sql_field])) {
        $select_query->addField($base_table, $sql_field, 'bundle');
      }
844 845 846 847 848 849 850 851
    }
    else {
      $sql_field = 'bundle';
      $select_query->addExpression(':bundle', 'bundle', array(':bundle' => $entity_type));
      $having = TRUE;
    }
    $id_map['bundle'] = $sql_field;
    if (isset($this->entityConditions['bundle'])) {
852 853 854 855 856 857 858
      if (!empty($entity_info['entity keys']['bundle'])) {
        $this->addCondition($select_query, $base_table . '.' . $sql_field, $this->entityConditions['bundle'], $having);
      }
      else {
        // This entity has no bundle, so invalidate the query.
        $select_query->where('1 = 0');
      }
859 860
    }

861 862 863 864 865 866 867 868
    // Order the query.
    foreach ($this->order as $order) {
      if ($order['type'] == 'entity') {
        $key = $order['specifier'];
        if (!isset($id_map[$key])) {
          throw new EntityFieldQueryException(t('Do not know how to order on @key for @entity_type', array('@key' => $key, '@entity_type' => $entity_type)));
        }
        $select_query->orderBy($id_map[$key], $order['direction']);
869
      }
870
      elseif ($order['type'] == 'property') {
871
        $select_query->orderBy($base_table . '.' . $order['specifier'], $order['direction']);
872 873
      }
    }
874

875 876 877
    return $this->finishQuery($select_query);
  }

878
  /**
879
   * Gets the total number of results and initialize a pager for the query.
880
   *
881 882
   * The pager can be disabled by either setting the pager limit to 0, or by
   * setting this query to be a count query.
883 884
   */
  function initializePager() {
885
    if ($this->pager && !empty($this->pager['limit']) && !$this->count) {
886 887 888 889 890 891 892 893 894
      $page = pager_find_page($this->pager['element']);
      $count_query = clone $this;
      $this->pager['total'] = $count_query->count()->execute();
      $this->pager['start'] = $page * $this->pager['limit'];
      pager_default_initialize($this->pager['total'], $this->pager['limit'], $this->pager['element']);
      $this->range($this->pager['start'], $this->pager['limit']);
    }
  }

895 896 897
  /**
   * Finishes the query.
   *
898
   * Adds tags, metaData, range and returns the requested list or count.
899 900 901 902 903 904 905 906 907 908 909
   *
   * @param SelectQuery $select_query
   *   A SelectQuery which has entity_type, entity_id, revision_id and bundle
   *   fields added.
   * @param $id_key
   *   Which field's values to use as the returned array keys.
   *
   * @return
   *   See EntityFieldQuery::execute().
   */
  function finishQuery($select_query, $id_key = 'entity_id') {
910 911 912 913 914 915 916
    foreach ($this->tags as $tag) {
      $select_query->addTag($tag);
    }
    foreach ($this->metaData as $key => $object) {
      $select_query->addMetaData($key, $object);
    }
    $select_query->addMetaData('entity_field_query', $this);
917 918 919 920 921 922 923
    if ($this->range) {
      $select_query->range($this->range['start'], $this->range['length']);
    }
    if ($this->count) {
      return $select_query->countQuery()->execute()->fetchField();
    }
    $return = array();
924 925 926 927 928 929
    foreach ($select_query->execute() as $ids) {
      if (!isset($ids->bundle)) {
        $ids->bundle = NULL;
      }
      $return[$ids->entity_type][$ids->$id_key] = $ids;
      $this->ordered_results[] = $ids;
930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949
    }
    return $return;
  }

  /**
   * Adds a condition to an already built SelectQuery (internal function).
   *
   * This is a helper for hook_entity_query() and hook_field_storage_query().
   *
   * @param SelectQuery $select_query
   *   A SelectQuery object.
   * @param $sql_field
   *   The name of the field.
   * @param $condition
   *   A condition as described in EntityFieldQuery::fieldCondition() and
   *   EntityFieldQuery::entityCondition().
   * @param $having
   *   HAVING or WHERE. This is necessary because SQL can't handle WHERE
   *   conditions on aliased columns.
   */
950
  public function addCondition(Select $select_query, $sql_field, $condition, $having = FALSE) {
951 952 953 954 955 956 957 958 959 960 961 962 963 964
    $method = $having ? 'havingCondition' : 'condition';
    $like_prefix = '';
    switch ($condition['operator']) {
      case 'CONTAINS':
        $like_prefix = '%';
      case 'STARTS_WITH':
        $select_query->$method($sql_field, $like_prefix . db_like($condition['value']) . '%', 'LIKE');
        break;
      default:
        $select_query->$method($sql_field, $condition['value'], $condition['operator']);
    }
  }

}