Projet

Général

Profil

Paste
Télécharger (140 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / simpletest / tests / database_test.test @ f7a2490e

1
<?php
2

    
3
/**
4
 * Dummy class for fetching into a class.
5
 *
6
 * PDO supports using a new instance of an arbitrary class for records
7
 * rather than just a stdClass or array. This class is for testing that
8
 * functionality. (See testQueryFetchClass() below)
9
 */
10
class FakeRecord { }
11

    
12
/**
13
 * Base test class for databases.
14
 *
15
 * Because all database tests share the same test data, we can centralize that
16
 * here.
17
 */
18
class DatabaseTestCase extends DrupalWebTestCase {
19
  protected $profile = 'testing';
20

    
21
  function setUp() {
22
    parent::setUp('database_test');
23

    
24
    $schema['test'] = drupal_get_schema('test');
25
    $schema['test_people'] = drupal_get_schema('test_people');
26
    $schema['test_people_copy'] = drupal_get_schema('test_people_copy');
27
    $schema['test_one_blob'] = drupal_get_schema('test_one_blob');
28
    $schema['test_two_blobs'] = drupal_get_schema('test_two_blobs');
29
    $schema['test_task'] = drupal_get_schema('test_task');
30

    
31
    $this->installTables($schema);
32

    
33
    $this->addSampleData();
34
  }
35

    
36
  /**
37
   * Set up several tables needed by a certain test.
38
   *
39
   * @param $schema
40
   *   An array of table definitions to install.
41
   */
42
  function installTables($schema) {
43
    // This ends up being a test for table drop and create, too, which is nice.
44
    foreach ($schema as $name => $data) {
45
      if (db_table_exists($name)) {
46
        db_drop_table($name);
47
      }
48
      db_create_table($name, $data);
49
    }
50

    
51
    foreach ($schema as $name => $data) {
52
      $this->assertTrue(db_table_exists($name), format_string('Table @name created successfully.', array('@name' => $name)));
53
    }
54
  }
55

    
56
  /**
57
   * Set up tables for NULL handling.
58
   */
59
  function ensureSampleDataNull() {
60
    $schema['test_null'] = drupal_get_schema('test_null');
61
    $this->installTables($schema);
62

    
63
    db_insert('test_null')
64
    ->fields(array('name', 'age'))
65
    ->values(array(
66
      'name' => 'Kermit',
67
      'age' => 25,
68
    ))
69
    ->values(array(
70
      'name' => 'Fozzie',
71
      'age' => NULL,
72
    ))
73
    ->values(array(
74
      'name' => 'Gonzo',
75
      'age' => 27,
76
    ))
77
    ->execute();
78
  }
79

    
80
  /**
81
   * Setup our sample data.
82
   *
83
   * These are added using db_query(), since we're not trying to test the
84
   * INSERT operations here, just populate.
85
   */
86
  function addSampleData() {
87
    // We need the IDs, so we can't use a multi-insert here.
88
    $john = db_insert('test')
89
      ->fields(array(
90
        'name' => 'John',
91
        'age' => 25,
92
        'job' => 'Singer',
93
      ))
94
      ->execute();
95

    
96
    $george = db_insert('test')
97
      ->fields(array(
98
        'name' => 'George',
99
        'age' => 27,
100
        'job' => 'Singer',
101
      ))
102
      ->execute();
103

    
104
    $ringo = db_insert('test')
105
      ->fields(array(
106
        'name' => 'Ringo',
107
        'age' => 28,
108
        'job' => 'Drummer',
109
      ))
110
      ->execute();
111

    
112
    $paul = db_insert('test')
113
      ->fields(array(
114
        'name' => 'Paul',
115
        'age' => 26,
116
        'job' => 'Songwriter',
117
      ))
118
      ->execute();
119

    
120
    db_insert('test_people')
121
      ->fields(array(
122
        'name' => 'Meredith',
123
        'age' => 30,
124
        'job' => 'Speaker',
125
      ))
126
      ->execute();
127

    
128
    db_insert('test_task')
129
      ->fields(array('pid', 'task', 'priority'))
130
      ->values(array(
131
        'pid' => $john,
132
        'task' => 'eat',
133
        'priority' => 3,
134
      ))
135
      ->values(array(
136
        'pid' => $john,
137
        'task' => 'sleep',
138
        'priority' => 4,
139
      ))
140
      ->values(array(
141
        'pid' => $john,
142
        'task' => 'code',
143
        'priority' => 1,
144
      ))
145
      ->values(array(
146
        'pid' => $george,
147
        'task' => 'sing',
148
        'priority' => 2,
149
      ))
150
      ->values(array(
151
        'pid' => $george,
152
        'task' => 'sleep',
153
        'priority' => 2,
154
      ))
155
      ->values(array(
156
        'pid' => $paul,
157
        'task' => 'found new band',
158
        'priority' => 1,
159
      ))
160
      ->values(array(
161
        'pid' => $paul,
162
        'task' => 'perform at superbowl',
163
        'priority' => 3,
164
      ))
165
      ->execute();
166
  }
167
}
168

    
169
/**
170
 * Test connection management.
171
 */
172
class DatabaseConnectionTestCase extends DatabaseTestCase {
173

    
174
  public static function getInfo() {
175
    return array(
176
      'name' => 'Connection tests',
177
      'description' => 'Tests of the core database system.',
178
      'group' => 'Database',
179
    );
180
  }
181

    
182
  /**
183
   * Test that connections return appropriate connection objects.
184
   */
185
  function testConnectionRouting() {
186
    // Clone the master credentials to a slave connection.
187
    // Note this will result in two independent connection objects that happen
188
    // to point to the same place.
189
    $connection_info = Database::getConnectionInfo('default');
190
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
191

    
192
    $db1 = Database::getConnection('default', 'default');
193
    $db2 = Database::getConnection('slave', 'default');
194

    
195
    $this->assertNotNull($db1, 'default connection is a real connection object.');
196
    $this->assertNotNull($db2, 'slave connection is a real connection object.');
197
    $this->assertNotIdentical($db1, $db2, 'Each target refers to a different connection.');
198

    
199
    // Try to open those targets another time, that should return the same objects.
200
    $db1b = Database::getConnection('default', 'default');
201
    $db2b = Database::getConnection('slave', 'default');
202
    $this->assertIdentical($db1, $db1b, 'A second call to getConnection() returns the same object.');
203
    $this->assertIdentical($db2, $db2b, 'A second call to getConnection() returns the same object.');
204

    
205
    // Try to open an unknown target.
206
    $unknown_target = $this->randomName();
207
    $db3 = Database::getConnection($unknown_target, 'default');
208
    $this->assertNotNull($db3, 'Opening an unknown target returns a real connection object.');
209
    $this->assertIdentical($db1, $db3, 'An unknown target opens the default connection.');
210

    
211
    // Try to open that unknown target another time, that should return the same object.
212
    $db3b = Database::getConnection($unknown_target, 'default');
213
    $this->assertIdentical($db3, $db3b, 'A second call to getConnection() returns the same object.');
214
  }
215

    
216
  /**
217
   * Test that connections return appropriate connection objects.
218
   */
219
  function testConnectionRoutingOverride() {
220
    // Clone the master credentials to a slave connection.
221
    // Note this will result in two independent connection objects that happen
222
    // to point to the same place.
223
    $connection_info = Database::getConnectionInfo('default');
224
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
225

    
226
    Database::ignoreTarget('default', 'slave');
227

    
228
    $db1 = Database::getConnection('default', 'default');
229
    $db2 = Database::getConnection('slave', 'default');
230

    
231
    $this->assertIdentical($db1, $db2, 'Both targets refer to the same connection.');
232
  }
233

    
234
  /**
235
   * Tests the closing of a database connection.
236
   */
237
  function testConnectionClosing() {
238
    // Open the default target so we have an object to compare.
239
    $db1 = Database::getConnection('default', 'default');
240

    
241
    // Try to close the the default connection, then open a new one.
242
    Database::closeConnection('default', 'default');
243
    $db2 = Database::getConnection('default', 'default');
244

    
245
    // Opening a connection after closing it should yield an object different than the original.
246
    $this->assertNotIdentical($db1, $db2, 'Opening the default connection after it is closed returns a new object.');
247
  }
248

    
249
  /**
250
   * Tests the connection options of the active database.
251
   */
252
  function testConnectionOptions() {
253
    $connection_info = Database::getConnectionInfo('default');
254

    
255
    // Be sure we're connected to the default database.
256
    $db = Database::getConnection('default', 'default');
257
    $connectionOptions = $db->getConnectionOptions();
258

    
259
    // In the MySQL driver, the port can be different, so check individual
260
    // options.
261
    $this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], 'The default connection info driver matches the current connection options driver.');
262
    $this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], 'The default connection info database matches the current connection options database.');
263

    
264
    // Set up identical slave and confirm connection options are identical.
265
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
266
    $db2 = Database::getConnection('slave', 'default');
267
    $connectionOptions2 = $db2->getConnectionOptions();
268

    
269
    // Get a fresh copy of the default connection options.
270
    $connectionOptions = $db->getConnectionOptions();
271
    $this->assertIdentical($connectionOptions, $connectionOptions2, 'The default and slave connection options are identical.');
272

    
273
    // Set up a new connection with different connection info.
274
    $test = $connection_info['default'];
275
    $test['database'] .= 'test';
276
    Database::addConnectionInfo('test', 'default', $test);
277
    $connection_info = Database::getConnectionInfo('test');
278

    
279
    // Get a fresh copy of the default connection options.
280
    $connectionOptions = $db->getConnectionOptions();
281
    $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.');
282
  }
283
}
284

    
285
/**
286
 * Test cloning Select queries.
287
 */
288
class DatabaseSelectCloneTest extends DatabaseTestCase {
289

    
290
  public static function getInfo() {
291
    return array(
292
      'name' => 'Select tests, cloning',
293
      'description' => 'Test cloning Select queries.',
294
      'group' => 'Database',
295
    );
296
  }
297

    
298
  /**
299
   * Test that subqueries as value within conditions are cloned properly.
300
   */
301
  function testSelectConditionSubQueryCloning() {
302
    $subquery = db_select('test', 't');
303
    $subquery->addField('t', 'id', 'id');
304
    $subquery->condition('age', 28, '<');
305

    
306
    $query = db_select('test', 't');
307
    $query->addField('t', 'name', 'name');
308
    $query->condition('id', $subquery, 'IN');
309

    
310
    $clone = clone $query;
311
    // Cloned query should not be altered by the following modification
312
    // happening on original query.
313
    $subquery->condition('age', 25, '>');
314

    
315
    $clone_result = $clone->countQuery()->execute()->fetchField();
316
    $query_result = $query->countQuery()->execute()->fetchField();
317

    
318
    // Make sure the cloned query has not been modified
319
    $this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows');
320
    $this->assertEqual(2, $query_result, 'The query returns the expected number of rows');
321
  }
322
}
323

    
324
/**
325
 * Test fetch actions, part 1.
326
 *
327
 * We get timeout errors if we try to run too many tests at once.
328
 */
329
class DatabaseFetchTestCase extends DatabaseTestCase {
330

    
331
  public static function getInfo() {
332
    return array(
333
      'name' => 'Fetch tests',
334
      'description' => 'Test the Database system\'s various fetch capabilities.',
335
      'group' => 'Database',
336
    );
337
  }
338

    
339
  /**
340
   * Confirm that we can fetch a record properly in default object mode.
341
   */
342
  function testQueryFetchDefault() {
343
    $records = array();
344
    $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25));
345
    $this->assertTrue($result instanceof DatabaseStatementInterface, 'Result set is a Drupal statement object.');
346
    foreach ($result as $record) {
347
      $records[] = $record;
348
      $this->assertTrue(is_object($record), 'Record is an object.');
349
      $this->assertIdentical($record->name, 'John', '25 year old is John.');
350
    }
351

    
352
    $this->assertIdentical(count($records), 1, 'There is only one record.');
353
  }
354

    
355
  /**
356
   * Confirm that we can fetch a record to an object explicitly.
357
   */
358
  function testQueryFetchObject() {
359
    $records = array();
360
    $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_OBJ));
361
    foreach ($result as $record) {
362
      $records[] = $record;
363
      $this->assertTrue(is_object($record), 'Record is an object.');
364
      $this->assertIdentical($record->name, 'John', '25 year old is John.');
365
    }
366

    
367
    $this->assertIdentical(count($records), 1, 'There is only one record.');
368
  }
369

    
370
  /**
371
   * Confirm that we can fetch a record to an array associative explicitly.
372
   */
373
  function testQueryFetchArray() {
374
    $records = array();
375
    $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_ASSOC));
376
    foreach ($result as $record) {
377
      $records[] = $record;
378
      if ($this->assertTrue(is_array($record), 'Record is an array.')) {
379
        $this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.');
380
      }
381
    }
382

    
383
    $this->assertIdentical(count($records), 1, 'There is only one record.');
384
  }
385

    
386
  /**
387
   * Confirm that we can fetch a record into a new instance of a custom class.
388
   *
389
   * @see FakeRecord
390
   */
391
  function testQueryFetchClass() {
392
    $records = array();
393
    $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => 'FakeRecord'));
394
    foreach ($result as $record) {
395
      $records[] = $record;
396
      if ($this->assertTrue($record instanceof FakeRecord, 'Record is an object of class FakeRecord.')) {
397
        $this->assertIdentical($record->name, 'John', '25 year old is John.');
398
      }
399
    }
400

    
401
    $this->assertIdentical(count($records), 1, 'There is only one record.');
402
  }
403
}
404

    
405
/**
406
 * Test fetch actions, part 2.
407
 *
408
 * We get timeout errors if we try to run too many tests at once.
409
 */
410
class DatabaseFetch2TestCase extends DatabaseTestCase {
411

    
412
  public static function getInfo() {
413
    return array(
414
      'name' => 'Fetch tests, part 2',
415
      'description' => 'Test the Database system\'s various fetch capabilities.',
416
      'group' => 'Database',
417
    );
418
  }
419

    
420
  function setUp() {
421
    parent::setUp();
422
  }
423

    
424
  // Confirm that we can fetch a record into an indexed array explicitly.
425
  function testQueryFetchNum() {
426
    $records = array();
427
    $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_NUM));
428
    foreach ($result as $record) {
429
      $records[] = $record;
430
      if ($this->assertTrue(is_array($record), 'Record is an array.')) {
431
        $this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.');
432
      }
433
    }
434

    
435
    $this->assertIdentical(count($records), 1, 'There is only one record');
436
  }
437

    
438
  /**
439
   * Confirm that we can fetch a record into a doubly-keyed array explicitly.
440
   */
441
  function testQueryFetchBoth() {
442
    $records = array();
443
    $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_BOTH));
444
    foreach ($result as $record) {
445
      $records[] = $record;
446
      if ($this->assertTrue(is_array($record), 'Record is an array.')) {
447
        $this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.');
448
        $this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.');
449
      }
450
    }
451

    
452
    $this->assertIdentical(count($records), 1, 'There is only one record.');
453
  }
454

    
455
  /**
456
   * Confirm that we can fetch an entire column of a result set at once.
457
   */
458
  function testQueryFetchCol() {
459
    $records = array();
460
    $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25));
461
    $column = $result->fetchCol();
462
    $this->assertIdentical(count($column), 3, 'fetchCol() returns the right number of records.');
463

    
464
    $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25));
465
    $i = 0;
466
    foreach ($result as $record) {
467
      $this->assertIdentical($record->name, $column[$i++], 'Column matches direct accesss.');
468
    }
469
  }
470
}
471

    
472
/**
473
 * Test the insert builder.
474
 */
475
class DatabaseInsertTestCase extends DatabaseTestCase {
476

    
477
  public static function getInfo() {
478
    return array(
479
      'name' => 'Insert tests',
480
      'description' => 'Test the Insert query builder.',
481
      'group' => 'Database',
482
    );
483
  }
484

    
485
  /**
486
   * Test the very basic insert functionality.
487
   */
488
  function testSimpleInsert() {
489
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
490

    
491
    $query = db_insert('test');
492
    $query->fields(array(
493
      'name' => 'Yoko',
494
      'age' => '29',
495
    ));
496
    $query->execute();
497

    
498
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
499
    $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
500
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Yoko'))->fetchField();
501
    $this->assertIdentical($saved_age, '29', 'Can retrieve after inserting.');
502
  }
503

    
504
  /**
505
   * Test that we can insert multiple records in one query object.
506
   */
507
  function testMultiInsert() {
508
    $num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
509

    
510
    $query = db_insert('test');
511
    $query->fields(array(
512
      'name' => 'Larry',
513
      'age' => '30',
514
    ));
515

    
516
    // We should be able to specify values in any order if named.
517
    $query->values(array(
518
      'age' => '31',
519
      'name' => 'Curly',
520
    ));
521

    
522
    // We should be able to say "use the field order".
523
    // This is not the recommended mechanism for most cases, but it should work.
524
    $query->values(array('Moe', '32'));
525
    $query->execute();
526

    
527
    $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
528
    $this->assertIdentical($num_records_before + 3, $num_records_after, 'Record inserts correctly.');
529
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
530
    $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
531
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
532
    $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
533
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
534
    $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
535
  }
536

    
537
  /**
538
   * Test that an insert object can be reused with new data after it executes.
539
   */
540
  function testRepeatedInsert() {
541
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
542

    
543
    $query = db_insert('test');
544

    
545
    $query->fields(array(
546
      'name' => 'Larry',
547
      'age' => '30',
548
    ));
549
    $query->execute();  // This should run the insert, but leave the fields intact.
550

    
551
    // We should be able to specify values in any order if named.
552
    $query->values(array(
553
      'age' => '31',
554
      'name' => 'Curly',
555
    ));
556
    $query->execute();
557

    
558
    // We should be able to say "use the field order".
559
    $query->values(array('Moe', '32'));
560
    $query->execute();
561

    
562
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
563
    $this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, 'Record inserts correctly.');
564
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
565
    $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
566
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
567
    $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
568
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
569
    $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
570
  }
