EntityQueryTest.php 16.8 KB
Newer Older
1 2 3 4 5 6 7 8 9
<?php

/**
 * @file
 * Definition of Drupal\system\Tests\Entity\EntityQueryTest.
 */

namespace Drupal\system\Tests\Entity;

10
use Drupal\Core\Entity\EntityStorageControllerInterface;
11
use Drupal\Core\Language\Language;
12
use Symfony\Component\HttpFoundation\Request;
13

14 15 16
/**
 * Tests the basic Entity API.
 */
17
class EntityQueryTest extends EntityUnitTestBase {
18 19 20 21 22 23

  /**
   * Modules to enable.
   *
   * @var array
   */
24
  public static $modules = array('field_test', 'language');
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59

  /**
   * @var array
   */
  protected $queryResults;

  /**
   * @var \Drupal\Core\Entity\Query\QueryFactory
   */
  protected $factory;

  /**
   * Field name for the greetings field.
   *
   * @var string
   */
  public $greetings;

  /**
   * Field name for the greetings field.
   *
   * @var string
   */
  public $figures;

  public static function getInfo() {
    return array(
      'name' => 'Entity Query',
      'description' => 'Tests Entity Query functionality.',
      'group' => 'Entity API',
    );
  }

  function setUp() {
    parent::setUp();
60
    $this->installSchema('entity_test', array('entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision'));
61
    $this->installSchema('system', array('variable'));
62 63
    $this->installConfig(array('language'));

64 65 66
    $figures = drupal_strtolower($this->randomName());
    $greetings = drupal_strtolower($this->randomName());
    foreach (array($figures => 'shape', $greetings => 'text') as $field_name => $field_type) {
67
      $field = entity_create('field_entity', array(
68 69
        'name' => $field_name,
        'entity_type' => 'entity_test_mulrev',
70 71
        'type' => $field_type,
        'cardinality' => 2,
72
        'translatable' => TRUE,
73 74 75
      ));
      $field->save();
      $fields[] = $field;
76 77 78 79
    }
    $bundles = array();
    for ($i = 0; $i < 2; $i++) {
      // For the sake of tablesort, make sure the second bundle is higher than
80
      // the first one. Beware: MySQL is not case sensitive.
81 82
      do {
        $bundle = $this->randomName();
83
      } while ($bundles && strtolower($bundles[0]) >= strtolower($bundle));
84
      entity_test_create_bundle($bundle);
85
      foreach ($fields as $field) {
86
        entity_create('field_instance', array(
87
          'field_name' => $field->name,
88
          'entity_type' => 'entity_test_mulrev',
89
          'bundle' => $bundle,
90
        ))->save();
91 92 93 94
      }
      $bundles[] = $bundle;
    }
    // Each unit is a list of field name, langcode and a column-value array.
95
    $units[] = array($figures, 'en', array(
96 97 98
      'color' => 'red',
      'shape' => 'triangle',
    ));
99
    $units[] = array($figures, 'en', array(
100 101 102 103 104 105 106 107 108 109 110 111 112 113
      'color' => 'blue',
      'shape' => 'circle',
    ));
    // To make it easier to test sorting, the greetings get formats according
    // to their langcode.
    $units[] = array($greetings, 'tr', array(
      'value' => 'merhaba',
      'format' => 'format-tr'
    ));
    $units[] = array($greetings, 'pl', array(
      'value' => 'siema',
      'format' => 'format-pl'
    ));
    // Make these languages available to the greetings field.
114
    $langcode = new Language(array(
115
      'id' => 'en',
116 117 118 119
      'name' => $this->randomString(),
    ));
    language_save($langcode);
    $langcode = new Language(array(
120
      'id' => 'tr',
121 122 123 124
      'name' => $this->randomString(),
    ));
    language_save($langcode);
    $langcode = new Language(array(
125
      'id' => 'pl',
126 127 128
      'name' => $this->randomString(),
    ));
    language_save($langcode);
129
    $field_langcodes = &drupal_static('field_available_languages');
130
    $field_langcodes['entity_test_mulrev'][$greetings] = array('tr', 'pl');
131 132 133 134 135
    // Calculate the cartesian product of the unit array by looking at the
    // bits of $i and add the unit at the bits that are 1. For example,
    // decimal 13 is binary 1101 so unit 3,2 and 0 will be added to the
    // entity.
    for ($i = 1; $i <= 15; $i++) {
136 137 138 139
      $entity = entity_create('entity_test_mulrev', array(
        'type' => $bundles[$i & 1],
        'name' => $this->randomName(),
        'langcode' => 'en',
140
      ));
141 142 143 144
      // Make sure the name is set for every language that we might create.
      foreach (array('tr', 'pl') as $langcode) {
        $entity->getTranslation($langcode)->name = $this->randomName();
      }
145 146
      foreach (array_reverse(str_split(decbin($i))) as $key => $bit) {
        if ($bit) {
147 148
          list($field_name, $langcode, $values) = $units[$key];
          $entity->getTranslation($langcode)->{$field_name}[] = $values;
149 150 151 152 153 154
        }
      }
      $entity->save();
    }
    $this->figures = $figures;
    $this->greetings = $greetings;
155
    $this->factory = \Drupal::service('entity.query');
156 157 158 159 160 161 162 163
  }