571

    
572
  /**
573
   * Test that we can specify fields without values and specify values later.
574
   */
575
  function testInsertFieldOnlyDefinintion() {
576
    // This is useful for importers, when we want to create a query and define
577
    // its fields once, then loop over a multi-insert execution.
578
    db_insert('test')
579
      ->fields(array('name', 'age'))
580
      ->values(array('Larry', '30'))
581
      ->values(array('Curly', '31'))
582
      ->values(array('Moe', '32'))
583
      ->execute();
584
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField();
585
    $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
586
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField();
587
    $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.');
588
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField();
589
    $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.');
590
  }
591

    
592
  /**
593
   * Test that inserts return the proper auto-increment ID.
594
   */
595
  function testInsertLastInsertID() {
596
    $id = db_insert('test')
597
      ->fields(array(
598
        'name' => 'Larry',
599
        'age' => '30',
600
      ))
601
      ->execute();
602

    
603
    $this->assertIdentical($id, '5', 'Auto-increment ID returned successfully.');
604
  }
605

    
606
  /**
607
   * Test that the INSERT INTO ... SELECT (fields) ... syntax works.
608
   */
609
  function testInsertSelectFields() {
610
    $query = db_select('test_people', 'tp');
611
    // The query builder will always append expressions after fields.
612
    // Add the expression first to test that the insert fields are correctly
613
    // re-ordered.
614
    $query->addExpression('tp.age', 'age');
615
    $query
616
      ->fields('tp', array('name','job'))
617
      ->condition('tp.name', 'Meredith');
618

    
619
    // The resulting query should be equivalent to:
620
    // INSERT INTO test (age, name, job)
621
    // SELECT tp.age AS age, tp.name AS name, tp.job AS job
622
    // FROM test_people tp
623
    // WHERE tp.name = 'Meredith'
624
    db_insert('test')
625
      ->from($query)
626
      ->execute();
627

    
628
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Meredith'))->fetchField();
629
    $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
630
  }
631

    
632
  /**
633
   * Tests that the INSERT INTO ... SELECT * ... syntax works.
634
   */
635
  function testInsertSelectAll() {
636
    $query = db_select('test_people', 'tp')
637
      ->fields('tp')
638
      ->condition('tp.name', 'Meredith');
639

    
640
    // The resulting query should be equivalent to:
641
    // INSERT INTO test_people_copy
642
    // SELECT *
643
    // FROM test_people tp
644
    // WHERE tp.name = 'Meredith'
645
    db_insert('test_people_copy')
646
      ->from($query)
647
      ->execute();
648

    
649
    $saved_age = db_query('SELECT age FROM {test_people_copy} WHERE name = :name', array(':name' => 'Meredith'))->fetchField();
650
    $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
651
  }
652
}
653

    
654
/**
655
 * Insert tests using LOB fields, which are weird on some databases.
656
 */
657
class DatabaseInsertLOBTestCase extends DatabaseTestCase {
658

    
659
  public static function getInfo() {
660
    return array(
661
      'name' => 'Insert tests, LOB fields',
662
      'description' => 'Test the Insert query builder with LOB fields.',
663
      'group' => 'Database',
664
    );
665
  }
666

    
667
  /**
668
   * Test that we can insert a single blob field successfully.
669
   */
670
  function testInsertOneBlob() {
671
    $data = "This is\000a test.";
672
    $this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.');
673
    $id = db_insert('test_one_blob')
674
      ->fields(array('blob1' => $data))
675
      ->execute();
676
    $r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc();
677
    $this->assertTrue($r['blob1'] === $data, format_string('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r))));
678
  }
679

    
680
  /**
681
   * Test that we can insert multiple blob fields in the same query.
682
   */
683
  function testInsertMultipleBlob() {
684
    $id = db_insert('test_two_blobs')
685
      ->fields(array(
686
        'blob1' => 'This is',
687
        'blob2' => 'a test',
688
      ))
689
      ->execute();
690
    $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc();
691
    $this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', 'Can insert multiple blobs per row.');
692
  }
693
}
694

    
695
/**
696
 * Insert tests for "database default" values.
697
 */
698
class DatabaseInsertDefaultsTestCase extends DatabaseTestCase {
699

    
700
  public static function getInfo() {
701
    return array(
702
      'name' => 'Insert tests, default fields',
703
      'description' => 'Test the Insert query builder with default values.',
704
      'group' => 'Database',
705
    );
706
  }
707

    
708
  /**
709
   * Test that we can run a query that is "default values for everything".
710
   */
711
  function testDefaultInsert() {
712
    $query = db_insert('test')->useDefaults(array('job'));
713
    $id = $query->execute();
714

    
715
    $schema = drupal_get_schema('test');
716

    
717
    $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
718
    $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
719
  }
720

    
721
  /**
722
   * Test that no action will be preformed if no fields are specified.
723
   */
724
  function testDefaultEmptyInsert() {
725
    $num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
726

    
727
    try {
728
      $result = db_insert('test')->execute();
729
      // This is only executed if no exception has been thrown.
730
      $this->fail('Expected exception NoFieldsException has not been thrown.');
731
    } catch (NoFieldsException $e) {
732
      $this->pass('Expected exception NoFieldsException has been thrown.');
733
    }
734

    
735
    $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
736
    $this->assertIdentical($num_records_before, $num_records_after, 'Do nothing as no fields are specified.');
737
  }
738

    
739
  /**
740
   * Test that we can insert fields with values and defaults in the same query.
741
   */
742
  function testDefaultInsertWithFields() {
743
    $query = db_insert('test')
744
      ->fields(array('name' => 'Bob'))
745
      ->useDefaults(array('job'));
746
    $id = $query->execute();
747

    
748
    $schema = drupal_get_schema('test');
749

    
750
    $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
751
    $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
752
  }
753
}
754

    
755
/**
756
 * Update builder tests.
757
 */
758
class DatabaseUpdateTestCase extends DatabaseTestCase {
759

    
760
  public static function getInfo() {
761
    return array(
762
      'name' => 'Update tests',
763
      'description' => 'Test the Update query builder.',
764
      'group' => 'Database',
765
    );
766
  }
767

    
768
  /**
769
   * Confirm that we can update a single record successfully.
770
   */
771
  function testSimpleUpdate() {
772
    $num_updated = db_update('test')
773
      ->fields(array('name' => 'Tiffany'))
774
      ->condition('id', 1)
775
      ->execute();
776
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
777

    
778
    $saved_name = db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 1))->fetchField();
779
    $this->assertIdentical($saved_name, 'Tiffany', 'Updated name successfully.');
780
  }
781

    
782
  /**
783
   * Confirm updating to NULL.
784
   */
785
  function testSimpleNullUpdate() {
786
    $this->ensureSampleDataNull();
787
    $num_updated = db_update('test_null')
788
      ->fields(array('age' => NULL))
789
      ->condition('name', 'Kermit')
790
      ->execute();
791
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
792

    
793
    $saved_age = db_query('SELECT age FROM {test_null} WHERE name = :name', array(':name' => 'Kermit'))->fetchField();
794
    $this->assertNull($saved_age, 'Updated name successfully.');
795
  }
796

    
797
  /**
798
   * Confirm that we can update a multiple records successfully.
799
   */
800
  function testMultiUpdate() {
801
    $num_updated = db_update('test')
802
      ->fields(array('job' => 'Musician'))
803
      ->condition('job', 'Singer')
804
      ->execute();
805
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
806

    
807
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
808
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
809
  }
810

    
811
  /**
812
   * Confirm that we can update a multiple records with a non-equality condition.
813
   */
814
  function testMultiGTUpdate() {
815
    $num_updated = db_update('test')
816
      ->fields(array('job' => 'Musician'))
817
      ->condition('age', 26, '>')
818
      ->execute();
819
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
820

    
821
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
822
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
823
  }
824

    
825
  /**
826
   * Confirm that we can update a multiple records with a where call.
827
   */
828
  function testWhereUpdate() {
829
    $num_updated = db_update('test')
830
      ->fields(array('job' => 'Musician'))
831
      ->where('age > :age', array(':age' => 26))
832
      ->execute();
833
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
834

    
835
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
836
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
837
  }
838

    
839
  /**
840
   * Confirm that we can stack condition and where calls.
841
   */
842
  function testWhereAndConditionUpdate() {
843
    $update = db_update('test')
844
      ->fields(array('job' => 'Musician'))
845
      ->where('age > :age', array(':age' => 26))
846
      ->condition('name', 'Ringo');
847
    $num_updated = $update->execute();
848
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
849

    
850
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
851
    $this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
852
  }
853

    
854
  /**
855
   * Test updating with expressions.
856
   */
857
  function testExpressionUpdate() {
858
    // Set age = 1 for a single row for this test to work.
859
    db_update('test')
860
      ->condition('id', 1)
861
      ->fields(array('age' => 1))
862
      ->execute();
863

    
864
    // Ensure that expressions are handled properly.  This should set every
865
    // record's age to a square of itself, which will change only three of the
866
    // four records in the table since 1*1 = 1. That means only three records
867
    // are modified, so we should get back 3, not 4, from execute().
868
    $num_rows = db_update('test')
869
      ->expression('age', 'age * age')
870
      ->execute();
871
    $this->assertIdentical($num_rows, 3, 'Number of affected rows are returned.');
872
  }
873

    
874
  /**
875
   * Confirm that we can update the primary key of a record successfully.
876
   */
877
  function testPrimaryKeyUpdate() {
878
    $num_updated = db_update('test')
879
      ->fields(array('id' => 42, 'name' => 'John'))
880
      ->condition('id', 1)
881
      ->execute();
882
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
883

    
884
    $saved_name= db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 42))->fetchField();
885
    $this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.');
886
  }
887
}
888

    
889
/**
890
 * Tests for more complex update statements.
891
 */
892
class DatabaseUpdateComplexTestCase extends DatabaseTestCase {
893

    
894
  public static function getInfo() {
895
    return array(
896
      'name' => 'Update tests, Complex',
897
      'description' => 'Test the Update query builder, complex queries.',
898
      'group' => 'Database',
899
    );
900
  }
901

    
902
  /**
903
   * Test updates with OR conditionals.
904
   */
905
  function testOrConditionUpdate() {
906
    $update = db_update('test')
907
      ->fields(array('job' => 'Musician'))
908
      ->condition(db_or()
909
        ->condition('name', 'John')
910
        ->condition('name', 'Paul')
911
      );
912
    $num_updated = $update->execute();
913
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
914

    
915
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
916
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
917
  }
918

    
919
  /**
920
   * Test WHERE IN clauses.
921
   */
922
  function testInConditionUpdate() {
923
    $num_updated = db_update('test')
924
      ->fields(array('job' => 'Musician'))
925
      ->condition('name', array('John', 'Paul'), 'IN')
926
      ->execute();
927
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
928

    
929
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
930
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
931
  }
932

    
933
  /**
934
   * Test WHERE NOT IN clauses.
935
   */
936
  function testNotInConditionUpdate() {
937
    // The o is lowercase in the 'NoT IN' operator, to make sure the operators
938
    // work in mixed case.
939
    $num_updated = db_update('test')
940
      ->fields(array('job' => 'Musician'))
941
      ->condition('name', array('John', 'Paul', 'George'), 'NoT IN')
942
      ->execute();
943
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
944

    
945
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
946
    $this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
947
  }
948

    
949
  /**
950
   * Test BETWEEN conditional clauses.
951
   */
952
  function testBetweenConditionUpdate() {
953
    $num_updated = db_update('test')
954
      ->fields(array('job' => 'Musician'))
955
      ->condition('age', array(25, 26), 'BETWEEN')
956
      ->execute();
957
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
958

    
959
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
960
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
961
  }
962

    
963
  /**
964
   * Test LIKE conditionals.
965
   */
966
  function testLikeConditionUpdate() {
967
    $num_updated = db_update('test')
968
      ->fields(array('job' => 'Musician'))
969
      ->condition('name', '%ge%', 'LIKE')
970
      ->execute();
971
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
972

    
973
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
974
    $this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
975
  }
976

    
977
  /**
978
   * Test update with expression values.
979
   */
980
  function testUpdateExpression() {
981
    $before_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
982
    $GLOBALS['larry_test'] = 1;
983
    $num_updated = db_update('test')
984
      ->condition('name', 'Ringo')
985
      ->fields(array('job' => 'Musician'))
986
      ->expression('age', 'age + :age', array(':age' => 4))
987
      ->execute();
988
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
989

    
990
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
991
    $this->assertIdentical($num_matches, '1', 'Updated fields successfully.');
992

    
993
    $person = db_query('SELECT * FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetch();
994
    $this->assertEqual($person->name, 'Ringo', 'Name set correctly.');
995
    $this->assertEqual($person->age, $before_age + 4, 'Age set correctly.');
996
    $this->assertEqual($person->job, 'Musician', 'Job set correctly.');
997
    $GLOBALS['larry_test'] = 0;
998
  }
999

    
1000
  /**
1001
   * Test update with only expression values.
1002
   */
1003
  function testUpdateOnlyExpression() {
1004
    $before_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
1005
    $num_updated = db_update('test')
1006
      ->condition('name', 'Ringo')
1007
      ->expression('age', 'age + :age', array(':age' => 4))
1008
      ->execute();
1009
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
1010

    
1011
    $after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
1012
    $this->assertEqual($before_age + 4, $after_age, 'Age updated correctly');
1013
  }
1014
}
1015

    
1016
/**
1017
 * Test update queries involving LOB values.
1018
 */
1019
class DatabaseUpdateLOBTestCase extends DatabaseTestCase {
1020

    
1021
  public static function getInfo() {
1022
    return array(
1023
      'name' => 'Update tests, LOB',
1024
      'description' => 'Test the Update query builder with LOB fields.',
1025
      'group' => 'Database',
1026
    );
1027
  }
1028

    
1029
  /**
1030
   * Confirm that we can update a blob column.
1031
   */
1032
  function testUpdateOneBlob() {
1033
    $data = "This is\000a test.";
1034
    $this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.');
1035
    $id = db_insert('test_one_blob')
1036
      ->fields(array('blob1' => $data))
1037
      ->execute();
1038

    
1039
    $data .= $data;
1040
    db_update('test_one_blob')
1041
      ->condition('id', $id)
1042
      ->fields(array('blob1' => $data))
1043
      ->execute();
1044

    
1045
    $r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc();
1046
    $this->assertTrue($r['blob1'] === $data, format_string('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r))));
1047
  }
1048

    
1049
  /**
1050
   * Confirm that we can update two blob columns in the same table.
1051
   */
1052
  function testUpdateMultipleBlob() {
1053
    $id = db_insert('test_two_blobs')
1054
      ->fields(array(
1055
        'blob1' => 'This is',
1056
        'blob2' => 'a test',
1057
      ))
1058
      ->execute();
1059

    
1060
    db_update('test_two_blobs')
1061
      ->condition('id', $id)
1062
      ->fields(array('blob1' => 'and so', 'blob2' => 'is this'))
1063
      ->execute();
1064

    
1065
    $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc();
1066
    $this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', 'Can update multiple blobs per row.');
1067
  }
1068
}
1069

    
1070
/**
1071
 * Delete/Truncate tests.
1072
 *
1073
 * The DELETE tests are not as extensive, as all of the interesting code for
1074
 * DELETE queries is in the conditional which is identical to the UPDATE and
1075
 * SELECT conditional handling.
1076
 *
1077
 * The TRUNCATE tests are not extensive either, because the behavior of
1078
 * TRUNCATE queries is not consistent across database engines. We only test
1079
 * that a TRUNCATE query actually deletes all rows from the target table.
1080
 */
1081
class DatabaseDeleteTruncateTestCase extends DatabaseTestCase {
1082

    
1083
  public static function getInfo() {
1084
    return array(
1085
      'name' => 'Delete/Truncate tests',
1086
      'description' => 'Test the Delete and Truncate query builders.',
1087
      'group' => 'Database',
1088
    );
1089
  }
1090

    
1091
  /**
1092
   * Confirm that we can use a subselect in a delete successfully.
1093
   */
1094
  function testSubselectDelete() {
1095
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
1096
    $pid_to_delete = db_query("SELECT * FROM {test_task} WHERE task = 'sleep'")->fetchField();
1097

    
1098
    $subquery = db_select('test', 't')
1099
      ->fields('t', array('id'))
1100
      ->condition('t.id', array($pid_to_delete), 'IN');
1101
    $delete = db_delete('test_task')
1102
      ->condition('task', 'sleep')
1103
      ->condition('pid', $subquery, 'IN');
1104

    
1105
    $num_deleted = $delete->execute();
1106
    $this->assertEqual($num_deleted, 1, "Deleted 1 record.");
1107

    
1108
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
1109
    $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
1110
  }
1111

    
1112
  /**
1113
   * Confirm that we can delete a single record successfully.
1114
   */
1115
  function testSimpleDelete() {
1116
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
1117

    
1118
    $num_deleted = db_delete('test')
1119
      ->condition('id', 1)
1120
      ->execute();
1121
    $this->assertIdentical($num_deleted, 1, 'Deleted 1 record.');
1122

    
1123
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
1124
    $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
1125
  }
1126

    
1127
  /**
1128
   * Confirm that we can truncate a whole table successfully.
1129
   */
1130
  function testTruncate() {
1131
    $num_records_before = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
1132

    
1133
    db_truncate('test')->execute();
1134

    
1135
    $num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
1136
    $this->assertEqual(0, $num_records_after, 'Truncate really deletes everything.');
1137
  }
1138
}
1139

    
1140
/**
1141
 * Test the MERGE query builder.
1142
 */
1143
class DatabaseMergeTestCase extends DatabaseTestCase {
1144

    
1145
  public static function getInfo() {
1146
    return array(
1147
      'name' => 'Merge tests',
1148
      'description' => 'Test the Merge query builder.',
1149
      'group' => 'Database',
1150
    );
1151
  }
1152

    
1153
  /**
1154
   * Confirm that we can merge-insert a record successfully.
1155
   */
1156
  function testMergeInsert() {
1157
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1158

    
1159
    $result = db_merge('test_people')
1160
      ->key(array('job' => 'Presenter'))
1161
      ->fields(array(
1162
        'age' => 31,
1163
        'name' => 'Tiffany',
1164
      ))
1165
      ->execute();
1166

    
1167
    $this->assertEqual($result, MergeQuery::STATUS_INSERT, 'Insert status returned.');
1168

    
1169
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1170
    $this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.');
1171

    
1172
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
1173
    $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
1174
    $this->assertEqual($person->age, 31, 'Age set correctly.');
1175
    $this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
1176
  }
1177

    
1178
  /**
1179
   * Confirm that we can merge-update a record successfully.
1180
   */
1181
  function testMergeUpdate() {
1182
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1183

    
1184
    $result = db_merge('test_people')
1185
      ->key(array('job' => 'Speaker'))
1186
      ->fields(array(
1187
        'age' => 31,
1188
        'name' => 'Tiffany',
1189
      ))
1190
      ->execute();
1191

    
1192
    $this->assertEqual($result, MergeQuery::STATUS_UPDATE, 'Update status returned.');
1193

    
1194
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1195
    $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
1196

    
1197
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1198
    $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
1199
    $this->assertEqual($person->age, 31, 'Age set correctly.');
1200
    $this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
1201
  }
1202

    
1203
  /**
1204
   * Confirm that we can merge-update a record successfully, with different insert and update.
1205
   */
1206
  function testMergeUpdateExcept() {
1207
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1208

    
1209
    db_merge('test_people')
1210
      ->key(array('job' => 'Speaker'))
1211
      ->insertFields(array('age' => 31))
1212
      ->updateFields(array('name' => 'Tiffany'))
1213
      ->execute();
1214

    
1215
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1216
    $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
1217

    
1218
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1219
    $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
1220
    $this->assertEqual($person->age, 30, 'Age skipped correctly.');
1221
    $this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
1222
  }
1223

    
1224
  /**
1225
   * Confirm that we can merge-update a record successfully, with alternate replacement.
1226
   */
1227
  function testMergeUpdateExplicit() {
1228
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1229

    
1230
    db_merge('test_people')
1231
      ->key(array('job' => 'Speaker'))
1232
      ->insertFields(array(
1233
        'age' => 31,
1234
        'name' => 'Tiffany',
1235
      ))
1236
      ->updateFields(array(
1237
        'name' => 'Joe',
1238
      ))
1239
      ->execute();
1240

    
1241
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1242
    $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
1243

    
1244
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1245
    $this->assertEqual($person->name, 'Joe', 'Name set correctly.');
1246
    $this->assertEqual($person->age, 30, 'Age skipped correctly.');
1247
    $this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
1248
  }
1249

    
1250
  /**
1251
   * Confirm that we can merge-update a record successfully, with expressions.
1252
   */
1253
  function testMergeUpdateExpression() {
1254
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1255

    
1256
    $age_before = db_query('SELECT age FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetchField();
1257

    
1258
    // This is a very contrived example, as I have no idea why you'd want to
1259
    // change age this way, but that's beside the point.
1260
    // Note that we are also double-setting age here, once as a literal and
1261
    // once as an expression. This test will only pass if the expression wins,
1262
    // which is what is supposed to happen.
1263
    db_merge('test_people')
1264
      ->key(array('job' => 'Speaker'))
1265
      ->fields(array('name' => 'Tiffany'))
1266
      ->insertFields(array('age' => 31))
1267
      ->expression('age', 'age + :age', array(':age' => 4))
1268
      ->execute();
1269

    
1270
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1271
    $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.');
1272

    
1273
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1274
    $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
1275
    $this->assertEqual($person->age, $age_before + 4, 'Age updated correctly.');
1276
    $this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
1277
  }
1278

    
1279
  /**
1280
   * Test that we can merge-insert without any update fields.
1281
   */
1282
  function testMergeInsertWithoutUpdate() {
1283
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1284

    
1285
    db_merge('test_people')
1286
      ->key(array('job' => 'Presenter'))
1287
      ->execute();
1288

    
1289
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1290
    $this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.');
1291

    
1292
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
1293
    $this->assertEqual($person->name, '', 'Name set correctly.');
1294
    $this->assertEqual($person->age, 0, 'Age set correctly.');
1295
    $this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
1296
  }
1297

    
1298
  /**
1299
   * Confirm that we can merge-update without any update fields.
1300
   */
1301
  function testMergeUpdateWithoutUpdate() {
1302
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1303

    
1304
    db_merge('test_people')
1305
      ->key(array('job' => 'Speaker'))
1306
      ->execute();
1307

    
1308
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1309
    $this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.');
1310

    
1311
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1312
    $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.');
1313
    $this->assertEqual($person->age, 30, 'Age skipped correctly.');
1314
    $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
1315

    
1316
    db_merge('test_people')
1317
      ->key(array('job' => 'Speaker'))
1318
      ->insertFields(array('age' => 31))
1319
      ->execute();
1320

    
1321
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1322
    $this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.');
1323

    
1324
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1325
    $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.');
1326
    $this->assertEqual($person->age, 30, 'Age skipped correctly.');
1327
    $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
1328
  }
1329

    
1330
  /**
1331
   * Test that an invalid merge query throws an exception like it is supposed to.
1332
   */
1333
  function testInvalidMerge() {
1334
    try {
1335
      // This query should die because there is no key field specified.
1336
      db_merge('test_people')
1337
        ->fields(array(
1338
          'age' => 31,
1339
          'name' => 'Tiffany',
1340
        ))
1341
        ->execute();
1342
    }
1343
    catch (InvalidMergeQueryException $e) {
1344
      $this->pass('InvalidMergeQueryException thrown for invalid query.');
1345
      return;
1346
    }
1347
    $this->fail('No InvalidMergeQueryException thrown');
1348
  }
1349
}
1350

    
1351
/**
1352
 * Test the SELECT builder.
1353
 */
1354
class DatabaseSelectTestCase extends DatabaseTestCase {
1355

    
1356
  public static function getInfo() {
1357
    return array(
1358
      'name' => 'Select tests',
1359
      'description' => 'Test the Select query builder.',
1360
      'group' => 'Database',
1361
    );
1362
  }
1363

    
1364
  /**
1365
   * Test rudimentary SELECT statements.
1366
   */
1367
  function testSimpleSelect() {
1368
    $query = db_select('test');
1369
    $name_field = $query->addField('test', 'name');
1370
    $age_field = $query->addField('test', 'age', 'age');
1371
    $result = $query->execute();
1372

    
1373
    $num_records = 0;
1374
    foreach ($result as $record) {
1375
      $num_records++;
1376
    }
1377

    
1378
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1379
  }
1380

    
1381
  /**
1382
   * Test rudimentary SELECT statement with a COMMENT.
1383
   */
1384
  function testSimpleComment() {
1385
    $query = db_select('test')->comment('Testing query comments');
1386
    $name_field = $query->addField('test', 'name');
1387
    $age_field = $query->addField('test', 'age', 'age');
1388
    $result = $query->execute();
1389

    
1390
    $num_records = 0;
1391
    foreach ($result as $record) {
1392
      $num_records++;
1393
    }
1394

    
1395
    $query = (string)$query;
1396
    $expected = "/* Testing query comments */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test";
1397

    
1398
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1399
    $this->assertEqual($query, $expected, 'The flattened query contains the comment string.');
1400
  }
1401

    
1402
  /**
1403
   * Test query COMMENT system against vulnerabilities.
1404
   */
1405
  function testVulnerableComment() {
1406
    $query = db_select('test')->comment('Testing query comments */ SELECT nid FROM {node}; --');
1407
    $name_field = $query->addField('test', 'name');
1408
    $age_field = $query->addField('test', 'age', 'age');
1409
    $result = $query->execute();
1410

    
1411
    $num_records = 0;
1412
    foreach ($result as $record) {
1413
      $num_records++;
1414
    }
1415

    
1416
    $query = (string)$query;
1417
    $expected = "/* Testing query comments SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test";
1418

    
1419
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1420
    $this->assertEqual($query, $expected, 'The flattened query contains the sanitised comment string.');
1421
  }
1422

    
1423
  /**
1424
   * Test basic conditionals on SELECT statements.
1425
   */
1426
  function testSimpleSelectConditional() {
1427
    $query = db_select('test');
1428
    $name_field = $query->addField('test', 'name');
1429
    $age_field = $query->addField('test', 'age', 'age');
1430
    $query->condition('age', 27);
1431
    $result = $query->execute();
1432

    
1433
    // Check that the aliases are being created the way we want.
1434
    $this->assertEqual($name_field, 'name', 'Name field alias is correct.');
1435
    $this->assertEqual($age_field, 'age', 'Age field alias is correct.');
1436

    
1437
    // Ensure that we got the right record.
1438
    $record = $result->fetch();
1439
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1440
    $this->assertEqual($record->$age_field, 27, 'Fetched age is correct.');
1441
  }
1442

    
1443
  /**
1444
   * Test SELECT statements with expressions.
1445
   */
1446
  function testSimpleSelectExpression() {
1447
    $query = db_select('test');
1448
    $name_field = $query->addField('test', 'name');
1449
    $age_field = $query->addExpression("age*2", 'double_age');
1450
    $query->condition('age', 27);
1451
    $result = $query->execute();
1452

    
1453
    // Check that the aliases are being created the way we want.
1454
    $this->assertEqual($name_field, 'name', 'Name field alias is correct.');
1455
    $this->assertEqual($age_field, 'double_age', 'Age field alias is correct.');
1456

    
1457
    // Ensure that we got the right record.
1458
    $record = $result->fetch();
1459
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1460
    $this->assertEqual($record->$age_field, 27*2, 'Fetched age expression is correct.');
1461
  }
1462

    
1463
  /**
1464
   * Test SELECT statements with multiple expressions.
1465
   */
1466
  function testSimpleSelectExpressionMultiple() {
1467
    $query = db_select('test');
1468
    $name_field = $query->addField('test', 'name');
1469
    $age_double_field = $query->addExpression("age*2");
1470
    $age_triple_field = $query->addExpression("age*3");
1471
    $query->condition('age', 27);
1472
    $result = $query->execute();
1473

    
1474
    // Check that the aliases are being created the way we want.
1475
    $this->assertEqual($age_double_field, 'expression', 'Double age field alias is correct.');
1476
    $this->assertEqual($age_triple_field, 'expression_2', 'Triple age field alias is correct.');
1477

    
1478
    // Ensure that we got the right record.
1479
    $record = $result->fetch();
1480
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1481
    $this->assertEqual($record->$age_double_field, 27*2, 'Fetched double age expression is correct.');
1482
    $this->assertEqual($record->$age_triple_field, 27*3, 'Fetched triple age expression is correct.');
1483
  }
1484

    
1485
  /**
1486
   * Test adding multiple fields to a select statement at the same time.
1487
   */
1488
  function testSimpleSelectMultipleFields() {
1489
    $record = db_select('test')
1490
      ->fields('test', array('id', 'name', 'age', 'job'))
1491
      ->condition('age', 27)
1492
      ->execute()->fetchObject();
1493

    
1494
    // Check that all fields we asked for are present.
1495
    $this->assertNotNull($record->id, 'ID field is present.');
1496
    $this->assertNotNull($record->name, 'Name field is present.');
1497
    $this->assertNotNull($record->age, 'Age field is present.');
1498
    $this->assertNotNull($record->job, 'Job field is present.');
1499

    
1500
    // Ensure that we got the right record.
1501
    // Check that all fields we asked for are present.
1502
    $this->assertEqual($record->id, 2, 'ID field has the correct value.');
1503
    $this->assertEqual($record->name, 'George', 'Name field has the correct value.');
1504
    $this->assertEqual($record->age, 27, 'Age field has the correct value.');
1505
    $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
1506
  }
1507

    
1508
  /**
1509
   * Test adding all fields from a given table to a select statement.
1510
   */
1511
  function testSimpleSelectAllFields() {
1512
    $record = db_select('test')
1513
      ->fields('test')
1514
      ->condition('age', 27)
1515
      ->execute()->fetchObject();
1516

    
1517
    // Check that all fields we asked for are present.
1518
    $this->assertNotNull($record->id, 'ID field is present.');
1519
    $this->assertNotNull($record->name, 'Name field is present.');
1520
    $this->assertNotNull($record->age, 'Age field is present.');
1521
    $this->assertNotNull($record->job, 'Job field is present.');
1522

    
1523
    // Ensure that we got the right record.
1524
    // Check that all fields we asked for are present.
1525
    $this->assertEqual($record->id, 2, 'ID field has the correct value.');
1526
    $this->assertEqual($record->name, 'George', 'Name field has the correct value.');
1527
    $this->assertEqual($record->age, 27, 'Age field has the correct value.');
1528
    $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
1529
  }
1530

    
1531
  /**
1532
   * Test that we can find a record with a NULL value.
1533
   */
1534
  function testNullCondition() {
1535
    $this->ensureSampleDataNull();
1536

    
1537
    $names = db_select('test_null', 'tn')
1538
      ->fields('tn', array('name'))
1539
      ->isNull('age')
1540
      ->execute()->fetchCol();
1541

    
1542
    $this->assertEqual(count($names), 1, 'Correct number of records found with NULL age.');
1543
    $this->assertEqual($names[0], 'Fozzie', 'Correct record returned for NULL age.');
1544
  }
1545

    
1546
  /**
1547
   * Test that we can find a record without a NULL value.
1548
   */
1549
  function testNotNullCondition() {
1550
    $this->ensureSampleDataNull();
1551

    
1552
    $names = db_select('test_null', 'tn')
1553
      ->fields('tn', array('name'))
1554
      ->isNotNull('tn.age')
1555
      ->orderBy('name')
1556
      ->execute()->fetchCol();
1557

    
1558
    $this->assertEqual(count($names), 2, 'Correct number of records found withNOT NULL age.');
1559
    $this->assertEqual($names[0], 'Gonzo', 'Correct record returned for NOT NULL age.');
1560
    $this->assertEqual($names[1], 'Kermit', 'Correct record returned for NOT NULL age.');
1561
  }
1562

    
1563
  /**
1564
   * Test that we can UNION multiple Select queries together. This is
1565
   * semantically equal to UNION DISTINCT, so we don't explicity test that.
1566
   */
1567
  function testUnion() {
1568
    $query_1 = db_select('test', 't')
1569
      ->fields('t', array('name'))
1570
      ->condition('age', array(27, 28), 'IN');
1571

    
1572
    $query_2 = db_select('test', 't')
1573
      ->fields('t', array('name'))
1574
      ->condition('age', 28);
1575

    
1576
    $query_1->union($query_2);
1577

    
1578
    $names = $query_1->execute()->fetchCol();
1579

    
1580
    // Ensure we only get 2 records.
1581
    $this->assertEqual(count($names), 2, 'UNION correctly discarded duplicates.');
1582

    
1583
    $this->assertEqual($names[0], 'George', 'First query returned correct name.');
1584
    $this->assertEqual($names[1], 'Ringo', 'Second query returned correct name.');
1585
  }
1586

    
1587
  /**
1588
   * Test that we can UNION ALL multiple Select queries together.
1589
   */
1590
  function testUnionAll() {
1591
    $query_1 = db_select('test', 't')
1592
      ->fields('t', array('name'))
1593
      ->condition('age', array(27, 28), 'IN');
1594

    
1595
    $query_2 = db_select('test', 't')
1596
      ->fields('t', array('name'))
1597
      ->condition('age', 28);
1598

    
1599
    $query_1->union($query_2, 'ALL');
1600

    
1601
    $names = $query_1->execute()->fetchCol();
1602

    
1603
    // Ensure we get all 3 records.
1604
    $this->assertEqual(count($names), 3, 'UNION ALL correctly preserved duplicates.');
1605

    
1606
    $this->assertEqual($names[0], 'George', 'First query returned correct first name.');
1607
    $this->assertEqual($names[1], 'Ringo', 'Second query returned correct second name.');
1608
    $this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.');
1609
  }
1610

    
1611
  /**
1612
   * Test that random ordering of queries works.
1613
   *
1614
   * We take the approach of testing the Drupal layer only, rather than trying
1615
   * to test that the database's random number generator actually produces
1616
   * random queries (which is very difficult to do without an unacceptable risk
1617
   * of the test failing by accident).
1618
   *
1619
   * Therefore, in this test we simply run the same query twice and assert that
1620
   * the two results are reordered versions of each other (as well as of the
1621
   * same query without the random ordering). It is reasonable to assume that
1622
   * if we run the same select query twice and the results are in a different
1623
   * order each time, the only way this could happen is if we have successfully
1624
   * triggered the database's random ordering functionality.
1625
   */
1626
  function testRandomOrder() {
1627
    // Use 52 items, so the chance that this test fails by accident will be the
1628
    // same as the chance that a deck of cards will come out in the same order
1629
    // after shuffling it (in other words, nearly impossible).
1630
    $number_of_items = 52;
1631
    while (db_query("SELECT MAX(id) FROM {test}")->fetchField() < $number_of_items) {
1632
      db_insert('test')->fields(array('name' => $this->randomName()))->execute();
1633
    }
1634

    
1635
    // First select the items in order and make sure we get an ordered list.
1636
    $expected_ids = range(1, $number_of_items);
1637
    $ordered_ids = db_select('test', 't')
1638
      ->fields('t', array('id'))
1639
      ->range(0, $number_of_items)
1640
      ->orderBy('id')
1641
      ->execute()
1642
      ->fetchCol();
1643
    $this->assertEqual($ordered_ids, $expected_ids, 'A query without random ordering returns IDs in the correct order.');
1644

    
1645
    // Now perform the same query, but instead choose a random ordering. We
1646
    // expect this to contain a differently ordered version of the original
1647
    // result.
1648
    $randomized_ids = db_select('test', 't')
1649
      ->fields('t', array('id'))
1650
      ->range(0, $number_of_items)
1651
      ->orderRandom()
1652
      ->execute()
1653
      ->fetchCol();
1654
    $this->assertNotEqual($randomized_ids, $ordered_ids, 'A query with random ordering returns an unordered set of IDs.');
1655
    $sorted_ids = $randomized_ids;
1656
    sort($sorted_ids);
1657
    $this->assertEqual($sorted_ids, $ordered_ids, 'After sorting the random list, the result matches the original query.');
1658

    
1659
    // Now perform the exact same query again, and make sure the order is
1660
    // different.
1661
    $randomized_ids_second_set = db_select('test', 't')
1662
      ->fields('t', array('id'))
1663
      ->range(0, $number_of_items)
1664
      ->orderRandom()
1665
      ->execute()
1666
      ->fetchCol();
1667
    $this->assertNotEqual($randomized_ids_second_set, $randomized_ids, 'Performing the query with random ordering a second time returns IDs in a different order.');
1668
    $sorted_ids_second_set = $randomized_ids_second_set;
1669
    sort($sorted_ids_second_set);
1670
    $this->assertEqual($sorted_ids_second_set, $sorted_ids, 'After sorting the second random list, the result matches the sorted version of the first random list.');
1671
  }
1672

    
1673
  /**
1674
   * Test that aliases are renamed when duplicates.
1675
   */
1676
  function testSelectDuplicateAlias() {
1677
    $query = db_select('test', 't');
1678
    $alias1 = $query->addField('t', 'name', 'the_alias');
1679
    $alias2 = $query->addField('t', 'age', 'the_alias');
1680
    $this->assertNotIdentical($alias1, $alias2, 'Duplicate aliases are renamed.');
1681
  }
1682
}
1683

    
1684
/**
1685
 * Test case for subselects in a dynamic SELECT query.
1686
 */
1687
class DatabaseSelectSubqueryTestCase extends DatabaseTestCase {
1688

    
1689
  public static function getInfo() {
1690
    return array(
1691
      'name' => 'Select tests, subqueries',
1692
      'description' => 'Test the Select query builder.',
1693
      'group' => 'Database',
1694
    );
1695
  }
1696

    
1697
  /**
1698
   * Test that we can use a subquery in a FROM clause.
1699
   */
1700
  function testFromSubquerySelect() {
1701
    // Create a subquery, which is just a normal query object.
1702
    $subquery = db_select('test_task', 'tt');
1703
    $subquery->addField('tt', 'pid', 'pid');
1704
    $subquery->addField('tt', 'task', 'task');
1705
    $subquery->condition('priority', 1);
1706

    
1707
    for ($i = 0; $i < 2; $i++) {
1708
      // Create another query that joins against the virtual table resulting
1709
      // from the subquery.
1710
      $select = db_select($subquery, 'tt2');
1711
      $select->join('test', 't', 't.id=tt2.pid');
1712
      $select->addField('t', 'name');
1713
      if ($i) {
1714
        // Use a different number of conditions here to confuse the subquery
1715
        // placeholder counter, testing http://drupal.org/node/1112854.
1716
        $select->condition('name', 'John');
1717
      }
1718
      $select->condition('task', 'code');
1719

    
1720
      // The resulting query should be equivalent to:
1721
      // SELECT t.name
1722
      // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt
1723
      //   INNER JOIN test t ON t.id=tt.pid
1724
      // WHERE tt.task = 'code'
1725
      $people = $select->execute()->fetchCol();
1726

    
1727
      $this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
1728
    }
1729
  }
1730

    
1731
  /**
1732
   * Test that we can use a subquery in a FROM clause with a limit.
1733
   */
1734
  function testFromSubquerySelectWithLimit() {
1735
    // Create a subquery, which is just a normal query object.
1736
    $subquery = db_select('test_task', 'tt');
1737
    $subquery->addField('tt', 'pid', 'pid');
1738
    $subquery->addField('tt', 'task', 'task');
1739
    $subquery->orderBy('priority', 'DESC');
1740
    $subquery->range(0, 1);
1741

    
1742
    // Create another query that joins against the virtual table resulting
1743
    // from the subquery.
1744
    $select = db_select($subquery, 'tt2');
1745
    $select->join('test', 't', 't.id=tt2.pid');
1746
    $select->addField('t', 'name');
1747

    
1748
    // The resulting query should be equivalent to:
1749
    // SELECT t.name
1750
    // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt ORDER BY priority DESC LIMIT 1 OFFSET 0) tt
1751
    //   INNER JOIN test t ON t.id=tt.pid
1752
    $people = $select->execute()->fetchCol();
1753

    
1754
    $this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
1755
  }
1756

    
1757
  /**
1758
   * Test that we can use a subquery in a WHERE clause.
1759
   */
1760
  function testConditionSubquerySelect() {
1761
    // Create a subquery, which is just a normal query object.
1762
    $subquery = db_select('test_task', 'tt');
1763
    $subquery->addField('tt', 'pid', 'pid');
1764
    $subquery->condition('tt.priority', 1);
1765

    
1766
    // Create another query that joins against the virtual table resulting
1767
    // from the subquery.
1768
    $select = db_select('test_task', 'tt2');
1769
    $select->addField('tt2', 'task');
1770
    $select->condition('tt2.pid', $subquery, 'IN');
1771

    
1772
    // The resulting query should be equivalent to:
1773
    // SELECT tt2.name
1774
    // FROM test tt2
1775
    // WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1)
1776
    $people = $select->execute()->fetchCol();
1777
    $this->assertEqual(count($people), 5, 'Returned the correct number of rows.');
1778
  }
1779

    
1780
  /**
1781
   * Test that we can use a subquery in a JOIN clause.
1782
   */
1783
  function testJoinSubquerySelect() {
1784
    // Create a subquery, which is just a normal query object.
1785
    $subquery = db_select('test_task', 'tt');
1786
    $subquery->addField('tt', 'pid', 'pid');
1787
    $subquery->condition('priority', 1);
1788

    
1789
    // Create another query that joins against the virtual table resulting
1790
    // from the subquery.
1791
    $select = db_select('test', 't');
1792
    $select->join($subquery, 'tt', 't.id=tt.pid');
1793
    $select->addField('t', 'name');
1794

    
1795
    // The resulting query should be equivalent to:
1796
    // SELECT t.name
1797
    // FROM test t
1798
    //   INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid
1799
    $people = $select->execute()->fetchCol();
1800

    
1801
    $this->assertEqual(count($people), 2, 'Returned the correct number of rows.');
1802
  }
1803

    
1804
  /**
1805
   * Test EXISTS subquery conditionals on SELECT statements.
1806
   *
1807
   * We essentially select all rows from the {test} table that have matching
1808
   * rows in the {test_people} table based on the shared name column.
1809
   */
1810
  function testExistsSubquerySelect() {
1811
    // Put George into {test_people}.
1812
    db_insert('test_people')
1813
      ->fields(array(
1814
        'name' => 'George',
1815
        'age' => 27,
1816
        'job' => 'Singer',
1817
      ))
1818
      ->execute();
1819
    // Base query to {test}.
1820
    $query = db_select('test', 't')
1821
      ->fields('t', array('name'));
1822
    // Subquery to {test_people}.
1823
    $subquery = db_select('test_people', 'tp')
1824
      ->fields('tp', array('name'))
1825
      ->where('tp.name = t.name');
1826
    $query->exists($subquery);
1827
    $result = $query->execute();
1828

    
1829
    // Ensure that we got the right record.
1830
    $record = $result->fetch();
1831
    $this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.');
1832
  }
1833

    
1834
  /**
1835
   * Test NOT EXISTS subquery conditionals on SELECT statements.
1836
   *
1837
   * We essentially select all rows from the {test} table that don't have
1838
   * matching rows in the {test_people} table based on the shared name column.
1839
   */
1840
  function testNotExistsSubquerySelect() {
1841
    // Put George into {test_people}.
1842
    db_insert('test_people')
1843
      ->fields(array(
1844
        'name' => 'George',
1845
        'age' => 27,
1846
        'job' => 'Singer',
1847
      ))
1848
      ->execute();
1849

    
1850
    // Base query to {test}.
1851
    $query = db_select('test', 't')
1852
      ->fields('t', array('name'));
1853
    // Subquery to {test_people}.
1854
    $subquery = db_select('test_people', 'tp')
1855
      ->fields('tp', array('name'))
1856
      ->where('tp.name = t.name');
1857
    $query->notExists($subquery);
1858

    
1859
    // Ensure that we got the right number of records.
1860
    $people = $query->execute()->fetchCol();
1861
    $this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.');
1862
  }
1863
}
1864

    
1865
/**
1866
 * Test select with order by clauses.
1867
 */
1868
class DatabaseSelectOrderedTestCase extends DatabaseTestCase {
1869

    
1870
  public static function getInfo() {
1871
    return array(
1872
      'name' => 'Select tests, ordered',
1873
      'description' => 'Test the Select query builder.',
1874
      'group' => 'Database',
1875
    );
1876
  }
1877

    
1878
  /**
1879
   * Test basic order by.
1880
   */
1881
  function testSimpleSelectOrdered() {
1882
    $query = db_select('test');
1883
    $name_field = $query->addField('test', 'name');
1884
    $age_field = $query->addField('test', 'age', 'age');
1885
    $query->orderBy($age_field);
1886
    $result = $query->execute();
1887

    
1888
    $num_records = 0;
1889
    $last_age = 0;
1890
    foreach ($result as $record) {
1891
      $num_records++;
1892
      $this->assertTrue($record->age >= $last_age, 'Results returned in correct order.');
1893
      $last_age = $record->age;
1894
    }
1895

    
1896
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1897
  }
1898

    
1899
  /**
1900
   * Test multiple order by.
1901
   */
1902
  function testSimpleSelectMultiOrdered() {
1903
    $query = db_select('test');
1904
    $name_field = $query->addField('test', 'name');
1905
    $age_field = $query->addField('test', 'age', 'age');
1906
    $job_field = $query->addField('test', 'job');
1907
    $query->orderBy($job_field);
1908
    $query->orderBy($age_field);
1909
    $result = $query->execute();
1910

    
1911
    $num_records = 0;
1912
    $expected = array(
1913
      array('Ringo', 28, 'Drummer'),
1914
      array('John', 25, 'Singer'),
1915
      array('George', 27, 'Singer'),
1916
      array('Paul', 26, 'Songwriter'),
1917
    );
1918
    $results = $result->fetchAll(PDO::FETCH_NUM);
1919
    foreach ($expected as $k => $record) {
1920
      $num_records++;
1921
      foreach ($record as $kk => $col) {
1922
        if ($expected[$k][$kk] != $results[$k][$kk]) {
1923
          $this->assertTrue(FALSE, 'Results returned in correct order.');
1924
        }
1925
      }
1926
    }
1927
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1928
  }
1929

    
1930
  /**
1931
   * Test order by descending.
1932
   */
1933
  function testSimpleSelectOrderedDesc() {
1934
    $query = db_select('test');
1935
    $name_field = $query->addField('test', 'name');
1936
    $age_field = $query->addField('test', 'age', 'age');
1937
    $query->orderBy($age_field, 'DESC');
1938
    $result = $query->execute();
1939

    
1940
    $num_records = 0;
1941
    $last_age = 100000000;
1942
    foreach ($result as $record) {
1943
      $num_records++;
1944
      $this->assertTrue($record->age <= $last_age, 'Results returned in correct order.');
1945
      $last_age = $record->age;
1946
    }
1947

    
1948
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1949
  }
1950
}
1951

    
1952
/**
1953
 * Test more complex select statements.
1954
 */
1955
class DatabaseSelectComplexTestCase extends DatabaseTestCase {
1956

    
1957
  public static function getInfo() {
1958
    return array(
1959
      'name' => 'Select tests, complex',
1960
      'description' => 'Test the Select query builder with more complex queries.',
1961
      'group' => 'Database',
1962
    );
1963
  }
1964

    
1965
  /**
1966
   * Test simple JOIN statements.
1967
   */
1968
  function testDefaultJoin() {
1969
    $query = db_select('test_task', 't');
1970
    $people_alias = $query->join('test', 'p', 't.pid = p.id');
1971
    $name_field = $query->addField($people_alias, 'name', 'name');
1972
    $task_field = $query->addField('t', 'task', 'task');
1973
    $priority_field = $query->addField('t', 'priority', 'priority');
1974

    
1975
    $query->orderBy($priority_field);
1976
    $result = $query->execute();
1977

    
1978
    $num_records = 0;
1979
    $last_priority = 0;
1980
    foreach ($result as $record) {
1981
      $num_records++;
1982
      $this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.');
1983
      $this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.');
1984
      $last_priority = $record->$priority_field;
1985
    }
1986

    
1987
    $this->assertEqual($num_records, 7, 'Returned the correct number of rows.');
1988
  }
1989

    
1990
  /**
1991
   * Test LEFT OUTER joins.
1992
   */
1993
  function testLeftOuterJoin() {
1994
    $query = db_select('test', 'p');
1995
    $people_alias = $query->leftJoin('test_task', 't', 't.pid = p.id');
1996
    $name_field = $query->addField('p', 'name', 'name');
1997
    $task_field = $query->addField($people_alias, 'task', 'task');
1998
    $priority_field = $query->addField($people_alias, 'priority', 'priority');
1999

    
2000
    $query->orderBy($name_field);
2001
    $result = $query->execute();
2002

    
2003
    $num_records = 0;
2004
    $last_name = 0;
2005

    
2006
    foreach ($result as $record) {
2007
      $num_records++;
2008
      $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.');
2009
      $last_priority = $record->$name_field;
2010
    }
2011

    
2012
    $this->assertEqual($num_records, 8, 'Returned the correct number of rows.');
2013
  }
2014

    
2015
  /**
2016
   * Test GROUP BY clauses.
2017
   */
2018
  function testGroupBy() {
2019
    $query = db_select('test_task', 't');
2020
    $count_field = $query->addExpression('COUNT(task)', 'num');
2021
    $task_field = $query->addField('t', 'task');
2022
    $query->orderBy($count_field);
2023
    $query->groupBy($task_field);
2024
    $result = $query->execute();
2025

    
2026
    $num_records = 0;
2027
    $last_count = 0;
2028
    $records = array();
2029
    foreach ($result as $record) {
2030
      $num_records++;
2031
      $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
2032
      $last_count = $record->$count_field;
2033
      $records[$record->$task_field] = $record->$count_field;
2034
    }
2035

    
2036
    $correct_results = array(
2037
      'eat' => 1,
2038
      'sleep' => 2,
2039
      'code' => 1,
2040
      'found new band' => 1,
2041
      'perform at superbowl' => 1,
2042
    );
2043

    
2044
    foreach ($correct_results as $task => $count) {
2045
      $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
2046
    }
2047

    
2048
    $this->assertEqual($num_records, 6, 'Returned the correct number of total rows.');
2049
  }
2050

    
2051
  /**
2052
   * Test GROUP BY and HAVING clauses together.
2053
   */
2054
  function testGroupByAndHaving() {
2055
    $query = db_select('test_task', 't');
2056
    $count_field = $query->addExpression('COUNT(task)', 'num');
2057
    $task_field = $query->addField('t', 'task');
2058
    $query->orderBy($count_field);
2059
    $query->groupBy($task_field);
2060
    $query->having('COUNT(task) >= 2');
2061
    $result = $query->execute();
2062

    
2063
    $num_records = 0;
2064
    $last_count = 0;
2065
    $records = array();
2066
    foreach ($result as $record) {
2067
      $num_records++;
2068
      $this->assertTrue($record->$count_field >= 2, 'Record has the minimum count.');
2069
      $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
2070
      $last_count = $record->$count_field;
2071
      $records[$record->$task_field] = $record->$count_field;
2072
    }
2073

    
2074
    $correct_results = array(
2075
      'sleep' => 2,
2076
    );
2077

    
2078
    foreach ($correct_results as $task => $count) {
2079
      $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
2080
    }
2081

    
2082
    $this->assertEqual($num_records, 1, 'Returned the correct number of total rows.');
2083
  }
2084

    
2085
  /**
2086
   * Test range queries. The SQL clause varies with the database.
2087
   */
2088
  function testRange() {
2089
    $query = db_select('test');
2090
    $name_field = $query->addField('test', 'name');
2091
    $age_field = $query->addField('test', 'age', 'age');
2092
    $query->range(0, 2);
2093
    $result = $query->execute();
2094

    
2095
    $num_records = 0;
2096
    foreach ($result as $record) {
2097
      $num_records++;
2098
    }
2099

    
2100
    $this->assertEqual($num_records, 2, 'Returned the correct number of rows.');
2101
  }
2102

    
2103
  /**
2104
   * Test distinct queries.
2105
   */
2106
  function testDistinct() {
2107
    $query = db_select('test_task');
2108
    $task_field = $query->addField('test_task', 'task');
2109
    $query->distinct();
2110
    $result = $query->execute();
2111

    
2112
    $num_records = 0;
2113
    foreach ($result as $record) {
2114
      $num_records++;
2115
    }
2116

    
2117
    $this->assertEqual($num_records, 6, 'Returned the correct number of rows.');
2118
  }
2119

    
2120
  /**
2121
   * Test that we can generate a count query from a built query.
2122
   */
2123
  function testCountQuery() {
2124
    $query = db_select('test');
2125
    $name_field = $query->addField('test', 'name');
2126
    $age_field = $query->addField('test', 'age', 'age');
2127
    $query->orderBy('name');
2128

    
2129
    $count = $query->countQuery()->execute()->fetchField();
2130

    
2131
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2132

    
2133
    // Now make sure we didn't break the original query!  We should still have
2134
    // all of the fields we asked for.
2135
    $record = $query->execute()->fetch();
2136
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2137
    $this->assertEqual($record->$age_field, 27, 'Correct data retrieved.');
2138
  }
2139

    
2140
  function testHavingCountQuery() {
2141
    $query = db_select('test')
2142
      ->extend('PagerDefault')
2143
      ->groupBy('age')
2144
      ->having('age + 1 > 0');
2145
    $query->addField('test', 'age');
2146
    $query->addExpression('age + 1');
2147
    $count = count($query->execute()->fetchCol());
2148
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2149
  }
2150

    
2151
  /**
2152
   * Test that countQuery properly removes 'all_fields' statements and
2153
   * ordering clauses.
2154
   */
2155
  function testCountQueryRemovals() {
2156
    $query = db_select('test');
2157
    $query->fields('test');
2158
    $query->orderBy('name');
2159
    $count = $query->countQuery();
2160

    
2161
    // Check that the 'all_fields' statement is handled properly.
2162
    $tables = $query->getTables();
2163
    $this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.');
2164
    $tables = $count->getTables();
2165
    $this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.');
2166

    
2167
    // Check that the ordering clause is handled properly.
2168
    $orderby = $query->getOrderBy();
2169
    $this->assertEqual($orderby['name'], 'ASC', 'Query correctly sets ordering clause.');
2170
    $orderby = $count->getOrderBy();
2171
    $this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering caluse.');
2172

    
2173
    // Make sure that the count query works.
2174
    $count = $count->execute()->fetchField();
2175

    
2176
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2177
  }
2178

    
2179

    
2180
  /**
2181
   * Test that countQuery properly removes fields and expressions.
2182
   */
2183
  function testCountQueryFieldRemovals() {
2184
    // countQuery should remove all fields and expressions, so this can be
2185
    // tested by adding a non-existent field and expression: if it ends
2186
    // up in the query, an error will be thrown. If not, it will return the
2187
    // number of records, which in this case happens to be 4 (there are four
2188
    // records in the {test} table).
2189
    $query = db_select('test');
2190
    $query->fields('test', array('fail'));
2191
    $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed fields');
2192

    
2193
    $query = db_select('test');
2194
    $query->addExpression('fail');
2195
    $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions');
2196
  }
2197

    
2198
  /**
2199
   * Test that we can generate a count query from a query with distinct.
2200
   */
2201
  function testCountQueryDistinct() {
2202
    $query = db_select('test_task');
2203
    $task_field = $query->addField('test_task', 'task');
2204
    $query->distinct();
2205

    
2206
    $count = $query->countQuery()->execute()->fetchField();
2207

    
2208
    $this->assertEqual($count, 6, 'Counted the correct number of records.');
2209
  }
2210

    
2211
  /**
2212
   * Test that we can generate a count query from a query with GROUP BY.
2213
   */
2214
  function testCountQueryGroupBy() {
2215
    $query = db_select('test_task');
2216
    $pid_field = $query->addField('test_task', 'pid');
2217
    $query->groupBy('pid');
2218

    
2219
    $count = $query->countQuery()->execute()->fetchField();
2220

    
2221
    $this->assertEqual($count, 3, 'Counted the correct number of records.');
2222

    
2223
    // Use a column alias as, without one, the query can succeed for the wrong
2224
    // reason.
2225
    $query = db_select('test_task');
2226
    $pid_field = $query->addField('test_task', 'pid', 'pid_alias');
2227
    $query->addExpression('COUNT(test_task.task)', 'count');
2228
    $query->groupBy('pid_alias');
2229
    $query->orderBy('pid_alias', 'asc');
2230

    
2231
    $count = $query->countQuery()->execute()->fetchField();
2232

    
2233
    $this->assertEqual($count, 3, 'Counted the correct number of records.');
2234
  }
2235

    
2236
  /**
2237
   * Confirm that we can properly nest conditional clauses.
2238
   */
2239
  function testNestedConditions() {
2240
    // This query should translate to:
2241
    // "SELECT job FROM {test} WHERE name = 'Paul' AND (age = 26 OR age = 27)"
2242
    // That should find only one record. Yes it's a non-optimal way of writing
2243
    // that query but that's not the point!
2244
    $query = db_select('test');
2245
    $query->addField('test', 'job');
2246
    $query->condition('name', 'Paul');
2247
    $query->condition(db_or()->condition('age', 26)->condition('age', 27));
2248

    
2249
    $job = $query->execute()->fetchField();
2250
    $this->assertEqual($job, 'Songwriter', 'Correct data retrieved.');
2251
  }
2252

    
2253
  /**
2254
   * Confirm we can join on a single table twice with a dynamic alias.
2255
   */
2256
  function testJoinTwice() {
2257
    $query = db_select('test')->fields('test');
2258
    $alias = $query->join('test', 'test', 'test.job = %alias.job');
2259
    $query->addField($alias, 'name', 'othername');
2260
    $query->addField($alias, 'job', 'otherjob');
2261
    $query->where("$alias.name <> test.name");
2262
    $crowded_job = $query->execute()->fetch();
2263
    $this->assertEqual($crowded_job->job, $crowded_job->otherjob, 'Correctly joined same table twice.');
2264
    $this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.');
2265
  }
2266

    
2267
}
2268

    
2269
/**
2270
 * Test more complex select statements, part 2.
2271
 */
2272
class DatabaseSelectComplexTestCase2 extends DatabaseTestCase {
2273

    
2274
  public static function getInfo() {
2275
    return array(
2276
      'name' => 'Select tests, complex 2',
2277
      'description' => 'Test the Select query builder with even more complex queries.',
2278
      'group' => 'Database',
2279
    );
2280
  }
2281

    
2282
  function setUp() {
2283
    DrupalWebTestCase::setUp('database_test', 'node_access_test');
2284

    
2285
    $schema['test'] = drupal_get_schema('test');
2286
    $schema['test_people'] = drupal_get_schema('test_people');
2287
    $schema['test_one_blob'] = drupal_get_schema('test_one_blob');
2288
    $schema['test_two_blobs'] = drupal_get_schema('test_two_blobs');
2289
    $schema['test_task'] = drupal_get_schema('test_task');
2290

    
2291
    $this->installTables($schema);
2292

    
2293
    $this->addSampleData();
2294
  }
2295

    
2296
  /**
2297
   * Test that we can join on a query.
2298
   */
2299
  function testJoinSubquery() {
2300
    $acct = $this->drupalCreateUser(array('access content'));
2301
    $this->drupalLogin($acct);
2302

    
2303
    $query = db_select('test_task', 'tt', array('target' => 'slave'));
2304
    $query->addExpression('tt.pid + 1', 'abc');
2305
    $query->condition('priority', 1, '>');
2306
    $query->condition('priority', 100, '<');
2307

    
2308
    $subquery = db_select('test', 'tp');
2309
    $subquery->join('test_one_blob', 'tpb', 'tp.id = tpb.id');
2310
    $subquery->join('node', 'n', 'tp.id = n.nid');
2311
    $subquery->addTag('node_access');
2312
    $subquery->addMetaData('account', $acct);
2313
    $subquery->addField('tp', 'id');
2314
    $subquery->condition('age', 5, '>');
2315
    $subquery->condition('age', 500, '<');
2316

    
2317
    $query->leftJoin($subquery, 'sq', 'tt.pid = sq.id');
2318
    $query->join('test_one_blob', 'tb3', 'tt.pid = tb3.id');
2319

    
2320
    // Construct the query string.
2321
    // This is the same sequence that SelectQuery::execute() goes through.
2322
    $query->preExecute();
2323
    $query->getArguments();
2324
    $str = (string) $query;
2325

    
2326
    // Verify that the string only has one copy of condition placeholder 0.
2327
    $pos = strpos($str, 'db_condition_placeholder_0', 0);
2328
    $pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1);
2329
    $this->assertFalse($pos2, 'Condition placeholder is not repeated.');
2330
  }
2331
}
2332

    
2333
class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase {
2334

    
2335
  public static function getInfo() {
2336
    return array(
2337
      'name' => 'Pager query tests',
2338
      'description' => 'Test the pager query extender.',
2339
      'group' => 'Database',
2340
    );
2341
  }
2342

    
2343
  /**
2344
   * Confirm that a pager query returns the correct results.
2345
   *
2346
   * Note that we have to make an HTTP request to a test page handler
2347
   * because the pager depends on GET parameters.
2348
   */
2349
  function testEvenPagerQuery() {
2350
    // To keep the test from being too brittle, we determine up front
2351
    // what the page count should be dynamically, and pass the control
2352
    // information forward to the actual query on the other side of the
2353
    // HTTP request.
2354
    $limit = 2;
2355
    $count = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
2356

    
2357
    $correct_number = $limit;
2358
    $num_pages = floor($count / $limit);
2359

    
2360
    // If there is no remainder from rounding, subtract 1 since we index from 0.
2361
    if (!($num_pages * $limit < $count)) {
2362
      $num_pages--;
2363
    }
2364

    
2365
    for ($page = 0; $page <= $num_pages; ++$page) {
2366
      $this->drupalGet('database_test/pager_query_even/' . $limit, array('query' => array('page' => $page)));
2367
      $data = json_decode($this->drupalGetContent());
2368

    
2369
      if ($page == $num_pages) {
2370
        $correct_number = $count - ($limit * $page);
2371
      }
2372

    
2373
      $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
2374
    }
2375
  }
2376

    
2377
  /**
2378
   * Confirm that a pager query returns the correct results.
2379
   *
2380
   * Note that we have to make an HTTP request to a test page handler
2381
   * because the pager depends on GET parameters.
2382
   */
2383
  function testOddPagerQuery() {
2384
    // To keep the test from being too brittle, we determine up front
2385
    // what the page count should be dynamically, and pass the control
2386
    // information forward to the actual query on the other side of the
2387
    // HTTP request.
2388
    $limit = 2;
2389
    $count = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
2390

    
2391
    $correct_number = $limit;
2392
    $num_pages = floor($count / $limit);
2393

    
2394
    // If there is no remainder from rounding, subtract 1 since we index from 0.
2395
    if (!($num_pages * $limit < $count)) {
2396
      $num_pages--;
2397
    }
2398

    
2399
    for ($page = 0; $page <= $num_pages; ++$page) {
2400
      $this->drupalGet('database_test/pager_query_odd/' . $limit, array('query' => array('page' => $page)));
2401
      $data = json_decode($this->drupalGetContent());
2402

    
2403
      if ($page == $num_pages) {
2404
        $correct_number = $count - ($limit * $page);
2405
      }
2406

    
2407
      $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
2408
    }
2409
  }
2410

    
2411
  /**
2412
   * Confirm that a pager query with inner pager query returns valid results.
2413
   *
2414
   * This is a regression test for #467984.
2415
   */
2416
  function testInnerPagerQuery() {
2417
    $query = db_select('test', 't')->extend('PagerDefault');
2418
    $query
2419
      ->fields('t', array('age'))
2420
      ->orderBy('age')
2421
      ->limit(5);
2422

    
2423
    $outer_query = db_select($query);
2424
    $outer_query->addField('subquery', 'age');
2425

    
2426
    $ages = $outer_query
2427
      ->execute()
2428
      ->fetchCol();
2429
    $this->assertEqual($ages, array(25, 26, 27, 28), 'Inner pager query returned the correct ages.');
2430
  }
2431

    
2432
  /**
2433
   * Confirm that a paging query with a having expression returns valid results.
2434
   *
2435
   * This is a regression test for #467984.
2436
   */
2437
  function testHavingPagerQuery() {
2438
    $query = db_select('test', 't')->extend('PagerDefault');
2439
    $query
2440
      ->fields('t', array('name'))
2441
      ->orderBy('name')
2442
      ->groupBy('name')
2443
      ->having('MAX(age) > :count', array(':count' => 26))
2444
      ->limit(5);
2445

    
2446
    $ages = $query
2447
      ->execute()
2448
      ->fetchCol();
2449
    $this->assertEqual($ages, array('George', 'Ringo'), 'Pager query with having expression returned the correct ages.');
2450
  }
2451

    
2452
  /**
2453
   * Confirm that every pager gets a valid non-overlaping element ID.
2454
   */
2455
  function testElementNumbers() {
2456
    $_GET['page'] = '3, 2, 1, 0';
2457

    
2458
    $name = db_select('test', 't')->extend('PagerDefault')
2459
      ->element(2)
2460
      ->fields('t', array('name'))
2461
      ->orderBy('age')
2462
      ->limit(1)
2463
      ->execute()
2464
      ->fetchField();
2465
    $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.');
2466

    
2467
    // Setting an element smaller than the previous one
2468
    // should not overwrite the pager $maxElement with a smaller value.
2469
    $name = db_select('test', 't')->extend('PagerDefault')
2470
      ->element(1)
2471
      ->fields('t', array('name'))
2472
      ->orderBy('age')
2473
      ->limit(1)
2474
      ->execute()
2475
      ->fetchField();
2476
    $this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.');
2477

    
2478
    $name = db_select('test', 't')->extend('PagerDefault')
2479
      ->fields('t', array('name'))
2480
      ->orderBy('age')
2481
      ->limit(1)
2482
      ->execute()
2483
      ->fetchField();
2484
    $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.');
2485

    
2486
    unset($_GET['page']);
2487
  }
2488
}
2489

    
2490

    
2491
class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase {
2492

    
2493
  public static function getInfo() {
2494
    return array(
2495
      'name' => 'Tablesort query tests',
2496
      'description' => 'Test the tablesort query extender.',
2497
      'group' => 'Database',
2498
    );
2499
  }
2500

    
2501
  /**
2502
   * Confirm that a tablesort query returns the correct results.
2503
   *
2504
   * Note that we have to make an HTTP request to a test page handler
2505
   * because the pager depends on GET parameters.
2506
   */
2507
  function testTableSortQuery() {
2508
    $sorts = array(
2509
      array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
2510
      array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
2511
      array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
2512
      array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
2513
      // more elements here
2514

    
2515
    );
2516

    
2517
    foreach ($sorts as $sort) {
2518
      $this->drupalGet('database_test/tablesort/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
2519
      $data = json_decode($this->drupalGetContent());
2520

    
2521
      $first = array_shift($data->tasks);
2522
      $last = array_pop($data->tasks);
2523

    
2524
      $this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.');
2525
      $this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.');
2526
    }
2527
  }
2528

    
2529
  /**
2530
   * Confirm that if a tablesort's orderByHeader is called before another orderBy, that the header happens first.
2531
   *
2532
   */
2533
  function testTableSortQueryFirst() {
2534
    $sorts = array(
2535
      array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
2536
      array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
2537
      array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
2538
      array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
2539
      // more elements here
2540

    
2541
    );
2542

    
2543
    foreach ($sorts as $sort) {
2544
      $this->drupalGet('database_test/tablesort_first/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
2545
      $data = json_decode($this->drupalGetContent());
2546

    
2547
      $first = array_shift($data->tasks);
2548
      $last = array_pop($data->tasks);
2549

    
2550
      $this->assertEqual($first->task, $sort['first'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort'])));
2551
      $this->assertEqual($last->task, $sort['last'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort'])));
2552
    }
2553
  }
2554

    
2555
  /**
2556
   * Confirm that if a sort is not set in a tableselect form there is no error thrown when using the default.
2557
   */
2558
  function testTableSortDefaultSort() {
2559
    $this->drupalGet('database_test/tablesort_default_sort');
2560
    // Any PHP errors or notices thrown would trigger a simpletest exception, so
2561
    // no additional assertions are needed.
2562
  }
2563
}
2564

    
2565
/**
2566
 * Select tagging tests.
2567
 *
2568
 * Tags are a way to flag queries for alter hooks so they know
2569
 * what type of query it is, such as "node_access".
2570
 */
2571
class DatabaseTaggingTestCase extends DatabaseTestCase {
2572

    
2573
  public static function getInfo() {
2574
    return array(
2575
      'name' => 'Query tagging tests',
2576
      'description' => 'Test the tagging capabilities of the Select builder.',
2577
      'group' => 'Database',
2578
    );
2579
  }
2580

    
2581
  /**
2582
   * Confirm that a query has a "tag" added to it.
2583
   */
2584
  function testHasTag() {
2585
    $query = db_select('test');
2586
    $query->addField('test', 'name');
2587
    $query->addField('test', 'age', 'age');
2588

    
2589
    $query->addTag('test');
2590

    
2591
    $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
2592
    $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
2593
  }
2594

    
2595
  /**
2596
   * Test query tagging "has all of these tags" functionality.
2597
   */
2598
  function testHasAllTags() {
2599
    $query = db_select('test');
2600
    $query->addField('test', 'name');
2601
    $query->addField('test', 'age', 'age');
2602

    
2603
    $query->addTag('test');
2604
    $query->addTag('other');
2605

    
2606
    $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
2607
    $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
2608
  }
2609

    
2610
  /**
2611
   * Test query tagging "has at least one of these tags" functionality.
2612
   */
2613
  function testHasAnyTag() {
2614
    $query = db_select('test');
2615
    $query->addField('test', 'name');
2616
    $query->addField('test', 'age', 'age');
2617

    
2618
    $query->addTag('test');
2619

    
2620
    $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
2621
    $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
2622
  }
2623

    
2624
  /**
2625
   * Test that we can attach meta data to a query object.
2626
   *
2627
   * This is how we pass additional context to alter hooks.
2628
   */
2629
  function testMetaData() {
2630
    $query = db_select('test');
2631
    $query->addField('test', 'name');
2632
    $query->addField('test', 'age', 'age');
2633

    
2634
    $data = array(
2635
      'a' => 'A',
2636
      'b' => 'B',
2637
    );
2638

    
2639
    $query->addMetaData('test', $data);
2640

    
2641
    $return = $query->getMetaData('test');
2642
    $this->assertEqual($data, $return, 'Corect metadata returned.');
2643

    
2644
    $return = $query->getMetaData('nothere');
2645
    $this->assertNull($return, 'Non-existent key returned NULL.');
2646
  }
2647
}
2648

    
2649
/**
2650
 * Select alter tests.
2651
 *
2652
 * @see database_test_query_alter()
2653
 */
2654
class DatabaseAlterTestCase extends DatabaseTestCase {
2655

    
2656
  public static function getInfo() {
2657
    return array(
2658
      'name' => 'Query altering tests',
2659
      'description' => 'Test the hook_query_alter capabilities of the Select builder.',
2660
      'group' => 'Database',
2661
    );
2662
  }
2663

    
2664
  /**
2665
   * Test that we can do basic alters.
2666
   */
2667
  function testSimpleAlter() {
2668
    $query = db_select('test');
2669
    $query->addField('test', 'name');
2670
    $query->addField('test', 'age', 'age');
2671
    $query->addTag('database_test_alter_add_range');
2672

    
2673
    $result = $query->execute();
2674

    
2675
    $num_records = 0;
2676
    foreach ($result as $record) {
2677
      $num_records++;
2678
    }
2679

    
2680
    $this->assertEqual($num_records, 2, 'Returned the correct number of rows.');
2681
  }
2682

    
2683
  /**
2684
   * Test that we can alter the joins on a query.
2685
   */
2686
  function testAlterWithJoin() {
2687
    $query = db_select('test_task');
2688
    $tid_field = $query->addField('test_task', 'tid');
2689
    $task_field = $query->addField('test_task', 'task');
2690
    $query->orderBy($task_field);
2691
    $query->addTag('database_test_alter_add_join');
2692

    
2693
    $result = $query->execute();
2694

    
2695
    $records = $result->fetchAll();
2696

    
2697
    $this->assertEqual(count($records), 2, 'Returned the correct number of rows.');
2698

    
2699
    $this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.');
2700
    $this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.');
2701
    $this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.');
2702
    $this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.');
2703
    $this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.');
2704
    $this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.');
2705
  }
2706

    
2707
  /**
2708
   * Test that we can alter a query's conditionals.
2709
   */
2710
  function testAlterChangeConditional() {
2711
    $query = db_select('test_task');
2712
    $tid_field = $query->addField('test_task', 'tid');
2713
    $pid_field = $query->addField('test_task', 'pid');
2714
    $task_field = $query->addField('test_task', 'task');
2715
    $people_alias = $query->join('test', 'people', "test_task.pid = people.id");
2716
    $name_field = $query->addField($people_alias, 'name', 'name');
2717
    $query->condition('test_task.tid', '1');
2718
    $query->orderBy($tid_field);
2719
    $query->addTag('database_test_alter_change_conditional');
2720

    
2721
    $result = $query->execute();
2722

    
2723
    $records = $result->fetchAll();
2724

    
2725
    $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
2726
    $this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.');
2727
    $this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.');
2728
    $this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.');
2729
    $this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.');
2730
  }
2731

    
2732
  /**
2733
   * Test that we can alter the fields of a query.
2734
   */
2735
  function testAlterChangeFields() {
2736
    $query = db_select('test');
2737
    $name_field = $query->addField('test', 'name');
2738
    $age_field = $query->addField('test', 'age', 'age');
2739
    $query->orderBy('name');
2740
    $query->addTag('database_test_alter_change_fields');
2741

    
2742
    $record = $query->execute()->fetch();
2743
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2744
    $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.');
2745
  }
2746

    
2747
  /**
2748
   * Test that we can alter expressions in the query.
2749
   */
2750
  function testAlterExpression() {
2751
    $query = db_select('test');
2752
    $name_field = $query->addField('test', 'name');
2753
    $age_field = $query->addExpression("age*2", 'double_age');
2754
    $query->condition('age', 27);
2755
    $query->addTag('database_test_alter_change_expressions');
2756
    $result = $query->execute();
2757

    
2758
    // Ensure that we got the right record.
2759
    $record = $result->fetch();
2760

    
2761
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2762
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2763
  }
2764

    
2765
  /**
2766
   * Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter().
2767
   */
2768
  function testAlterRemoveRange() {
2769
    $query = db_select('test');
2770
    $query->addField('test', 'name');
2771
    $query->addField('test', 'age', 'age');
2772
    $query->range(0, 2);
2773
    $query->addTag('database_test_alter_remove_range');
2774

    
2775
    $num_records = count($query->execute()->fetchAll());
2776

    
2777
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
2778
  }
2779

    
2780
  /**
2781
   * Test that we can do basic alters on subqueries.
2782
   */
2783
  function testSimpleAlterSubquery() {
2784
    // Create a sub-query with an alter tag.
2785
    $subquery = db_select('test', 'p');
2786
    $subquery->addField('p', 'name');
2787
    $subquery->addField('p', 'id');
2788
    // Pick out George.
2789
    $subquery->condition('age', 27);
2790
    $subquery->addExpression("age*2", 'double_age');
2791
    // This query alter should change it to age * 3.
2792
    $subquery->addTag('database_test_alter_change_expressions');
2793

    
2794
    // Create a main query and join to sub-query.
2795
    $query = db_select('test_task', 'tt');
2796
    $query->join($subquery, 'pq', 'pq.id = tt.pid');
2797
    $age_field = $query->addField('pq', 'double_age');
2798
    $name_field = $query->addField('pq', 'name');
2799

    
2800
    $record = $query->execute()->fetch();
2801
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2802
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2803
  }
2804
}
2805

    
2806
/**
2807
 * Regression tests.
2808
 */
2809
class DatabaseRegressionTestCase extends DatabaseTestCase {
2810

    
2811
  public static function getInfo() {
2812
    return array(
2813
      'name' => 'Regression tests',
2814
      'description' => 'Regression tests cases for the database layer.',
2815
      'group' => 'Database',
2816
    );
2817
  }
2818

    
2819
  /**
2820
   * Regression test for #310447.
2821
   *
2822
   * Tries to insert non-ascii UTF-8 data in a database column and checks
2823
   * if its stored properly.
2824
   */
2825
  function testRegression_310447() {
2826
    // That's a 255 character UTF-8 string.
2827
    $name = str_repeat("é", 255);
2828
    db_insert('test')
2829
      ->fields(array(
2830
        'name' => $name,
2831
        'age' => 20,
2832
        'job' => 'Dancer',
2833
      ))->execute();
2834

    
2835
    $from_database = db_query('SELECT name FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
2836
    $this->assertIdentical($name, $from_database, "The database handles UTF-8 characters cleanly.");
2837
  }
2838

    
2839
  /**
2840
   * Test the db_table_exists() function.
2841
   */
2842
  function testDBTableExists() {
2843
    $this->assertIdentical(TRUE, db_table_exists('node'), 'Returns true for existent table.');
2844
    $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.');
2845
  }
2846

    
2847
  /**
2848
   * Test the db_field_exists() function.
2849
   */
2850
  function testDBFieldExists() {
2851
    $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), 'Returns true for existent column.');
2852
    $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), 'Returns false for nonexistent column.');
2853
  }
2854

    
2855
  /**
2856
   * Test the db_index_exists() function.
2857
   */
2858
  function testDBIndexExists() {
2859
    $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.');
2860
    $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.');
2861
  }
2862
}
2863

    
2864
/**
2865
 * Query logging tests.
2866
 */
2867
class DatabaseLoggingTestCase extends DatabaseTestCase {
2868

    
2869
  public static function getInfo() {
2870
    return array(
2871
      'name' => 'Query logging',
2872
      'description' => 'Test the query logging facility.',
2873
      'group' => 'Database',
2874
    );
2875
  }
2876

    
2877
  /**
2878
   * Test that we can log the existence of a query.
2879
   */
2880
  function testEnableLogging() {
2881
    $log = Database::startLog('testing');
2882

    
2883
    db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
2884
    db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol();
2885

    
2886
    // Trigger a call that does not have file in the backtrace.
2887
    call_user_func_array('db_query', array('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo')))->fetchCol();
2888

    
2889
    $queries = Database::getLog('testing', 'default');
2890

    
2891
    $this->assertEqual(count($queries), 3, 'Correct number of queries recorded.');
2892

    
2893
    foreach ($queries as $query) {
2894
      $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.');
2895
    }
2896
  }
2897

    
2898
  /**
2899
   * Test that we can run two logs in parallel.
2900
   */
2901
  function testEnableMultiLogging() {
2902
    Database::startLog('testing1');
2903

    
2904
    db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
2905

    
2906
    Database::startLog('testing2');
2907

    
2908
    db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol();
2909

    
2910
    $queries1 = Database::getLog('testing1');
2911
    $queries2 = Database::getLog('testing2');
2912

    
2913
    $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.');
2914
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.');
2915
  }
2916

    
2917
  /**
2918
   * Test that we can log queries against multiple targets on the same connection.
2919
   */
2920
  function testEnableTargetLogging() {
2921
    // Clone the master credentials to a slave connection and to another fake
2922
    // connection.
2923
    $connection_info = Database::getConnectionInfo('default');
2924
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
2925

    
2926
    Database::startLog('testing1');
2927

    
2928
    db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
2929

    
2930
    db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'slave'));//->fetchCol();
2931

    
2932
    $queries1 = Database::getLog('testing1');
2933

    
2934
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
2935
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
2936
    $this->assertEqual($queries1[1]['target'], 'slave', 'Second query used slave target.');
2937
  }
2938

    
2939
  /**
2940
   * Test that logs to separate targets collapse to the same connection properly.
2941
   *
2942
   * This test is identical to the one above, except that it doesn't create
2943
   * a fake target so the query should fall back to running on the default
2944
   * target.
2945
   */
2946
  function testEnableTargetLoggingNoTarget() {
2947
    Database::startLog('testing1');
2948

    
2949
    db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
2950

    
2951
    // We use "fake" here as a target because any non-existent target will do.
2952
    // However, because all of the tests in this class share a single page
2953
    // request there is likely to be a target of "slave" from one of the other
2954
    // unit tests, so we use a target here that we know with absolute certainty
2955
    // does not exist.
2956
    db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'fake'))->fetchCol();
2957

    
2958
    $queries1 = Database::getLog('testing1');
2959

    
2960
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
2961
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
2962
    $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.');
2963
  }
2964

    
2965
  /**
2966
   * Test that we can log queries separately on different connections.
2967
   */
2968
  function testEnableMultiConnectionLogging() {
2969
    // Clone the master credentials to a fake connection.
2970
    // That both connections point to the same physical database is irrelevant.
2971
    $connection_info = Database::getConnectionInfo('default');
2972
    Database::addConnectionInfo('test2', 'default', $connection_info['default']);
2973

    
2974
    Database::startLog('testing1');
2975
    Database::startLog('testing1', 'test2');
2976

    
2977
    db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
2978

    
2979
    $old_key = db_set_active('test2');
2980

    
2981
    db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'), array('target' => 'slave'))->fetchCol();
2982

    
2983
    db_set_active($old_key);
2984

    
2985
    $queries1 = Database::getLog('testing1');
2986
    $queries2 = Database::getLog('testing1', 'test2');
2987

    
2988
    $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.');
2989
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
2990
  }
2991
}
2992

    
2993
/**
2994
 * Query serialization tests.
2995
 */
2996
class DatabaseSerializeQueryTestCase extends DatabaseTestCase {
2997
  public static function getInfo() {
2998
    return array(
2999
      'name' => 'Serialize query',
3000
      'description' => 'Test serializing and unserializing a query.',
3001
      'group' => 'Database',
3002
    );
3003
  }
3004

    
3005
  /**
3006
   * Confirm that a query can be serialized and unserialized.
3007
   */
3008
  function testSerializeQuery() {
3009
    $query = db_select('test');
3010
    $query->addField('test', 'age');
3011
    $query->condition('name', 'Ringo');
3012
    // If this doesn't work, it will throw an exception, so no need for an
3013
    // assertion.
3014
    $query = unserialize(serialize($query));
3015
    $results = $query->execute()->fetchCol();
3016
    $this->assertEqual($results[0], 28, 'Query properly executed after unserialization.');
3017
  }
3018
}
3019

    
3020
/**
3021
 * Range query tests.
3022
 */
3023
class DatabaseRangeQueryTestCase extends DrupalWebTestCase {
3024
  public static function getInfo() {
3025
    return array(
3026
      'name' => 'Range query test',
3027
      'description' => 'Test the Range query functionality.',
3028
      'group' => 'Database',
3029
    );
3030
  }
3031

    
3032
  function setUp() {
3033
    parent::setUp('database_test');
3034
  }
3035

    
3036
  /**
3037
   * Confirm that range query work and return correct result.
3038
   */
3039
  function testRangeQuery() {
3040
    // Test if return correct number of rows.
3041
    $range_rows = db_query_range("SELECT name FROM {system} ORDER BY name", 2, 3)->fetchAll();
3042
    $this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.');
3043

    
3044
    // Test if return target data.
3045
    $raw_rows = db_query('SELECT name FROM {system} ORDER BY name')->fetchAll();
3046
    $raw_rows = array_slice($raw_rows, 2, 3);
3047
    $this->assertEqual($range_rows, $raw_rows, 'Range query work and return target data.');
3048
  }
3049
}
3050

    
3051
/**
3052
 * Temporary query tests.
3053
 */
3054
class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase {
3055
  public static function getInfo() {
3056
    return array(
3057
      'name' => 'Temporary query test',
3058
      'description' => 'Test the temporary query functionality.',
3059
      'group' => 'Database',
3060
    );
3061
  }
3062

    
3063
  function setUp() {
3064
    parent::setUp('database_test');
3065
  }
3066

    
3067
  /**
3068
   * Return the number of rows of a table.
3069
   */
3070
  function countTableRows($table_name) {
3071
    return db_select($table_name)->countQuery()->execute()->fetchField();
3072
  }
3073

    
3074
  /**
3075
   * Confirm that temporary tables work and are limited to one request.
3076
   */
3077
  function testTemporaryQuery() {
3078
    $this->drupalGet('database_test/db_query_temporary');
3079
    $data = json_decode($this->drupalGetContent());
3080
    if ($data) {
3081
      $this->assertEqual($this->countTableRows("system"), $data->row_count, 'The temporary table contains the correct amount of rows.');
3082
      $this->assertFalse(db_table_exists($data->table_name), 'The temporary table is, indeed, temporary.');
3083
    }
3084
    else {
3085
      $this->fail("The creation of the temporary table failed.");
3086
    }
3087

    
3088
    // Now try to run two db_query_temporary() in the same request.
3089
    $table_name_system = db_query_temporary('SELECT status FROM {system}', array());
3090
    $table_name_users = db_query_temporary('SELECT uid FROM {users}', array());
3091

    
3092
    $this->assertEqual($this->countTableRows($table_name_system), $this->countTableRows("system"), 'A temporary table was created successfully in this request.');
3093
    $this->assertEqual($this->countTableRows($table_name_users), $this->countTableRows("users"), 'A second temporary table was created successfully in this request.');
3094
  }
3095
}
3096

    
3097
/**
3098
 * Test how the current database driver interprets the SQL syntax.
3099
 *
3100
 * In order to ensure consistent SQL handling throughout Drupal
3101
 * across multiple kinds of database systems, we test that the
3102
 * database system interprets SQL syntax in an expected fashion.
3103
 */
3104
class DatabaseBasicSyntaxTestCase extends DatabaseTestCase {
3105
  public static function getInfo() {
3106
    return array(
3107
      'name' => 'Basic SQL syntax tests',
3108
      'description' => 'Test SQL syntax interpretation.',
3109
      'group' => 'Database',
3110
    );
3111
  }
3112

    
3113
  function setUp() {
3114
    parent::setUp('database_test');
3115
  }
3116

    
3117
  /**
3118
   * Test for string concatenation.
3119
   */
3120
  function testBasicConcat() {
3121
    $result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', array(
3122
      ':a1' => 'This',
3123
      ':a2' => ' ',
3124
      ':a3' => 'is',
3125
      ':a4' => ' a ',
3126
      ':a5' => 'test.',
3127
    ));
3128
    $this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.');
3129
  }