  /**
   * Test basic functionality.
   */
  function testEntityQuery() {
    $greetings = $this->greetings;
    $figures = $this->figures;
164
    $this->queryResults = $this->factory->get('entity_test_mulrev')
165 166
      ->exists($greetings, 'tr')
      ->condition("$figures.color", 'red')
167
      ->sort('id')
168 169 170 171 172
      ->execute();
    // As unit 0 was the red triangle and unit 2 was the turkish greeting,
    // bit 0 and bit 2 needs to be set.
    $this->assertResult(5, 7, 13, 15);

173
    $query = $this->factory->get('entity_test_mulrev', 'OR')
174 175
      ->exists($greetings, 'tr')
      ->condition("$figures.color", 'red')
176
      ->sort('id');
177 178 179 180 181 182 183
    $count_query = clone $query;
    $this->assertEqual(12, $count_query->count()->execute());
    $this->queryResults = $query->execute();
    // Now bit 0 (1, 3, 5, 7, 9, 11, 13, 15) or bit 2 (4, 5, 6, 7, 12, 13, 14,
    // 15) needs to be set.
    $this->assertResult(1, 3, 4, 5, 6, 7, 9, 11, 12, 13, 14, 15);

184
    // Test cloning of query conditions.
185
    $query = $this->factory->get('entity_test_mulrev')
186
      ->condition("$figures.color", 'red')
187
      ->sort('id');
188 189 190 191 192 193 194 195 196 197
    $cloned_query = clone $query;
    $cloned_query
      ->condition("$figures.shape", 'circle');
    // Bit 0 (1, 3, 5, 7, 9, 11, 13, 15) needs to be set.
    $this->queryResults = $query->execute();
    $this->assertResult(1, 3, 5, 7, 9, 11, 13, 15);
    // No red color has a circle shape.
    $this->queryResults = $cloned_query->execute();
    $this->assertResult();

198
    $query = $this->factory->get('entity_test_mulrev');
199 200 201 202 203 204
    $group = $query->orConditionGroup()
      ->exists($greetings, 'tr')
      ->condition("$figures.color", 'red');
    $this->queryResults = $query
      ->condition($group)
      ->condition("$greetings.value", 'sie', 'STARTS_WITH')
205
      ->sort('revision_id')
206 207 208 209 210
      ->execute();
    // Bit 3 and (bit 0 or 2) -- the above 8 part of the above.
    $this->assertResult(9, 11, 12, 13, 14, 15);

    // No figure has both the colors blue and red at the same time.
211
    $this->queryResults = $this->factory->get('entity_test_mulrev')
212 213
      ->condition("$figures.color", 'blue')
      ->condition("$figures.color", 'red')
214
      ->sort('id')
215 216 217 218
      ->execute();
    $this->assertResult();

    // But an entity might have a red and a blue figure both.
219
    $query = $this->factory->get('entity_test_mulrev');
220 221 222 223 224
    $group_blue = $query->andConditionGroup()->condition("$figures.color", 'blue');
    $group_red = $query->andConditionGroup()->condition("$figures.color", 'red');
    $this->queryResults = $query
      ->condition($group_blue)
      ->condition($group_red)
225
      ->sort('revision_id')
226 227 228 229
      ->execute();
    // Unit 0 and unit 1, so bits 0 1.
    $this->assertResult(3, 7, 11, 15);

230
    $this->queryResults = $this->factory->get('entity_test_mulrev')
231 232
      ->exists("$figures.color")
      ->notExists("$greetings.value")
233
      ->sort('id')
234 235 236 237 238
      ->execute();
    // Bit 0 or 1 is on but 2 and 3 are not.
    $this->assertResult(1, 2, 3);
    // Now update the 'merhaba' string to xsiemax which is not a meaningful
    // word but allows us to test revisions and string operations.
239
    $ids = $this->factory->get('entity_test_mulrev')
240 241
      ->condition("$greetings.value", 'merhaba')
      ->execute();
242
    $entities = entity_load_multiple('entity_test_mulrev', $ids);
243 244
    foreach ($entities as $entity) {
      $entity->setNewRevision();
245
      $entity->getTranslation('tr')->$greetings->value = 'xsiemax';
246 247 248
      $entity->save();
    }
    // When querying current revisions, this string is no longer found.
249
    $this->queryResults = $this->factory->get('entity_test_mulrev')
250 251 252
      ->condition("$greetings.value", 'merhaba')
      ->execute();
    $this->assertResult();
253
    $this->queryResults = $this->factory->get('entity_test_mulrev')
254
      ->condition("$greetings.value", 'merhaba')
255
      ->age(EntityStorageControllerInterface::FIELD_LOAD_REVISION)
256
      ->sort('revision_id')
257 258 259 260 261
      ->execute();
    // Bit 2 needs to be set.
    // The keys must be 16-23 because the first batch stopped at 15 so the
    // second started at 16 and eight entities were saved.
    $assert = $this->assertRevisionResult(range(16, 23), array(4, 5, 6, 7, 12, 13, 14, 15));
262
    $results = $this->factory->get('entity_test_mulrev')
263
      ->condition("$greetings.value", 'siema', 'CONTAINS')
264
      ->sort('id')
265 266 267 268 269 270
      ->execute();
    // This is the same as the previous one because xsiemax replaced merhaba
    // but also it contains the entities that siema originally but not
    // merhaba.
    $assert = array_slice($assert, 0, 4, TRUE) + array(8 => '8', 9 => '9', 10 => '10', 11 => '11') + array_slice($assert, 4, 4, TRUE);
    $this->assertIdentical($results, $assert);
271
    $results = $this->factory->get('entity_test_mulrev')
272 273 274 275 276
      ->condition("$greetings.value", 'siema', 'STARTS_WITH')
      ->execute();
    // Now we only get the ones that originally were siema, entity id 8 and
    // above.
    $this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
277
    $results = $this->factory->get('entity_test_mulrev')
278 279 280 281 282
      ->condition("$greetings.value", 'a', 'ENDS_WITH')
      ->execute();
    // It is very important that we do not get the ones which only have
    // xsiemax despite originally they were merhaba, ie. ended with a.
    $this->assertIdentical($results, array_slice($assert, 4, 8, TRUE));
283
    $results = $this->factory->get('entity_test_mulrev')
284
      ->condition("$greetings.value", 'a', 'ENDS_WITH')
285
      ->age(EntityStorageControllerInterface::FIELD_LOAD_REVISION)
286
      ->sort('id')
287 288 289 290 291 292 293 294 295 296 297 298 299 300
      ->execute();
    // Now we get everything.
    $this->assertIdentical($results, $assert);
  }