3130

    
3131
  /**
3132
   * Test for string concatenation with field values.
3133
   */
3134
  function testFieldConcat() {
3135
    $result = db_query('SELECT CONCAT(:a1, CONCAT(name, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', array(
3136
      ':a1' => 'The age of ',
3137
      ':a2' => ' is ',
3138
      ':a3' => '.',
3139
      ':age' => 25,
3140
    ));
3141
    $this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.');
3142
  }
3143

    
3144
  /**
3145
   * Test escaping of LIKE wildcards.
3146
   */
3147
  function testLikeEscape() {
3148
    db_insert('test')
3149
      ->fields(array(
3150
        'name' => 'Ring_',
3151
      ))
3152
      ->execute();
3153

    
3154
    // Match both "Ringo" and "Ring_".
3155
    $num_matches = db_select('test', 't')
3156
      ->condition('name', 'Ring_', 'LIKE')
3157
      ->countQuery()
3158
      ->execute()
3159
      ->fetchField();
3160
    $this->assertIdentical($num_matches, '2', 'Found 2 records.');
3161
    // Match only "Ring_" using a LIKE expression with no wildcards.
3162
    $num_matches = db_select('test', 't')
3163
      ->condition('name', db_like('Ring_'), 'LIKE')
3164
      ->countQuery()
3165
      ->execute()
3166
      ->fetchField();
3167
    $this->assertIdentical($num_matches, '1', 'Found 1 record.');
3168
  }
3169

    
3170
  /**
3171
   * Test LIKE query containing a backslash.
3172
   */
3173
  function testLikeBackslash() {
3174
    db_insert('test')
3175
      ->fields(array('name'))
3176
      ->values(array(
3177
        'name' => 'abcde\f',
3178
      ))
3179
      ->values(array(
3180
        'name' => 'abc%\_',
3181
      ))
3182
      ->execute();
3183

    
3184
    // Match both rows using a LIKE expression with two wildcards and a verbatim
3185
    // backslash.
3186
    $num_matches = db_select('test', 't')
3187
      ->condition('name', 'abc%\\\\_', 'LIKE')
3188
      ->countQuery()
3189
      ->execute()
3190
      ->fetchField();
3191
    $this->assertIdentical($num_matches, '2', 'Found 2 records.');
3192
    // Match only the former using a LIKE expression with no wildcards.
3193
    $num_matches = db_select('test', 't')
3194
      ->condition('name', db_like('abc%\_'), 'LIKE')
3195
      ->countQuery()
3196
      ->execute()
3197
      ->fetchField();
3198
    $this->assertIdentical($num_matches, '1', 'Found 1 record.');
3199
  }
3200
}
3201

    
3202
/**
3203
 * Test case sensitivity handling.
3204
 */