  /**
   * Test sort().
   *
   * Warning: this is complicated.
   */
  function testSort() {
    $greetings = $this->greetings;
    $figures = $this->figures;
    // Order up and down on a number.
301 302
    $this->queryResults = $this->factory->get('entity_test_mulrev')
      ->sort('id')
303 304
      ->execute();
    $this->assertResult(range(1, 15));
305 306
    $this->queryResults = $this->factory->get('entity_test_mulrev')
      ->sort('id', 'DESC')
307 308
      ->execute();
    $this->assertResult(range(15, 1));
309
    $query = $this->factory->get('entity_test_mulrev')
310 311
      ->sort("$figures.color")
      ->sort("$greetings.format")
312
      ->sort('id');
313
    // As we do not have any conditions, here are the possible colors and
314
    // language codes, already in order, with the first occurrence of the
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
    // entity id marked with *:
    // 8  NULL pl *
    // 12 NULL pl *

    // 4  NULL tr *
    // 12 NULL tr

    // 2  blue NULL *
    // 3  blue NULL *

    // 10 blue pl *
    // 11 blue pl *
    // 14 blue pl *
    // 15 blue pl *

    // 6  blue tr *
    // 7  blue tr *
    // 14 blue tr
    // 15 blue tr

    // 1  red  NULL
    // 3  red  NULL

    // 9  red  pl *
    // 11 red  pl
    // 13 red  pl *
    // 15 red  pl

    // 5  red  tr *
    // 7  red  tr
    // 13 red  tr
    // 15 red  tr
    $count_query = clone $query;
    $this->assertEqual(15, $count_query->count()->execute());
    $this->queryResults = $query->execute();
    $this->assertResult(8, 12, 4, 2, 3, 10, 11, 14, 15, 6, 7, 1, 9, 13, 5);

    // Test the pager by setting element #1 to page 2 with a page size of 4.
    // Results will be #8-12 from above.
354 355 356 357 358
    $request = Request::createFromGlobals();
    $request->query->replace(array(
      'page' => '0,2',
    ));
    \Drupal::getContainer()->set('request', $request);
359
    $this->queryResults = $this->factory->get('entity_test_mulrev')
360 361
      ->sort("$figures.color")
      ->sort("$greetings.format")
362
      ->sort('id')
363 364 365 366 367
      ->pager(4, 1)
      ->execute();
    $this->assertResult(15, 6, 7, 1);

    // Now test the reversed order.
368
    $query = $this->factory->get('entity_test_mulrev')
369 370
      ->sort("$figures.color", 'DESC')
      ->sort("$greetings.format", 'DESC')
371
      ->sort('id', 'DESC');
372 373 374 375 376 377 378 379 380 381 382 383 384
    $count_query = clone $query;
    $this->assertEqual(15, $count_query->count()->execute());
    $this->queryResults = $query->execute();
    $this->assertResult(15, 13, 7, 5, 11, 9, 3, 1, 14, 6, 10, 2, 12, 4, 8);
  }