3205
class DatabaseCaseSensitivityTestCase extends DatabaseTestCase {
3206
  public static function getInfo() {
3207
    return array(
3208
      'name' => 'Case sensitivity',
3209
      'description' => 'Test handling case sensitive collation.',
3210
      'group' => 'Database',
3211
    );
3212
  }
3213

    
3214
  /**
3215
   * Test BINARY collation in MySQL.
3216
   */
3217
  function testCaseSensitiveInsert() {
3218
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3219

    
3220
    $john = db_insert('test')
3221
      ->fields(array(
3222
        'name' => 'john', // <- A record already exists with name 'John'.
3223
        'age' => 2,
3224
        'job' => 'Baby',
3225
      ))
3226
      ->execute();
3227

    
3228
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3229
    $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
3230
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
3231
    $this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.');
3232
  }
3233
}
3234

    
3235
/**
3236
 * Test invalid data handling.
3237
 */
3238
class DatabaseInvalidDataTestCase extends DatabaseTestCase {
3239
  public static function getInfo() {
3240
    return array(
3241
      'name' => 'Invalid data',
3242
      'description' => 'Test handling of some invalid data.',
3243
      'group' => 'Database',
3244
    );
3245
  }
3246

    
3247
  function setUp() {
3248
    parent::setUp('database_test');
3249
  }
3250

    
3251
  /**
3252
   * Traditional SQL database systems abort inserts when invalid data is encountered.
3253
   */
3254
  function testInsertDuplicateData() {
3255
    // Try to insert multiple records where at least one has bad data.
3256
    try {
3257
      db_insert('test')
3258
        ->fields(array('name', 'age', 'job'))
3259
        ->values(array(
3260
          'name' => 'Elvis',
3261
          'age' => 63,
3262
          'job' => 'Singer',
3263
        ))->values(array(
3264
          'name' => 'John', // <-- Duplicate value on unique field.
3265
          'age' => 17,
3266
          'job' => 'Consultant',
3267
        ))
3268
        ->values(array(
3269
          'name' => 'Frank',
3270
          'age' => 75,
3271
          'job' => 'Singer',
3272
        ))
3273
        ->execute();
3274
      $this->fail('Insert succeedded when it should not have.');
3275
    }
3276
    catch (Exception $e) {
3277
      // Check if the first record was inserted.
3278
      $name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField();
3279

    
3280
      if ($name == 'Elvis') {
3281
        if (!Database::getConnection()->supportsTransactions()) {
3282
          // This is an expected fail.
3283
          // Database engines that don't support transactions can leave partial
3284
          // inserts in place when an error occurs. This is the case for MySQL
3285
          // when running on a MyISAM table.
3286
          $this->pass("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions");
3287
        }
3288
        else {
3289
          $this->fail('The whole transaction is rolled back when a duplicate key insert occurs.');
3290
        }
3291
      }
3292
      else {
3293
        $this->pass('The whole transaction is rolled back when a duplicate key insert occurs.');
3294
      }
3295

    
3296
      // Ensure the other values were not inserted.
3297
      $record = db_select('test')
3298
        ->fields('test', array('name', 'age'))
3299
        ->condition('age', array(17, 75), 'IN')
3300
        ->execute()->fetchObject();
3301

    
3302
      $this->assertFalse($record, 'The rest of the insert aborted as expected.');
3303
    }
3304
  }
3305

    
3306
}
3307

    
3308
/**
3309
 * Drupal-specific SQL syntax tests.
3310
 */