  /**
   * Test tablesort().
   */
  protected function testTableSort() {
    // While ordering on bundles do not give us a definite order, we can still
    // assert that all entities from one bundle are after the other as the
    // order dictates.
385 386 387 388 389 390 391
    $request = Request::createFromGlobals();
    $request->query->replace(array(
      'sort' => 'asc',
      'order' => 'Type',
    ));
    \Drupal::getContainer()->set('request', $request);

392
    $header = array(
393 394
      'id' => array('data' => 'Id', 'specifier' => 'id'),
      'type' => array('data' => 'Type', 'specifier' => 'type'),
395 396
    );

397
    $this->queryResults = array_values($this->factory->get('entity_test_mulrev')
398 399 400
      ->tableSort($header)
      ->execute());
    $this->assertBundleOrder('asc');
401 402 403 404 405 406

    $request->query->add(array(
      'sort' => 'desc',
    ));
    \Drupal::getContainer()->set('request', $request);

407
    $header = array(
408 409
      'id' => array('data' => 'Id', 'specifier' => 'id'),
      'type' => array('data' => 'Type', 'specifier' => 'type'),
410
    );
411
    $this->queryResults = array_values($this->factory->get('entity_test_mulrev')
412 413 414
      ->tableSort($header)
      ->execute());
    $this->assertBundleOrder('desc');
415

416
    // Ordering on ID is definite, however.
417 418 419 420
    $request->query->add(array(
      'order' => 'Id',
    ));
    \Drupal::getContainer()->set('request', $request);
421
    $this->queryResults = $this->factory->get('entity_test_mulrev')
422 423 424 425 426
      ->tableSort($header)
      ->execute();
    $this->assertResult(range(15, 1));
  }

427
  /**
428
   * Test that count queries are separated across entity types.
429 430
   */
  protected function testCount() {
431
    // Create a field with the same name in a different entity type.
432
    $field_name = $this->figures;
433 434 435 436 437 438 439 440
    $field = entity_create('field_entity', array(
      'name' => $field_name,
      'entity_type' => 'entity_test',
      'type' => 'shape',
      'cardinality' => 2,
      'translatable' => TRUE,
    ));
    $field->save();
441
    $bundle = $this->randomName();
442
    entity_create('field_instance', array(
443
      'field_name' => $field_name,
444
      'entity_type' => 'entity_test',
445
      'bundle' => $bundle,
446
    ))->save();
447

448 449 450
    $entity = entity_create('entity_test', array(
      'id' => 1,
      'type' => $bundle,
451 452 453 454
    ));
    $entity->enforceIsNew();
    $entity->setNewRevision();
    $entity->save();
455

456 457
    // As the single entity of this type we just saved does not have a value
    // in the color field, the result should be 0.
458
    $count = $this->factory->get('entity_test')
459 460 461 462 463 464
      ->exists("$field_name.color")
      ->count()
      ->execute();
     $this->assertFalse($count);
  }

465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
  protected function assertResult() {
    $assert = array();
    $expected = func_get_args();
    if ($expected && is_array($expected[0])) {
      $expected = $expected[0];
    }
    foreach ($expected as $binary) {
      $assert[$binary] = strval($binary);
    }
    $this->assertIdentical($this->queryResults, $assert);
  }