3311
class DatabaseQueryTestCase extends DatabaseTestCase {
3312
  public static function getInfo() {
3313
    return array(
3314
      'name' => 'Custom query syntax tests',
3315
      'description' => 'Test Drupal\'s extended prepared statement syntax..',
3316
      'group' => 'Database',
3317
    );
3318
  }
3319

    
3320
  function setUp() {
3321
    parent::setUp('database_test');
3322
  }
3323

    
3324
  /**
3325
   * Test that we can specify an array of values in the query by simply passing in an array.
3326
   */
3327
  function testArraySubstitution() {
3328
    $names = db_query('SELECT name FROM {test} WHERE age IN (:ages) ORDER BY age', array(':ages' => array(25, 26, 27)))->fetchAll();
3329

    
3330
    $this->assertEqual(count($names), 3, 'Correct number of names returned');
3331
  }
3332
}
3333

    
3334
/**
3335
 * Test transaction support, particularly nesting.
3336
 *
3337
 * We test nesting by having two transaction layers, an outer and inner. The
3338
 * outer layer encapsulates the inner layer. Our transaction nesting abstraction
3339
 * should allow the outer layer function to call any function it wants,
3340
 * especially the inner layer that starts its own transaction, and be
3341
 * confident that, when the function it calls returns, its own transaction
3342
 * is still "alive."
3343
 *
3344
 * Call structure:
3345
 *   transactionOuterLayer()
3346
 *     Start transaction
3347
 *     transactionInnerLayer()
3348
 *       Start transaction (does nothing in database)
3349
 *       [Maybe decide to roll back]
3350
 *     Do more stuff
3351
 *     Should still be in transaction A
3352
 *
3353
 */
3354
class DatabaseTransactionTestCase extends DatabaseTestCase {
3355

    
3356
  public static function getInfo() {
3357
    return array(
3358
      'name' => 'Transaction tests',
3359
      'description' => 'Test the transaction abstraction system.',
3360
      'group' => 'Database',
3361
    );
3362
  }
3363

    
3364
  /**
3365
   * Helper method for transaction unit test. This "outer layer" transaction
3366
   * starts and then encapsulates the "inner layer" transaction. This nesting
3367
   * is used to evaluate whether the the database transaction API properly
3368
   * supports nesting. By "properly supports," we mean the outer transaction
3369
   * continues to exist regardless of what functions are called and whether
3370
   * those functions start their own transactions.
3371
   *
3372
   * In contrast, a typical database would commit the outer transaction, start
3373
   * a new transaction for the inner layer, commit the inner layer transaction,
3374
   * and then be confused when the outer layer transaction tries to commit its
3375
   * transaction (which was already committed when the inner transaction
3376
   * started).
3377
   *
3378
   * @param $suffix
3379
   *   Suffix to add to field values to differentiate tests.
3380
   * @param $rollback
3381
   *   Whether or not to try rolling back the transaction when we're done.
3382
   * @param $ddl_statement
3383
   *   Whether to execute a DDL statement during the inner transaction.
3384
   */
3385
  protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
3386
    $connection = Database::getConnection();
3387
    $depth = $connection->transactionDepth();
3388
    $txn = db_transaction();
3389

    
3390
    // Insert a single row into the testing table.
3391
    db_insert('test')
3392
      ->fields(array(
3393
        'name' => 'David' . $suffix,
3394
        'age' => '24',
3395
      ))
3396
      ->execute();
3397

    
3398
    $this->assertTrue($connection->inTransaction(), 'In transaction before calling nested transaction.');
3399

    
3400
    // We're already in a transaction, but we call ->transactionInnerLayer
3401
    // to nest another transaction inside the current one.
3402
    $this->transactionInnerLayer($suffix, $rollback, $ddl_statement);
3403

    
3404
    $this->assertTrue($connection->inTransaction(), 'In transaction after calling nested transaction.');
3405

    
3406
    if ($rollback) {
3407
      // Roll back the transaction, if requested.
3408
      // This rollback should propagate to the last savepoint.
3409
      $txn->rollback();
3410
      $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
3411
    }
3412
  }
3413

    
3414
  /**
3415
   * Helper method for transaction unit tests. This "inner layer" transaction
3416
   * is either used alone or nested inside of the "outer layer" transaction.
3417
   *
3418
   * @param $suffix
3419
   *   Suffix to add to field values to differentiate tests.
3420
   * @param $rollback
3421
   *   Whether or not to try rolling back the transaction when we're done.
3422
   * @param $ddl_statement
3423
   *   Whether to execute a DDL statement during the transaction.
3424
   */
3425
  protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
3426
    $connection = Database::getConnection();
3427

    
3428
    $depth = $connection->transactionDepth();
3429
    // Start a transaction. If we're being called from ->transactionOuterLayer,
3430
    // then we're already in a transaction. Normally, that would make starting
3431
    // a transaction here dangerous, but the database API handles this problem
3432
    // for us by tracking the nesting and avoiding the danger.
3433
    $txn = db_transaction();
3434

    
3435
    $depth2 = $connection->transactionDepth();
3436
    $this->assertTrue($depth < $depth2, 'Transaction depth is has increased with new transaction.');
3437

    
3438
    // Insert a single row into the testing table.
3439
    db_insert('test')
3440
      ->fields(array(
3441
        'name' => 'Daniel' . $suffix,
3442
        'age' => '19',
3443
      ))
3444
      ->execute();
3445

    
3446
    $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
3447

    
3448
    if ($ddl_statement) {
3449
      $table = array(
3450
        'fields' => array(
3451
          'id' => array(
3452
            'type' => 'serial',
3453
            'unsigned' => TRUE,
3454
            'not null' => TRUE,
3455
          ),
3456
        ),
3457
        'primary key' => array('id'),
3458
      );
3459
      db_create_table('database_test_1', $table);
3460

    
3461
      $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
3462
    }
3463

    
3464
    if ($rollback) {
3465
      // Roll back the transaction, if requested.
3466
      // This rollback should propagate to the last savepoint.
3467
      $txn->rollback();
3468
      $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
3469
    }
3470
  }
3471

    
3472
  /**
3473
   * Test transaction rollback on a database that supports transactions.
3474
   *
3475
   * If the active connection does not support transactions, this test does nothing.
3476
   */
3477
  function testTransactionRollBackSupported() {
3478
    // This test won't work right if transactions are not supported.
3479
    if (!Database::getConnection()->supportsTransactions()) {
3480
      return;
3481
    }
3482
    try {
3483
      // Create two nested transactions. Roll back from the inner one.
3484
      $this->transactionOuterLayer('B', TRUE);
3485

    
3486
      // Neither of the rows we inserted in the two transaction layers
3487
      // should be present in the tables post-rollback.
3488
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
3489
      $this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.');
3490
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
3491
      $this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.');
3492
    }
3493
    catch (Exception $e) {
3494
      $this->fail($e->getMessage());
3495
    }
3496
  }
3497

    
3498
  /**
3499
   * Test transaction rollback on a database that does not support transactions.
3500
   *
3501
   * If the active driver supports transactions, this test does nothing.
3502
   */
3503
  function testTransactionRollBackNotSupported() {
3504
    // This test won't work right if transactions are supported.
3505
    if (Database::getConnection()->supportsTransactions()) {
3506
      return;
3507
    }
3508
    try {
3509
      // Create two nested transactions. Attempt to roll back from the inner one.
3510
      $this->transactionOuterLayer('B', TRUE);
3511

    
3512
      // Because our current database claims to not support transactions,
3513
      // the inserted rows should be present despite the attempt to roll back.
3514
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
3515
      $this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.');
3516
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
3517
      $this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.');
3518
    }
3519
    catch (Exception $e) {
3520
      $this->fail($e->getMessage());
3521
    }
3522
  }
3523

    
3524
  /**
3525
   * Test committed transaction.
3526
   *
3527
   * The behavior of this test should be identical for connections that support
3528
   * transactions and those that do not.
3529
   */
3530
  function testCommittedTransaction() {
3531
    try {
3532
      // Create two nested transactions. The changes should be committed.
3533
      $this->transactionOuterLayer('A');
3534

    
3535
      // Because we committed, both of the inserted rows should be present.
3536
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField();
3537
      $this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.');
3538
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField();
3539
      $this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.');
3540
    }
3541
    catch (Exception $e) {
3542
      $this->fail($e->getMessage());
3543
    }
3544
  }
3545

    
3546
  /**
3547
   * Test the compatibility of transactions with DDL statements.
3548
   */