  protected function assertRevisionResult($keys, $expected) {
    $assert = array();
    foreach ($expected as $key => $binary) {
      $assert[$keys[$key]] = strval($binary);
    }
    $this->assertIdentical($this->queryResults, $assert);
    return $assert;
  }

  protected function assertBundleOrder($order) {
    // This loop is for bundle1 entities.
    for ($i = 1; $i <= 15; $i +=2) {
      $ok = TRUE;
      $index1 = array_search($i, $this->queryResults);
      $this->assertNotIdentical($index1, FALSE, "$i found at $index1.");
      // This loop is for bundle2 entities.
      for ($j = 2; $j <= 15; $j += 2) {
        if ($ok) {
          if ($order == 'asc') {
            $ok = $index1 > array_search($j, $this->queryResults);
          }
          else {
            $ok = $index1 < array_search($j, $this->queryResults);
          }
        }
      }
      $this->assertTrue($ok, format_string("$i is after all entities in bundle2"));
    }
  }
506 507 508 509

  /**
   * Test adding a tag and metadata to the Entity query object.
   *
510
   * The tags and metadata should propagate to the SQL query object.
511 512
   */
  function testMetaData() {
513
    $query = \Drupal::entityQuery('entity_test_mulrev');
514 515 516 517 518 519
    $query
      ->addTag('efq_metadata_test')
      ->addMetaData('foo', 'bar')
      ->execute();

    global $efq_test_metadata;
520
    $this->assertEqual($efq_test_metadata, 'bar', 'Tag and metadata propagated to the SQL query object.');
521
  }
522
}