3549
  function testTransactionWithDdlStatement() {
3550
    // First, test that a commit works normally, even with DDL statements.
3551
    $transaction = db_transaction();
3552
    $this->insertRow('row');
3553
    $this->executeDDLStatement();
3554
    unset($transaction);
3555
    $this->assertRowPresent('row');
3556

    
3557
    // Even in different order.
3558
    $this->cleanUp();
3559
    $transaction = db_transaction();
3560
    $this->executeDDLStatement();
3561
    $this->insertRow('row');
3562
    unset($transaction);
3563
    $this->assertRowPresent('row');
3564

    
3565
    // Even with stacking.
3566
    $this->cleanUp();
3567
    $transaction = db_transaction();
3568
    $transaction2 = db_transaction();
3569
    $this->executeDDLStatement();
3570
    unset($transaction2);
3571
    $transaction3 = db_transaction();
3572
    $this->insertRow('row');
3573
    unset($transaction3);
3574
    unset($transaction);
3575
    $this->assertRowPresent('row');
3576

    
3577
    // A transaction after a DDL statement should still work the same.
3578
    $this->cleanUp();
3579
    $transaction = db_transaction();
3580
    $transaction2 = db_transaction();
3581
    $this->executeDDLStatement();
3582
    unset($transaction2);
3583
    $transaction3 = db_transaction();
3584
    $this->insertRow('row');
3585
    $transaction3->rollback();
3586
    unset($transaction3);
3587
    unset($transaction);
3588
    $this->assertRowAbsent('row');
3589

    
3590
    // The behavior of a rollback depends on the type of database server.
3591
    if (Database::getConnection()->supportsTransactionalDDL()) {
3592
      // For database servers that support transactional DDL, a rollback
3593
      // of a transaction including DDL statements should be possible.
3594
      $this->cleanUp();
3595
      $transaction = db_transaction();
3596
      $this->insertRow('row');
3597
      $this->executeDDLStatement();
3598
      $transaction->rollback();
3599
      unset($transaction);
3600
      $this->assertRowAbsent('row');
3601

    
3602
      // Including with stacking.
3603
      $this->cleanUp();
3604
      $transaction = db_transaction();
3605
      $transaction2 = db_transaction();
3606
      $this->executeDDLStatement();
3607
      unset($transaction2);
3608
      $transaction3 = db_transaction();
3609
      $this->insertRow('row');
3610
      unset($transaction3);
3611
      $transaction->rollback();
3612
      unset($transaction);
3613
      $this->assertRowAbsent('row');
3614
    }
3615
    else {
3616
      // For database servers that do not support transactional DDL,
3617
      // the DDL statement should commit the transaction stack.
3618
      $this->cleanUp();
3619
      $transaction = db_transaction();
3620
      $this->insertRow('row');
3621
      $this->executeDDLStatement();
3622
      // Rollback the outer transaction.
3623
      try {
3624
        $transaction->rollback();
3625
        unset($transaction);
3626
        // @TODO: an exception should be triggered here, but is not, because
3627
        // "ROLLBACK" fails silently in MySQL if there is no transaction active.
3628
        // $this->fail(t('Rolling back a transaction containing DDL should fail.'));
3629
      }
3630
      catch (DatabaseTransactionNoActiveException $e) {
3631
        $this->pass('Rolling back a transaction containing DDL should fail.');
3632
      }
3633
      $this->assertRowPresent('row');
3634
    }
3635
  }
3636

    
3637
  /**
3638
   * Insert a single row into the testing table.
3639
   */
3640
  protected function insertRow($name) {
3641
    db_insert('test')
3642
      ->fields(array(
3643
        'name' => $name,
3644
      ))
3645
      ->execute();
3646
  }
3647

    
3648
  /**
3649
   * Execute a DDL statement.
3650
   */
3651
  protected function executeDDLStatement() {
3652
    static $count = 0;
3653
    $table = array(
3654
      'fields' => array(
3655
        'id' => array(
3656
          'type' => 'serial',
3657
          'unsigned' => TRUE,
3658
          'not null' => TRUE,
3659
        ),
3660
      ),
3661
      'primary key' => array('id'),
3662
    );
3663
    db_create_table('database_test_' . ++$count, $table);
3664
  }
3665

    
3666
  /**
3667
   * Start over for a new test.
3668
   */
3669
  protected function cleanUp() {
3670
    db_truncate('test')
3671
      ->execute();
3672
  }
3673

    
3674
  /**
3675
   * Assert that a given row is present in the test table.
3676
   *
3677
   * @param $name
3678
   *   The name of the row.
3679
   * @param $message
3680
   *   The message to log for the assertion.
3681
   */
3682
  function assertRowPresent($name, $message = NULL) {
3683
    if (!isset($message)) {
3684
      $message = format_string('Row %name is present.', array('%name' => $name));
3685
    }
3686
    $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
3687
    return $this->assertTrue($present, $message);
3688
  }
3689

    
3690
  /**
3691
   * Assert that a given row is absent from the test table.
3692
   *
3693
   * @param $name
3694
   *   The name of the row.
3695
   * @param $message
3696
   *   The message to log for the assertion.
3697
   */
3698
  function assertRowAbsent($name, $message = NULL) {
3699
    if (!isset($message)) {
3700
      $message = format_string('Row %name is absent.', array('%name' => $name));
3701
    }
3702
    $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
3703
    return $this->assertFalse($present, $message);
3704
  }
3705

    
3706
  /**
3707
   * Test transaction stacking and commit / rollback.
3708
   */
3709
  function testTransactionStacking() {
3710
    // This test won't work right if transactions are not supported.
3711
    if (!Database::getConnection()->supportsTransactions()) {
3712
      return;
3713
    }
3714

    
3715
    $database = Database::getConnection();
3716

    
3717
    // Standard case: pop the inner transaction before the outer transaction.
3718
    $transaction = db_transaction();
3719
    $this->insertRow('outer');
3720
    $transaction2 = db_transaction();
3721
    $this->insertRow('inner');
3722
    // Pop the inner transaction.
3723
    unset($transaction2);
3724
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction');
3725
    // Pop the outer transaction.
3726
    unset($transaction);
3727
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction');
3728
    $this->assertRowPresent('outer');
3729
    $this->assertRowPresent('inner');
3730

    
3731
    // Pop the transaction in a different order they have been pushed.
3732
    $this->cleanUp();
3733
    $transaction = db_transaction();
3734
    $this->insertRow('outer');
3735
    $transaction2 = db_transaction();
3736
    $this->insertRow('inner');
3737
    // Pop the outer transaction, nothing should happen.
3738
    unset($transaction);
3739
    $this->insertRow('inner-after-outer-commit');
3740
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3741
    // Pop the inner transaction, the whole transaction should commit.
3742
    unset($transaction2);
3743
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3744
    $this->assertRowPresent('outer');
3745
    $this->assertRowPresent('inner');
3746
    $this->assertRowPresent('inner-after-outer-commit');
3747

    
3748
    // Rollback the inner transaction.
3749
    $this->cleanUp();
3750
    $transaction = db_transaction();
3751
    $this->insertRow('outer');
3752
    $transaction2 = db_transaction();
3753
    $this->insertRow('inner');
3754
    // Now rollback the inner transaction.
3755
    $transaction2->rollback();
3756
    unset($transaction2);
3757
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3758
    // Pop the outer transaction, it should commit.
3759
    $this->insertRow('outer-after-inner-rollback');
3760
    unset($transaction);
3761
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3762
    $this->assertRowPresent('outer');
3763
    $this->assertRowAbsent('inner');
3764
    $this->assertRowPresent('outer-after-inner-rollback');
3765

    
3766
    // Rollback the inner transaction after committing the outer one.
3767
    $this->cleanUp();
3768
    $transaction = db_transaction();
3769
    $this->insertRow('outer');
3770
    $transaction2 = db_transaction();
3771
    $this->insertRow('inner');
3772
    // Pop the outer transaction, nothing should happen.
3773
    unset($transaction);
3774
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3775
    // Now rollback the inner transaction, it should rollback.
3776
    $transaction2->rollback();
3777
    unset($transaction2);
3778
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3779
    $this->assertRowPresent('outer');
3780
    $this->assertRowAbsent('inner');
3781

    
3782
    // Rollback the outer transaction while the inner transaction is active.
3783
    // In that case, an exception will be triggered because we cannot
3784
    // ensure that the final result will have any meaning.
3785
    $this->cleanUp();
3786
    $transaction = db_transaction();
3787
    $this->insertRow('outer');
3788
    $transaction2 = db_transaction();
3789
    $this->insertRow('inner');
3790
    $transaction3 = db_transaction();
3791
    $this->insertRow('inner2');
3792
    // Rollback the outer transaction.
3793
    try {
3794
      $transaction->rollback();
3795
      unset($transaction);
3796
      $this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
3797
    }
3798
    catch (DatabaseTransactionOutOfOrderException $e) {
3799
      $this->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
3800
    }
3801
    $this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction');
3802
    // Try to commit one inner transaction.
3803
    unset($transaction3);
3804
    $this->pass('Trying to commit an inner transaction resulted in an exception.');
3805
    // Try to rollback one inner transaction.
3806
    try {
3807
      $transaction->rollback();
3808
      unset($transaction2);
3809
      $this->fail('Trying to commit an inner transaction resulted in an exception.');
3810
    }
3811
    catch (DatabaseTransactionNoActiveException $e) {
3812
      $this->pass('Trying to commit an inner transaction resulted in an exception.');
3813
    }
3814
    $this->assertRowAbsent('outer');
3815
    $this->assertRowAbsent('inner');
3816
    $this->assertRowAbsent('inner2');
3817
  }
3818
}
3819

    
3820

    
3821
/**
3822
 * Check the sequences API.
3823
 */
3824
class DatabaseNextIdCase extends DrupalWebTestCase {
3825
  public static function getInfo() {
3826
    return array(
3827
      'name' => 'Sequences API',
3828
      'description' => 'Test the secondary sequences API.',
3829
      'group' => 'Database',
3830
    );
3831
  }
3832

    
3833
  /**
3834
   * Test that the sequences API work.
3835
   */
3836
  function testDbNextId() {
3837
    $first = db_next_id();
3838
    $second = db_next_id();
3839
    // We can test for exact increase in here because we know there is no
3840
    // other process operating on these tables -- normally we could only
3841
    // expect $second > $first.
3842
    $this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.');
3843
    $result = db_next_id(1000);
3844
    $this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.');
3845
  }
3846
}
3847

    
3848
/**
3849
 * Tests the empty pseudo-statement class.
3850
 */
3851
class DatabaseEmptyStatementTestCase extends DrupalWebTestCase {
3852
  public static function getInfo() {
3853
    return array(
3854
      'name' => 'Empty statement',
3855
      'description' => 'Test the empty pseudo-statement class.',
3856
      'group' => 'Database',
3857
    );
3858
  }
3859

    
3860
  /**
3861
   * Test that the empty result set behaves as empty.
3862
   */
3863
  function testEmpty() {
3864
    $result = new DatabaseStatementEmpty();
3865

    
3866
    $this->assertTrue($result instanceof DatabaseStatementInterface, 'Class implements expected interface');
3867
    $this->assertNull($result->fetchObject(), 'Null result returned.');
3868
  }
3869

    
3870
  /**
3871
   * Test that the empty result set iterates safely.
3872
   */
3873
  function testEmptyIteration() {
3874
    $result = new DatabaseStatementEmpty();
3875

    
3876
    foreach ($result as $record) {
3877
      $this->fail('Iterating empty result set should not iterate.');
3878
      return;
3879
    }
3880

    
3881
    $this->pass('Iterating empty result set skipped iteration.');
3882
  }
3883

    
3884
  /**
3885
   * Test that the empty result set mass-fetches in an expected way.
3886
   */
3887
  function testEmptyFetchAll() {
3888
    $result = new DatabaseStatementEmpty();
3889

    
3890
    $this->assertEqual($result->fetchAll(), array(), 'Empty array returned from empty result set.');
3891
  }
3892
}
3893

    
3894
/**
3895
 * Tests management of database connections.
3896
 */
3897
class ConnectionUnitTest extends DrupalUnitTestCase {
3898

    
3899
  protected $key;
3900
  protected $target;
3901

    
3902
  protected $monitor;
3903
  protected $originalCount;
3904

    
3905
  public static function getInfo() {
3906
    return array(
3907
      'name' => 'Connection unit tests',
3908
      'description' => 'Tests management of database connections.',
3909
      'group' => 'Database',
3910
    );
3911
  }
3912

    
3913
  function setUp() {
3914
    parent::setUp();
3915

    
3916
    $this->key = 'default';
3917
    $this->originalTarget = 'default';
3918
    $this->target = 'DatabaseConnectionUnitTest';
3919

    
3920
    // Determine whether the database driver is MySQL. If it is not, the test
3921
    // methods will not be executed.
3922
    // @todo Make this test driver-agnostic, or find a proper way to skip it.
3923
    // @see http://drupal.org/node/1273478
3924
    $connection_info = Database::getConnectionInfo('default');
3925
    $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql';
3926
    if ($this->skipTest) {
3927
      // Insert an assertion to prevent Simpletest from interpreting the test
3928
      // as failure.
3929
      $this->pass('This test is only compatible with MySQL.');
3930
    }
3931

    
3932
    // Create an additional connection to monitor the connections being opened
3933
    // and closed in this test.
3934
    // @see TestBase::changeDatabasePrefix()
3935
    $connection_info = Database::getConnectionInfo('default');
3936
    Database::addConnectionInfo('default', 'monitor', $connection_info['default']);
3937
    global $databases;
3938
    $databases['default']['monitor'] = $connection_info['default'];
3939
    $this->monitor = Database::getConnection('monitor');
3940
  }
3941

    
3942
  /**
3943
   * Adds a new database connection info to Database.
3944
   */
3945
  protected function addConnection() {
3946
    // Add a new target to the connection, by cloning the current connection.
3947
    $connection_info = Database::getConnectionInfo($this->key);
3948
    Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]);
3949

    
3950
    // Verify that the new target exists.
3951
    $info = Database::getConnectionInfo($this->key);
3952
    // Note: Custom assertion message to not expose database credentials.
3953
    $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.');
3954
  }
3955

    
3956
  /**
3957
   * Returns the connection ID of the current test connection.
3958
   *
3959
   * @return integer
3960
   */
3961
  protected function getConnectionID() {
3962
    return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField();
3963
  }
3964

    
3965
  /**
3966
   * Asserts that a connection ID exists.
3967
   *
3968
   * @param integer $id
3969
   *   The connection ID to verify.
3970
   */
3971
  protected function assertConnection($id) {
3972
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
3973
    return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id)));
3974
  }
3975

    
3976
  /**
3977
   * Asserts that a connection ID does not exist.
3978
   *
3979
   * @param integer $id
3980
   *   The connection ID to verify.
3981
   */
3982
  protected function assertNoConnection($id) {
3983
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
3984
    return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id)));
3985
  }
3986

    
3987
  /**
3988
   * Tests Database::closeConnection() without query.
3989
   *
3990
   * @todo getConnectionID() executes a query.
3991
   */
3992
  function testOpenClose() {
3993
    if ($this->skipTest) {
3994
      return;
3995
    }
3996
    // Add and open a new connection.
3997
    $this->addConnection();
3998
    $id = $this->getConnectionID();
3999
    Database::getConnection($this->target, $this->key);
4000

    
4001
    // Verify that there is a new connection.
4002
    $this->assertConnection($id);
4003

    
4004
    // Close the connection.
4005
    Database::closeConnection($this->target, $this->key);
4006
    // Wait 20ms to give the database engine sufficient time to react.
4007
    usleep(20000);
4008

    
4009
    // Verify that we are back to the original connection count.
4010
    $this->assertNoConnection($id);
4011
  }
4012

    
4013
  /**
4014
   * Tests Database::closeConnection() with a query.
4015
   */
4016
  function testOpenQueryClose() {
4017
    if ($this->skipTest) {
4018
      return;
4019
    }
4020
    // Add and open a new connection.
4021
    $this->addConnection();
4022
    $id = $this->getConnectionID();
4023
    Database::getConnection($this->target, $this->key);
4024

    
4025
    // Verify that there is a new connection.
4026
    $this->assertConnection($id);
4027

    
4028
    // Execute a query.
4029
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES');
4030

    
4031
    // Close the connection.
4032
    Database::closeConnection($this->target, $this->key);
4033
    // Wait 20ms to give the database engine sufficient time to react.
4034
    usleep(20000);
4035

    
4036
    // Verify that we are back to the original connection count.
4037
    $this->assertNoConnection($id);
4038
  }
4039

    
4040
  /**
4041
   * Tests Database::closeConnection() with a query and custom prefetch method.
4042
   */
4043
  function testOpenQueryPrefetchClose() {
4044
    if ($this->skipTest) {
4045
      return;
4046
    }
4047
    // Add and open a new connection.
4048
    $this->addConnection();
4049
    $id = $this->getConnectionID();
4050
    Database::getConnection($this->target, $this->key);
4051

    
4052
    // Verify that there is a new connection.
4053
    $this->assertConnection($id);
4054

    
4055
    // Execute a query.
4056
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol();
4057

    
4058
    // Close the connection.
4059
    Database::closeConnection($this->target, $this->key);
4060
    // Wait 20ms to give the database engine sufficient time to react.
4061
    usleep(20000);
4062

    
4063
    // Verify that we are back to the original connection count.
4064
    $this->assertNoConnection($id);
4065
  }
4066

    
4067
  /**
4068
   * Tests Database::closeConnection() with a select query.
4069
   */
4070
  function testOpenSelectQueryClose() {
4071
    if ($this->skipTest) {
4072
      return;
4073
    }
4074
    // Add and open a new connection.
4075
    $this->addConnection();
4076
    $id = $this->getConnectionID();
4077
    Database::getConnection($this->target, $this->key);
4078

    
4079
    // Verify that there is a new connection.
4080
    $this->assertConnection($id);
4081

    
4082
    // Create a table.
4083
    $name = 'foo';
4084
    Database::getConnection($this->target, $this->key)->schema()->createTable($name, array(
4085
      'fields' => array(
4086
        'name' => array(
4087
          'type' => 'varchar',
4088
          'length' => 255,
4089
        ),
4090
      ),
4091
    ));
4092

    
4093
    // Execute a query.
4094
    Database::getConnection($this->target, $this->key)->select('foo', 'f')
4095
      ->fields('f', array('name'))
4096
      ->execute()
4097
      ->fetchAll();
4098

    
4099
    // Drop the table.
4100
    Database::getConnection($this->target, $this->key)->schema()->dropTable($name);
4101

    
4102
    // Close the connection.
4103
    Database::closeConnection($this->target, $this->key);
4104
    // Wait 20ms to give the database engine sufficient time to react.
4105
    usleep(20000);
4106

    
4107
    // Verify that we are back to the original connection count.
4108
    $this->assertNoConnection($id);
4109
  }
4110

    
4111
}