Projet

Général

Profil

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

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

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
   * Confirm that an extended query has a "tag" added to it.
2626
   */
2627
  function testExtenderHasTag() {
2628
    $query = db_select('test')
2629
      ->extend('SelectQueryExtender');
2630
    $query->addField('test', 'name');
2631
    $query->addField('test', 'age', 'age');
2632

    
2633
    $query->addTag('test');
2634

    
2635
    $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
2636
    $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
2637
  }
2638

    
2639
  /**
2640
   * Test extended query tagging "has all of these tags" functionality.
2641
   */
2642
  function testExtenderHasAllTags() {
2643
    $query = db_select('test')
2644
      ->extend('SelectQueryExtender');
2645
    $query->addField('test', 'name');
2646
    $query->addField('test', 'age', 'age');
2647

    
2648
    $query->addTag('test');
2649
    $query->addTag('other');
2650

    
2651
    $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
2652
    $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
2653
  }
2654

    
2655
  /**
2656
   * Test extended query tagging "has at least one of these tags" functionality.
2657
   */
2658
  function testExtenderHasAnyTag() {
2659
    $query = db_select('test')
2660
      ->extend('SelectQueryExtender');
2661
    $query->addField('test', 'name');
2662
    $query->addField('test', 'age', 'age');
2663

    
2664
    $query->addTag('test');
2665

    
2666
    $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
2667
    $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
2668
  }
2669

    
2670
  /**
2671
   * Test that we can attach meta data to a query object.
2672
   *
2673
   * This is how we pass additional context to alter hooks.
2674
   */
2675
  function testMetaData() {
2676
    $query = db_select('test');
2677
    $query->addField('test', 'name');
2678
    $query->addField('test', 'age', 'age');
2679

    
2680
    $data = array(
2681
      'a' => 'A',
2682
      'b' => 'B',
2683
    );
2684

    
2685
    $query->addMetaData('test', $data);
2686

    
2687
    $return = $query->getMetaData('test');
2688
    $this->assertEqual($data, $return, 'Corect metadata returned.');
2689

    
2690
    $return = $query->getMetaData('nothere');
2691
    $this->assertNull($return, 'Non-existent key returned NULL.');
2692
  }
2693
}
2694

    
2695
/**
2696
 * Select alter tests.
2697
 *
2698
 * @see database_test_query_alter()
2699
 */
2700
class DatabaseAlterTestCase extends DatabaseTestCase {
2701

    
2702
  public static function getInfo() {
2703
    return array(
2704
      'name' => 'Query altering tests',
2705
      'description' => 'Test the hook_query_alter capabilities of the Select builder.',
2706
      'group' => 'Database',
2707
    );
2708
  }
2709

    
2710
  /**
2711
   * Test that we can do basic alters.
2712
   */
2713
  function testSimpleAlter() {
2714
    $query = db_select('test');
2715
    $query->addField('test', 'name');
2716
    $query->addField('test', 'age', 'age');
2717
    $query->addTag('database_test_alter_add_range');
2718

    
2719
    $result = $query->execute();
2720

    
2721
    $num_records = 0;
2722
    foreach ($result as $record) {
2723
      $num_records++;
2724
    }
2725

    
2726
    $this->assertEqual($num_records, 2, 'Returned the correct number of rows.');
2727
  }
2728

    
2729
  /**
2730
   * Test that we can alter the joins on a query.
2731
   */
2732
  function testAlterWithJoin() {
2733
    $query = db_select('test_task');
2734
    $tid_field = $query->addField('test_task', 'tid');
2735
    $task_field = $query->addField('test_task', 'task');
2736
    $query->orderBy($task_field);
2737
    $query->addTag('database_test_alter_add_join');
2738

    
2739
    $result = $query->execute();
2740

    
2741
    $records = $result->fetchAll();
2742

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

    
2745
    $this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.');
2746
    $this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.');
2747
    $this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.');
2748
    $this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.');
2749
    $this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.');
2750
    $this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.');
2751
  }
2752

    
2753
  /**
2754
   * Test that we can alter a query's conditionals.
2755
   */
2756
  function testAlterChangeConditional() {
2757
    $query = db_select('test_task');
2758
    $tid_field = $query->addField('test_task', 'tid');
2759
    $pid_field = $query->addField('test_task', 'pid');
2760
    $task_field = $query->addField('test_task', 'task');
2761
    $people_alias = $query->join('test', 'people', "test_task.pid = people.id");
2762
    $name_field = $query->addField($people_alias, 'name', 'name');
2763
    $query->condition('test_task.tid', '1');
2764
    $query->orderBy($tid_field);
2765
    $query->addTag('database_test_alter_change_conditional');
2766

    
2767
    $result = $query->execute();
2768

    
2769
    $records = $result->fetchAll();
2770

    
2771
    $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
2772
    $this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.');
2773
    $this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.');
2774
    $this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.');
2775
    $this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.');
2776
  }
2777

    
2778
  /**
2779
   * Test that we can alter the fields of a query.
2780
   */
2781
  function testAlterChangeFields() {
2782
    $query = db_select('test');
2783
    $name_field = $query->addField('test', 'name');
2784
    $age_field = $query->addField('test', 'age', 'age');
2785
    $query->orderBy('name');
2786
    $query->addTag('database_test_alter_change_fields');
2787

    
2788
    $record = $query->execute()->fetch();
2789
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2790
    $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.');
2791
  }
2792

    
2793
  /**
2794
   * Test that we can alter expressions in the query.
2795
   */
2796
  function testAlterExpression() {
2797
    $query = db_select('test');
2798
    $name_field = $query->addField('test', 'name');
2799
    $age_field = $query->addExpression("age*2", 'double_age');
2800
    $query->condition('age', 27);
2801
    $query->addTag('database_test_alter_change_expressions');
2802
    $result = $query->execute();
2803

    
2804
    // Ensure that we got the right record.
2805
    $record = $result->fetch();
2806

    
2807
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2808
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2809
  }
2810

    
2811
  /**
2812
   * Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter().
2813
   */
2814
  function testAlterRemoveRange() {
2815
    $query = db_select('test');
2816
    $query->addField('test', 'name');
2817
    $query->addField('test', 'age', 'age');
2818
    $query->range(0, 2);
2819
    $query->addTag('database_test_alter_remove_range');
2820

    
2821
    $num_records = count($query->execute()->fetchAll());
2822

    
2823
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
2824
  }
2825

    
2826
  /**
2827
   * Test that we can do basic alters on subqueries.
2828
   */
2829
  function testSimpleAlterSubquery() {
2830
    // Create a sub-query with an alter tag.
2831
    $subquery = db_select('test', 'p');
2832
    $subquery->addField('p', 'name');
2833
    $subquery->addField('p', 'id');
2834
    // Pick out George.
2835
    $subquery->condition('age', 27);
2836
    $subquery->addExpression("age*2", 'double_age');
2837
    // This query alter should change it to age * 3.
2838
    $subquery->addTag('database_test_alter_change_expressions');
2839

    
2840
    // Create a main query and join to sub-query.
2841
    $query = db_select('test_task', 'tt');
2842
    $query->join($subquery, 'pq', 'pq.id = tt.pid');
2843
    $age_field = $query->addField('pq', 'double_age');
2844
    $name_field = $query->addField('pq', 'name');
2845

    
2846
    $record = $query->execute()->fetch();
2847
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2848
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2849
  }
2850
}
2851

    
2852
/**
2853
 * Regression tests.
2854
 */
2855
class DatabaseRegressionTestCase extends DatabaseTestCase {
2856

    
2857
  public static function getInfo() {
2858
    return array(
2859
      'name' => 'Regression tests',
2860
      'description' => 'Regression tests cases for the database layer.',
2861
      'group' => 'Database',
2862
    );
2863
  }
2864

    
2865
  /**
2866
   * Regression test for #310447.
2867
   *
2868
   * Tries to insert non-ascii UTF-8 data in a database column and checks
2869
   * if its stored properly.
2870
   */
2871
  function testRegression_310447() {
2872
    // That's a 255 character UTF-8 string.
2873
    $name = str_repeat("é", 255);
2874
    db_insert('test')
2875
      ->fields(array(
2876
        'name' => $name,
2877
        'age' => 20,
2878
        'job' => 'Dancer',
2879
      ))->execute();
2880

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

    
2885
  /**
2886
   * Test the db_table_exists() function.
2887
   */
2888
  function testDBTableExists() {
2889
    $this->assertIdentical(TRUE, db_table_exists('node'), 'Returns true for existent table.');
2890
    $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.');
2891
  }
2892

    
2893
  /**
2894
   * Test the db_field_exists() function.
2895
   */
2896
  function testDBFieldExists() {
2897
    $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), 'Returns true for existent column.');
2898
    $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), 'Returns false for nonexistent column.');
2899
  }
2900

    
2901
  /**
2902
   * Test the db_index_exists() function.
2903
   */
2904
  function testDBIndexExists() {
2905
    $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.');
2906
    $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.');
2907
  }
2908
}
2909

    
2910
/**
2911
 * Query logging tests.
2912
 */
2913
class DatabaseLoggingTestCase extends DatabaseTestCase {
2914

    
2915
  public static function getInfo() {
2916
    return array(
2917
      'name' => 'Query logging',
2918
      'description' => 'Test the query logging facility.',
2919
      'group' => 'Database',
2920
    );
2921
  }
2922

    
2923
  /**
2924
   * Test that we can log the existence of a query.
2925
   */
2926
  function testEnableLogging() {
2927
    $log = Database::startLog('testing');
2928

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

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

    
2935
    $queries = Database::getLog('testing', 'default');
2936

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

    
2939
    foreach ($queries as $query) {
2940
      $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.');
2941
    }
2942
  }
2943

    
2944
  /**
2945
   * Test that we can run two logs in parallel.
2946
   */
2947
  function testEnableMultiLogging() {
2948
    Database::startLog('testing1');
2949

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

    
2952
    Database::startLog('testing2');
2953

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

    
2956
    $queries1 = Database::getLog('testing1');
2957
    $queries2 = Database::getLog('testing2');
2958

    
2959
    $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.');
2960
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.');
2961
  }
2962

    
2963
  /**
2964
   * Test that we can log queries against multiple targets on the same connection.
2965
   */
2966
  function testEnableTargetLogging() {
2967
    // Clone the master credentials to a slave connection and to another fake
2968
    // connection.
2969
    $connection_info = Database::getConnectionInfo('default');
2970
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
2971

    
2972
    Database::startLog('testing1');
2973

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

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

    
2978
    $queries1 = Database::getLog('testing1');
2979

    
2980
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
2981
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
2982
    $this->assertEqual($queries1[1]['target'], 'slave', 'Second query used slave target.');
2983
  }
2984

    
2985
  /**
2986
   * Test that logs to separate targets collapse to the same connection properly.
2987
   *
2988
   * This test is identical to the one above, except that it doesn't create
2989
   * a fake target so the query should fall back to running on the default
2990
   * target.
2991
   */
2992
  function testEnableTargetLoggingNoTarget() {
2993
    Database::startLog('testing1');
2994

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

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

    
3004
    $queries1 = Database::getLog('testing1');
3005

    
3006
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
3007
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
3008
    $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.');
3009
  }
3010

    
3011
  /**
3012
   * Test that we can log queries separately on different connections.
3013
   */
3014
  function testEnableMultiConnectionLogging() {
3015
    // Clone the master credentials to a fake connection.
3016
    // That both connections point to the same physical database is irrelevant.
3017
    $connection_info = Database::getConnectionInfo('default');
3018
    Database::addConnectionInfo('test2', 'default', $connection_info['default']);
3019

    
3020
    Database::startLog('testing1');
3021
    Database::startLog('testing1', 'test2');
3022

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

    
3025
    $old_key = db_set_active('test2');
3026

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

    
3029
    db_set_active($old_key);
3030

    
3031
    $queries1 = Database::getLog('testing1');
3032
    $queries2 = Database::getLog('testing1', 'test2');
3033

    
3034
    $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.');
3035
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
3036
  }
3037
}
3038

    
3039
/**
3040
 * Query serialization tests.
3041
 */
3042
class DatabaseSerializeQueryTestCase extends DatabaseTestCase {
3043
  public static function getInfo() {
3044
    return array(
3045
      'name' => 'Serialize query',
3046
      'description' => 'Test serializing and unserializing a query.',
3047
      'group' => 'Database',
3048
    );
3049
  }
3050

    
3051
  /**
3052
   * Confirm that a query can be serialized and unserialized.
3053
   */
3054
  function testSerializeQuery() {
3055
    $query = db_select('test');
3056
    $query->addField('test', 'age');
3057
    $query->condition('name', 'Ringo');
3058
    // If this doesn't work, it will throw an exception, so no need for an
3059
    // assertion.
3060
    $query = unserialize(serialize($query));
3061
    $results = $query->execute()->fetchCol();
3062
    $this->assertEqual($results[0], 28, 'Query properly executed after unserialization.');
3063
  }
3064
}
3065

    
3066
/**
3067
 * Range query tests.
3068
 */
3069
class DatabaseRangeQueryTestCase extends DrupalWebTestCase {
3070
  public static function getInfo() {
3071
    return array(
3072
      'name' => 'Range query test',
3073
      'description' => 'Test the Range query functionality.',
3074
      'group' => 'Database',
3075
    );
3076
  }
3077

    
3078
  function setUp() {
3079
    parent::setUp('database_test');
3080
  }
3081

    
3082
  /**
3083
   * Confirm that range query work and return correct result.
3084
   */
3085
  function testRangeQuery() {
3086
    // Test if return correct number of rows.
3087
    $range_rows = db_query_range("SELECT name FROM {system} ORDER BY name", 2, 3)->fetchAll();
3088
    $this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.');
3089

    
3090
    // Test if return target data.
3091
    $raw_rows = db_query('SELECT name FROM {system} ORDER BY name')->fetchAll();
3092
    $raw_rows = array_slice($raw_rows, 2, 3);
3093
    $this->assertEqual($range_rows, $raw_rows, 'Range query work and return target data.');
3094
  }
3095
}
3096

    
3097
/**
3098
 * Temporary query tests.
3099
 */
3100
class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase {
3101
  public static function getInfo() {
3102
    return array(
3103
      'name' => 'Temporary query test',
3104
      'description' => 'Test the temporary query functionality.',
3105
      'group' => 'Database',
3106
    );
3107
  }
3108

    
3109
  function setUp() {
3110
    parent::setUp('database_test');
3111
  }
3112

    
3113
  /**
3114
   * Return the number of rows of a table.
3115
   */
3116
  function countTableRows($table_name) {
3117
    return db_select($table_name)->countQuery()->execute()->fetchField();
3118
  }
3119

    
3120
  /**
3121
   * Confirm that temporary tables work and are limited to one request.
3122
   */
3123
  function testTemporaryQuery() {
3124
    $this->drupalGet('database_test/db_query_temporary');
3125
    $data = json_decode($this->drupalGetContent());
3126
    if ($data) {
3127
      $this->assertEqual($this->countTableRows("system"), $data->row_count, 'The temporary table contains the correct amount of rows.');
3128
      $this->assertFalse(db_table_exists($data->table_name), 'The temporary table is, indeed, temporary.');
3129
    }
3130
    else {
3131
      $this->fail("The creation of the temporary table failed.");
3132
    }
3133

    
3134
    // Now try to run two db_query_temporary() in the same request.
3135
    $table_name_system = db_query_temporary('SELECT status FROM {system}', array());
3136
    $table_name_users = db_query_temporary('SELECT uid FROM {users}', array());
3137

    
3138
    $this->assertEqual($this->countTableRows($table_name_system), $this->countTableRows("system"), 'A temporary table was created successfully in this request.');
3139
    $this->assertEqual($this->countTableRows($table_name_users), $this->countTableRows("users"), 'A second temporary table was created successfully in this request.');
3140

    
3141
    // Check that leading whitespace and comments do not cause problems
3142
    // in the modified query.
3143
    $sql = "
3144
      -- Let's select some rows into a temporary table
3145
      SELECT name FROM {test}
3146
    ";
3147
    $table_name_test = db_query_temporary($sql, array());
3148
    $this->assertEqual($this->countTableRows($table_name_test), $this->countTableRows('test'), 'Leading white space and comments do not interfere with temporary table creation.');
3149
  }
3150
}
3151

    
3152
/**
3153
 * Test how the current database driver interprets the SQL syntax.
3154
 *
3155
 * In order to ensure consistent SQL handling throughout Drupal
3156
 * across multiple kinds of database systems, we test that the
3157
 * database system interprets SQL syntax in an expected fashion.
3158
 */
3159
class DatabaseBasicSyntaxTestCase extends DatabaseTestCase {
3160
  public static function getInfo() {
3161
    return array(
3162
      'name' => 'Basic SQL syntax tests',
3163
      'description' => 'Test SQL syntax interpretation.',
3164
      'group' => 'Database',
3165
    );
3166
  }
3167

    
3168
  function setUp() {
3169
    parent::setUp('database_test');
3170
  }
3171

    
3172
  /**
3173
   * Test for string concatenation.
3174
   */
3175
  function testBasicConcat() {
3176
    $result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', array(
3177
      ':a1' => 'This',
3178
      ':a2' => ' ',
3179
      ':a3' => 'is',
3180
      ':a4' => ' a ',
3181
      ':a5' => 'test.',
3182
    ));
3183
    $this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.');
3184
  }
3185

    
3186
  /**
3187
   * Test for string concatenation with field values.
3188
   */
3189
  function testFieldConcat() {
3190
    $result = db_query('SELECT CONCAT(:a1, CONCAT(name, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', array(
3191
      ':a1' => 'The age of ',
3192
      ':a2' => ' is ',
3193
      ':a3' => '.',
3194
      ':age' => 25,
3195
    ));
3196
    $this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.');
3197
  }
3198

    
3199
  /**
3200
   * Test escaping of LIKE wildcards.
3201
   */
3202
  function testLikeEscape() {
3203
    db_insert('test')
3204
      ->fields(array(
3205
        'name' => 'Ring_',
3206
      ))
3207
      ->execute();
3208

    
3209
    // Match both "Ringo" and "Ring_".
3210
    $num_matches = db_select('test', 't')
3211
      ->condition('name', 'Ring_', 'LIKE')
3212
      ->countQuery()
3213
      ->execute()
3214
      ->fetchField();
3215
    $this->assertIdentical($num_matches, '2', 'Found 2 records.');
3216
    // Match only "Ring_" using a LIKE expression with no wildcards.
3217
    $num_matches = db_select('test', 't')
3218
      ->condition('name', db_like('Ring_'), 'LIKE')
3219
      ->countQuery()
3220
      ->execute()
3221
      ->fetchField();
3222
    $this->assertIdentical($num_matches, '1', 'Found 1 record.');
3223
  }
3224

    
3225
  /**
3226
   * Test LIKE query containing a backslash.
3227
   */
3228
  function testLikeBackslash() {
3229
    db_insert('test')
3230
      ->fields(array('name'))
3231
      ->values(array(
3232
        'name' => 'abcde\f',
3233
      ))
3234
      ->values(array(
3235
        'name' => 'abc%\_',
3236
      ))
3237
      ->execute();
3238

    
3239
    // Match both rows using a LIKE expression with two wildcards and a verbatim
3240
    // backslash.
3241
    $num_matches = db_select('test', 't')
3242
      ->condition('name', 'abc%\\\\_', 'LIKE')
3243
      ->countQuery()
3244
      ->execute()
3245
      ->fetchField();
3246
    $this->assertIdentical($num_matches, '2', 'Found 2 records.');
3247
    // Match only the former using a LIKE expression with no wildcards.
3248
    $num_matches = db_select('test', 't')
3249
      ->condition('name', db_like('abc%\_'), 'LIKE')
3250
      ->countQuery()
3251
      ->execute()
3252
      ->fetchField();
3253
    $this->assertIdentical($num_matches, '1', 'Found 1 record.');
3254
  }
3255
}
3256

    
3257
/**
3258
 * Test case sensitivity handling.
3259
 */
3260
class DatabaseCaseSensitivityTestCase extends DatabaseTestCase {
3261
  public static function getInfo() {
3262
    return array(
3263
      'name' => 'Case sensitivity',
3264
      'description' => 'Test handling case sensitive collation.',
3265
      'group' => 'Database',
3266
    );
3267
  }
3268

    
3269
  /**
3270
   * Test BINARY collation in MySQL.
3271
   */
3272
  function testCaseSensitiveInsert() {
3273
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3274

    
3275
    $john = db_insert('test')
3276
      ->fields(array(
3277
        'name' => 'john', // <- A record already exists with name 'John'.
3278
        'age' => 2,
3279
        'job' => 'Baby',
3280
      ))
3281
      ->execute();
3282

    
3283
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3284
    $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
3285
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
3286
    $this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.');
3287
  }
3288
}
3289

    
3290
/**
3291
 * Test invalid data handling.
3292
 */
3293
class DatabaseInvalidDataTestCase extends DatabaseTestCase {
3294
  public static function getInfo() {
3295
    return array(
3296
      'name' => 'Invalid data',
3297
      'description' => 'Test handling of some invalid data.',
3298
      'group' => 'Database',
3299
    );
3300
  }
3301

    
3302
  function setUp() {
3303
    parent::setUp('database_test');
3304
  }
3305

    
3306
  /**
3307
   * Traditional SQL database systems abort inserts when invalid data is encountered.
3308
   */
3309
  function testInsertDuplicateData() {
3310
    // Try to insert multiple records where at least one has bad data.
3311
    try {
3312
      db_insert('test')
3313
        ->fields(array('name', 'age', 'job'))
3314
        ->values(array(
3315
          'name' => 'Elvis',
3316
          'age' => 63,
3317
          'job' => 'Singer',
3318
        ))->values(array(
3319
          'name' => 'John', // <-- Duplicate value on unique field.
3320
          'age' => 17,
3321
          'job' => 'Consultant',
3322
        ))
3323
        ->values(array(
3324
          'name' => 'Frank',
3325
          'age' => 75,
3326
          'job' => 'Singer',
3327
        ))
3328
        ->execute();
3329
      $this->fail('Insert succeedded when it should not have.');
3330
    }
3331
    catch (Exception $e) {
3332
      // Check if the first record was inserted.
3333
      $name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField();
3334

    
3335
      if ($name == 'Elvis') {
3336
        if (!Database::getConnection()->supportsTransactions()) {
3337
          // This is an expected fail.
3338
          // Database engines that don't support transactions can leave partial
3339
          // inserts in place when an error occurs. This is the case for MySQL
3340
          // when running on a MyISAM table.
3341
          $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");
3342
        }
3343
        else {
3344
          $this->fail('The whole transaction is rolled back when a duplicate key insert occurs.');
3345
        }
3346
      }
3347
      else {
3348
        $this->pass('The whole transaction is rolled back when a duplicate key insert occurs.');
3349
      }
3350

    
3351
      // Ensure the other values were not inserted.
3352
      $record = db_select('test')
3353
        ->fields('test', array('name', 'age'))
3354
        ->condition('age', array(17, 75), 'IN')
3355
        ->execute()->fetchObject();
3356

    
3357
      $this->assertFalse($record, 'The rest of the insert aborted as expected.');
3358
    }
3359
  }
3360

    
3361
}
3362

    
3363
/**
3364
 * Drupal-specific SQL syntax tests.
3365
 */
3366
class DatabaseQueryTestCase extends DatabaseTestCase {
3367
  public static function getInfo() {
3368
    return array(
3369
      'name' => 'Custom query syntax tests',
3370
      'description' => 'Test Drupal\'s extended prepared statement syntax..',
3371
      'group' => 'Database',
3372
    );
3373
  }
3374

    
3375
  function setUp() {
3376
    parent::setUp('database_test');
3377
  }
3378

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

    
3385
    $this->assertEqual(count($names), 3, 'Correct number of names returned');
3386
  }
3387

    
3388
  /**
3389
   * Test SQL injection via database query array arguments.
3390
   */
3391
  public function testArrayArgumentsSQLInjection() {
3392
    // Attempt SQL injection and verify that it does not work.
3393
    $condition = array(
3394
      "1 ;INSERT INTO {test} SET name = 'test12345678'; -- " => '',
3395
      '1' => '',
3396
    );
3397
    try {
3398
      db_query("SELECT * FROM {test} WHERE name = :name", array(':name' => $condition))->fetchObject();
3399
      $this->fail('SQL injection attempt via array arguments should result in a PDOException.');
3400
    }
3401
    catch (PDOException $e) {
3402
      $this->pass('SQL injection attempt via array arguments should result in a PDOException.');
3403
    }
3404

    
3405
    // Test that the insert query that was used in the SQL injection attempt did
3406
    // not result in a row being inserted in the database.
3407
    $result = db_select('test')
3408
      ->condition('name', 'test12345678')
3409
      ->countQuery()
3410
      ->execute()
3411
      ->fetchField();
3412
    $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
3413
  }
3414

    
3415
}
3416

    
3417
/**
3418
 * Test transaction support, particularly nesting.
3419
 *
3420
 * We test nesting by having two transaction layers, an outer and inner. The
3421
 * outer layer encapsulates the inner layer. Our transaction nesting abstraction
3422
 * should allow the outer layer function to call any function it wants,
3423
 * especially the inner layer that starts its own transaction, and be
3424
 * confident that, when the function it calls returns, its own transaction
3425
 * is still "alive."
3426
 *
3427
 * Call structure:
3428
 *   transactionOuterLayer()
3429
 *     Start transaction
3430
 *     transactionInnerLayer()
3431
 *       Start transaction (does nothing in database)
3432
 *       [Maybe decide to roll back]
3433
 *     Do more stuff
3434
 *     Should still be in transaction A
3435
 *
3436
 */
3437
class DatabaseTransactionTestCase extends DatabaseTestCase {
3438

    
3439
  public static function getInfo() {
3440
    return array(
3441
      'name' => 'Transaction tests',
3442
      'description' => 'Test the transaction abstraction system.',
3443
      'group' => 'Database',
3444
    );
3445
  }
3446

    
3447
  /**
3448
   * Helper method for transaction unit test. This "outer layer" transaction
3449
   * starts and then encapsulates the "inner layer" transaction. This nesting
3450
   * is used to evaluate whether the the database transaction API properly
3451
   * supports nesting. By "properly supports," we mean the outer transaction
3452
   * continues to exist regardless of what functions are called and whether
3453
   * those functions start their own transactions.
3454
   *
3455
   * In contrast, a typical database would commit the outer transaction, start
3456
   * a new transaction for the inner layer, commit the inner layer transaction,
3457
   * and then be confused when the outer layer transaction tries to commit its
3458
   * transaction (which was already committed when the inner transaction
3459
   * started).
3460
   *
3461
   * @param $suffix
3462
   *   Suffix to add to field values to differentiate tests.
3463
   * @param $rollback
3464
   *   Whether or not to try rolling back the transaction when we're done.
3465
   * @param $ddl_statement
3466
   *   Whether to execute a DDL statement during the inner transaction.
3467
   */
3468
  protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
3469
    $connection = Database::getConnection();
3470
    $depth = $connection->transactionDepth();
3471
    $txn = db_transaction();
3472

    
3473
    // Insert a single row into the testing table.
3474
    db_insert('test')
3475
      ->fields(array(
3476
        'name' => 'David' . $suffix,
3477
        'age' => '24',
3478
      ))
3479
      ->execute();
3480

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

    
3483
    // We're already in a transaction, but we call ->transactionInnerLayer
3484
    // to nest another transaction inside the current one.
3485
    $this->transactionInnerLayer($suffix, $rollback, $ddl_statement);
3486

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

    
3489
    if ($rollback) {
3490
      // Roll back the transaction, if requested.
3491
      // This rollback should propagate to the last savepoint.
3492
      $txn->rollback();
3493
      $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
3494
    }
3495
  }
3496

    
3497
  /**
3498
   * Helper method for transaction unit tests. This "inner layer" transaction
3499
   * is either used alone or nested inside of the "outer layer" transaction.
3500
   *
3501
   * @param $suffix
3502
   *   Suffix to add to field values to differentiate tests.
3503
   * @param $rollback
3504
   *   Whether or not to try rolling back the transaction when we're done.
3505
   * @param $ddl_statement
3506
   *   Whether to execute a DDL statement during the transaction.
3507
   */
3508
  protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
3509
    $connection = Database::getConnection();
3510

    
3511
    $depth = $connection->transactionDepth();
3512
    // Start a transaction. If we're being called from ->transactionOuterLayer,
3513
    // then we're already in a transaction. Normally, that would make starting
3514
    // a transaction here dangerous, but the database API handles this problem
3515
    // for us by tracking the nesting and avoiding the danger.
3516
    $txn = db_transaction();
3517

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

    
3521
    // Insert a single row into the testing table.
3522
    db_insert('test')
3523
      ->fields(array(
3524
        'name' => 'Daniel' . $suffix,
3525
        'age' => '19',
3526
      ))
3527
      ->execute();
3528

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

    
3531
    if ($ddl_statement) {
3532
      $table = array(
3533
        'fields' => array(
3534
          'id' => array(
3535
            'type' => 'serial',
3536
            'unsigned' => TRUE,
3537
            'not null' => TRUE,
3538
          ),
3539
        ),
3540
        'primary key' => array('id'),
3541
      );
3542
      db_create_table('database_test_1', $table);
3543

    
3544
      $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
3545
    }
3546

    
3547
    if ($rollback) {
3548
      // Roll back the transaction, if requested.
3549
      // This rollback should propagate to the last savepoint.
3550
      $txn->rollback();
3551
      $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
3552
    }
3553
  }
3554

    
3555
  /**
3556
   * Test transaction rollback on a database that supports transactions.
3557
   *
3558
   * If the active connection does not support transactions, this test does nothing.
3559
   */
3560
  function testTransactionRollBackSupported() {
3561
    // This test won't work right if transactions are not supported.
3562
    if (!Database::getConnection()->supportsTransactions()) {
3563
      return;
3564
    }
3565
    try {
3566
      // Create two nested transactions. Roll back from the inner one.
3567
      $this->transactionOuterLayer('B', TRUE);
3568

    
3569
      // Neither of the rows we inserted in the two transaction layers
3570
      // should be present in the tables post-rollback.
3571
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
3572
      $this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.');
3573
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
3574
      $this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.');
3575
    }
3576
    catch (Exception $e) {
3577
      $this->fail($e->getMessage());
3578
    }
3579
  }
3580

    
3581
  /**
3582
   * Test transaction rollback on a database that does not support transactions.
3583
   *
3584
   * If the active driver supports transactions, this test does nothing.
3585
   */
3586
  function testTransactionRollBackNotSupported() {
3587
    // This test won't work right if transactions are supported.
3588
    if (Database::getConnection()->supportsTransactions()) {
3589
      return;
3590
    }
3591
    try {
3592
      // Create two nested transactions. Attempt to roll back from the inner one.
3593
      $this->transactionOuterLayer('B', TRUE);
3594

    
3595
      // Because our current database claims to not support transactions,
3596
      // the inserted rows should be present despite the attempt to roll back.
3597
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
3598
      $this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.');
3599
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
3600
      $this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.');
3601
    }
3602
    catch (Exception $e) {
3603
      $this->fail($e->getMessage());
3604
    }
3605
  }
3606

    
3607
  /**
3608
   * Test committed transaction.
3609
   *
3610
   * The behavior of this test should be identical for connections that support
3611
   * transactions and those that do not.
3612
   */
3613
  function testCommittedTransaction() {
3614
    try {
3615
      // Create two nested transactions. The changes should be committed.
3616
      $this->transactionOuterLayer('A');
3617

    
3618
      // Because we committed, both of the inserted rows should be present.
3619
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField();
3620
      $this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.');
3621
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField();
3622
      $this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.');
3623
    }
3624
    catch (Exception $e) {
3625
      $this->fail($e->getMessage());
3626
    }
3627
  }
3628

    
3629
  /**
3630
   * Test the compatibility of transactions with DDL statements.
3631
   */
3632
  function testTransactionWithDdlStatement() {
3633
    // First, test that a commit works normally, even with DDL statements.
3634
    $transaction = db_transaction();
3635
    $this->insertRow('row');
3636
    $this->executeDDLStatement();
3637
    unset($transaction);
3638
    $this->assertRowPresent('row');
3639

    
3640
    // Even in different order.
3641
    $this->cleanUp();
3642
    $transaction = db_transaction();
3643
    $this->executeDDLStatement();
3644
    $this->insertRow('row');
3645
    unset($transaction);
3646
    $this->assertRowPresent('row');
3647

    
3648
    // Even with stacking.
3649
    $this->cleanUp();
3650
    $transaction = db_transaction();
3651
    $transaction2 = db_transaction();
3652
    $this->executeDDLStatement();
3653
    unset($transaction2);
3654
    $transaction3 = db_transaction();
3655
    $this->insertRow('row');
3656
    unset($transaction3);
3657
    unset($transaction);
3658
    $this->assertRowPresent('row');
3659

    
3660
    // A transaction after a DDL statement should still work the same.
3661
    $this->cleanUp();
3662
    $transaction = db_transaction();
3663
    $transaction2 = db_transaction();
3664
    $this->executeDDLStatement();
3665
    unset($transaction2);
3666
    $transaction3 = db_transaction();
3667
    $this->insertRow('row');
3668
    $transaction3->rollback();
3669
    unset($transaction3);
3670
    unset($transaction);
3671
    $this->assertRowAbsent('row');
3672

    
3673
    // The behavior of a rollback depends on the type of database server.
3674
    if (Database::getConnection()->supportsTransactionalDDL()) {
3675
      // For database servers that support transactional DDL, a rollback
3676
      // of a transaction including DDL statements should be possible.
3677
      $this->cleanUp();
3678
      $transaction = db_transaction();
3679
      $this->insertRow('row');
3680
      $this->executeDDLStatement();
3681
      $transaction->rollback();
3682
      unset($transaction);
3683
      $this->assertRowAbsent('row');
3684

    
3685
      // Including with stacking.
3686
      $this->cleanUp();
3687
      $transaction = db_transaction();
3688
      $transaction2 = db_transaction();
3689
      $this->executeDDLStatement();
3690
      unset($transaction2);
3691
      $transaction3 = db_transaction();
3692
      $this->insertRow('row');
3693
      unset($transaction3);
3694
      $transaction->rollback();
3695
      unset($transaction);
3696
      $this->assertRowAbsent('row');
3697
    }
3698
    else {
3699
      // For database servers that do not support transactional DDL,
3700
      // the DDL statement should commit the transaction stack.
3701
      $this->cleanUp();
3702
      $transaction = db_transaction();
3703
      $this->insertRow('row');
3704
      $this->executeDDLStatement();
3705
      // Rollback the outer transaction.
3706
      try {
3707
        $transaction->rollback();
3708
        unset($transaction);
3709
        // @TODO: an exception should be triggered here, but is not, because
3710
        // "ROLLBACK" fails silently in MySQL if there is no transaction active.
3711
        // $this->fail(t('Rolling back a transaction containing DDL should fail.'));
3712
      }
3713
      catch (DatabaseTransactionNoActiveException $e) {
3714
        $this->pass('Rolling back a transaction containing DDL should fail.');
3715
      }
3716
      $this->assertRowPresent('row');
3717
    }
3718
  }
3719

    
3720
  /**
3721
   * Insert a single row into the testing table.
3722
   */
3723
  protected function insertRow($name) {
3724
    db_insert('test')
3725
      ->fields(array(
3726
        'name' => $name,
3727
      ))
3728
      ->execute();
3729
  }
3730

    
3731
  /**
3732
   * Execute a DDL statement.
3733
   */
3734
  protected function executeDDLStatement() {
3735
    static $count = 0;
3736
    $table = array(
3737
      'fields' => array(
3738
        'id' => array(
3739
          'type' => 'serial',
3740
          'unsigned' => TRUE,
3741
          'not null' => TRUE,
3742
        ),
3743
      ),
3744
      'primary key' => array('id'),
3745
    );
3746
    db_create_table('database_test_' . ++$count, $table);
3747
  }
3748

    
3749
  /**
3750
   * Start over for a new test.
3751
   */
3752
  protected function cleanUp() {
3753
    db_truncate('test')
3754
      ->execute();
3755
  }
3756

    
3757
  /**
3758
   * Assert that a given row is present in the test table.
3759
   *
3760
   * @param $name
3761
   *   The name of the row.
3762
   * @param $message
3763
   *   The message to log for the assertion.
3764
   */
3765
  function assertRowPresent($name, $message = NULL) {
3766
    if (!isset($message)) {
3767
      $message = format_string('Row %name is present.', array('%name' => $name));
3768
    }
3769
    $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
3770
    return $this->assertTrue($present, $message);
3771
  }
3772

    
3773
  /**
3774
   * Assert that a given row is absent from the test table.
3775
   *
3776
   * @param $name
3777
   *   The name of the row.
3778
   * @param $message
3779
   *   The message to log for the assertion.
3780
   */
3781
  function assertRowAbsent($name, $message = NULL) {
3782
    if (!isset($message)) {
3783
      $message = format_string('Row %name is absent.', array('%name' => $name));
3784
    }
3785
    $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
3786
    return $this->assertFalse($present, $message);
3787
  }
3788

    
3789
  /**
3790
   * Test transaction stacking and commit / rollback.
3791
   */
3792
  function testTransactionStacking() {
3793
    // This test won't work right if transactions are not supported.
3794
    if (!Database::getConnection()->supportsTransactions()) {
3795
      return;
3796
    }
3797

    
3798
    $database = Database::getConnection();
3799

    
3800
    // Standard case: pop the inner transaction before the outer transaction.
3801
    $transaction = db_transaction();
3802
    $this->insertRow('outer');
3803
    $transaction2 = db_transaction();
3804
    $this->insertRow('inner');
3805
    // Pop the inner transaction.
3806
    unset($transaction2);
3807
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction');
3808
    // Pop the outer transaction.
3809
    unset($transaction);
3810
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction');
3811
    $this->assertRowPresent('outer');
3812
    $this->assertRowPresent('inner');
3813

    
3814
    // Pop the transaction in a different order they have been pushed.
3815
    $this->cleanUp();
3816
    $transaction = db_transaction();
3817
    $this->insertRow('outer');
3818
    $transaction2 = db_transaction();
3819
    $this->insertRow('inner');
3820
    // Pop the outer transaction, nothing should happen.
3821
    unset($transaction);
3822
    $this->insertRow('inner-after-outer-commit');
3823
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3824
    // Pop the inner transaction, the whole transaction should commit.
3825
    unset($transaction2);
3826
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3827
    $this->assertRowPresent('outer');
3828
    $this->assertRowPresent('inner');
3829
    $this->assertRowPresent('inner-after-outer-commit');
3830

    
3831
    // Rollback the inner transaction.
3832
    $this->cleanUp();
3833
    $transaction = db_transaction();
3834
    $this->insertRow('outer');
3835
    $transaction2 = db_transaction();
3836
    $this->insertRow('inner');
3837
    // Now rollback the inner transaction.
3838
    $transaction2->rollback();
3839
    unset($transaction2);
3840
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3841
    // Pop the outer transaction, it should commit.
3842
    $this->insertRow('outer-after-inner-rollback');
3843
    unset($transaction);
3844
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3845
    $this->assertRowPresent('outer');
3846
    $this->assertRowAbsent('inner');
3847
    $this->assertRowPresent('outer-after-inner-rollback');
3848

    
3849
    // Rollback the inner transaction after committing the outer one.
3850
    $this->cleanUp();
3851
    $transaction = db_transaction();
3852
    $this->insertRow('outer');
3853
    $transaction2 = db_transaction();
3854
    $this->insertRow('inner');
3855
    // Pop the outer transaction, nothing should happen.
3856
    unset($transaction);
3857
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3858
    // Now rollback the inner transaction, it should rollback.
3859
    $transaction2->rollback();
3860
    unset($transaction2);
3861
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3862
    $this->assertRowPresent('outer');
3863
    $this->assertRowAbsent('inner');
3864

    
3865
    // Rollback the outer transaction while the inner transaction is active.
3866
    // In that case, an exception will be triggered because we cannot
3867
    // ensure that the final result will have any meaning.
3868
    $this->cleanUp();
3869
    $transaction = db_transaction();
3870
    $this->insertRow('outer');
3871
    $transaction2 = db_transaction();
3872
    $this->insertRow('inner');
3873
    $transaction3 = db_transaction();
3874
    $this->insertRow('inner2');
3875
    // Rollback the outer transaction.
3876
    try {
3877
      $transaction->rollback();
3878
      unset($transaction);
3879
      $this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
3880
    }
3881
    catch (DatabaseTransactionOutOfOrderException $e) {
3882
      $this->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
3883
    }
3884
    $this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction');
3885
    // Try to commit one inner transaction.
3886
    unset($transaction3);
3887
    $this->pass('Trying to commit an inner transaction resulted in an exception.');
3888
    // Try to rollback one inner transaction.
3889
    try {
3890
      $transaction->rollback();
3891
      unset($transaction2);
3892
      $this->fail('Trying to commit an inner transaction resulted in an exception.');
3893
    }
3894
    catch (DatabaseTransactionNoActiveException $e) {
3895
      $this->pass('Trying to commit an inner transaction resulted in an exception.');
3896
    }
3897
    $this->assertRowAbsent('outer');
3898
    $this->assertRowAbsent('inner');
3899
    $this->assertRowAbsent('inner2');
3900
  }
3901
}
3902

    
3903

    
3904
/**
3905
 * Check the sequences API.
3906
 */
3907
class DatabaseNextIdCase extends DrupalWebTestCase {
3908
  public static function getInfo() {
3909
    return array(
3910
      'name' => 'Sequences API',
3911
      'description' => 'Test the secondary sequences API.',
3912
      'group' => 'Database',
3913
    );
3914
  }
3915

    
3916
  /**
3917
   * Test that the sequences API work.
3918
   */
3919
  function testDbNextId() {
3920
    $first = db_next_id();
3921
    $second = db_next_id();
3922
    // We can test for exact increase in here because we know there is no
3923
    // other process operating on these tables -- normally we could only
3924
    // expect $second > $first.
3925
    $this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.');
3926
    $result = db_next_id(1000);
3927
    $this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.');
3928
  }
3929
}
3930

    
3931
/**
3932
 * Tests the empty pseudo-statement class.
3933
 */
3934
class DatabaseEmptyStatementTestCase extends DrupalWebTestCase {
3935
  public static function getInfo() {
3936
    return array(
3937
      'name' => 'Empty statement',
3938
      'description' => 'Test the empty pseudo-statement class.',
3939
      'group' => 'Database',
3940
    );
3941
  }
3942

    
3943
  /**
3944
   * Test that the empty result set behaves as empty.
3945
   */
3946
  function testEmpty() {
3947
    $result = new DatabaseStatementEmpty();
3948

    
3949
    $this->assertTrue($result instanceof DatabaseStatementInterface, 'Class implements expected interface');
3950
    $this->assertNull($result->fetchObject(), 'Null result returned.');
3951
  }
3952

    
3953
  /**
3954
   * Test that the empty result set iterates safely.
3955
   */
3956
  function testEmptyIteration() {
3957
    $result = new DatabaseStatementEmpty();
3958

    
3959
    foreach ($result as $record) {
3960
      $this->fail('Iterating empty result set should not iterate.');
3961
      return;
3962
    }
3963

    
3964
    $this->pass('Iterating empty result set skipped iteration.');
3965
  }
3966

    
3967
  /**
3968
   * Test that the empty result set mass-fetches in an expected way.
3969
   */
3970
  function testEmptyFetchAll() {
3971
    $result = new DatabaseStatementEmpty();
3972

    
3973
    $this->assertEqual($result->fetchAll(), array(), 'Empty array returned from empty result set.');
3974
  }
3975
}
3976

    
3977
/**
3978
 * Tests management of database connections.
3979
 */
3980
class ConnectionUnitTest extends DrupalUnitTestCase {
3981

    
3982
  protected $key;
3983
  protected $target;
3984

    
3985
  protected $monitor;
3986
  protected $originalCount;
3987

    
3988
  public static function getInfo() {
3989
    return array(
3990
      'name' => 'Connection unit tests',
3991
      'description' => 'Tests management of database connections.',
3992
      'group' => 'Database',
3993
    );
3994
  }
3995

    
3996
  function setUp() {
3997
    parent::setUp();
3998

    
3999
    $this->key = 'default';
4000
    $this->originalTarget = 'default';
4001
    $this->target = 'DatabaseConnectionUnitTest';
4002

    
4003
    // Determine whether the database driver is MySQL. If it is not, the test
4004
    // methods will not be executed.
4005
    // @todo Make this test driver-agnostic, or find a proper way to skip it.
4006
    // @see http://drupal.org/node/1273478
4007
    $connection_info = Database::getConnectionInfo('default');
4008
    $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql';
4009
    if ($this->skipTest) {
4010
      // Insert an assertion to prevent Simpletest from interpreting the test
4011
      // as failure.
4012
      $this->pass('This test is only compatible with MySQL.');
4013
    }
4014

    
4015
    // Create an additional connection to monitor the connections being opened
4016
    // and closed in this test.
4017
    // @see TestBase::changeDatabasePrefix()
4018
    $connection_info = Database::getConnectionInfo('default');
4019
    Database::addConnectionInfo('default', 'monitor', $connection_info['default']);
4020
    global $databases;
4021
    $databases['default']['monitor'] = $connection_info['default'];
4022
    $this->monitor = Database::getConnection('monitor');
4023
  }
4024

    
4025
  /**
4026
   * Adds a new database connection info to Database.
4027
   */
4028
  protected function addConnection() {
4029
    // Add a new target to the connection, by cloning the current connection.
4030
    $connection_info = Database::getConnectionInfo($this->key);
4031
    Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]);
4032

    
4033
    // Verify that the new target exists.
4034
    $info = Database::getConnectionInfo($this->key);
4035
    // Note: Custom assertion message to not expose database credentials.
4036
    $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.');
4037
  }
4038

    
4039
  /**
4040
   * Returns the connection ID of the current test connection.
4041
   *
4042
   * @return integer
4043
   */
4044
  protected function getConnectionID() {
4045
    return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField();
4046
  }
4047

    
4048
  /**
4049
   * Asserts that a connection ID exists.
4050
   *
4051
   * @param integer $id
4052
   *   The connection ID to verify.
4053
   */
4054
  protected function assertConnection($id) {
4055
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
4056
    return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id)));
4057
  }
4058

    
4059
  /**
4060
   * Asserts that a connection ID does not exist.
4061
   *
4062
   * @param integer $id
4063
   *   The connection ID to verify.
4064
   */
4065
  protected function assertNoConnection($id) {
4066
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
4067
    return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id)));
4068
  }
4069

    
4070
  /**
4071
   * Tests Database::closeConnection() without query.
4072
   *
4073
   * @todo getConnectionID() executes a query.
4074
   */
4075
  function testOpenClose() {
4076
    if ($this->skipTest) {
4077
      return;
4078
    }
4079
    // Add and open a new connection.
4080
    $this->addConnection();
4081
    $id = $this->getConnectionID();
4082
    Database::getConnection($this->target, $this->key);
4083

    
4084
    // Verify that there is a new connection.
4085
    $this->assertConnection($id);
4086

    
4087
    // Close the connection.
4088
    Database::closeConnection($this->target, $this->key);
4089
    // Wait 20ms to give the database engine sufficient time to react.
4090
    usleep(20000);
4091

    
4092
    // Verify that we are back to the original connection count.
4093
    $this->assertNoConnection($id);
4094
  }
4095

    
4096
  /**
4097
   * Tests Database::closeConnection() with a query.
4098
   */
4099
  function testOpenQueryClose() {
4100
    if ($this->skipTest) {
4101
      return;
4102
    }
4103
    // Add and open a new connection.
4104
    $this->addConnection();
4105
    $id = $this->getConnectionID();
4106
    Database::getConnection($this->target, $this->key);
4107

    
4108
    // Verify that there is a new connection.
4109
    $this->assertConnection($id);
4110

    
4111
    // Execute a query.
4112
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES');
4113

    
4114
    // Close the connection.
4115
    Database::closeConnection($this->target, $this->key);
4116
    // Wait 20ms to give the database engine sufficient time to react.
4117
    usleep(20000);
4118

    
4119
    // Verify that we are back to the original connection count.
4120
    $this->assertNoConnection($id);
4121
  }
4122

    
4123
  /**
4124
   * Tests Database::closeConnection() with a query and custom prefetch method.
4125
   */
4126
  function testOpenQueryPrefetchClose() {
4127
    if ($this->skipTest) {
4128
      return;
4129
    }
4130
    // Add and open a new connection.
4131
    $this->addConnection();
4132
    $id = $this->getConnectionID();
4133
    Database::getConnection($this->target, $this->key);
4134

    
4135
    // Verify that there is a new connection.
4136
    $this->assertConnection($id);
4137

    
4138
    // Execute a query.
4139
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol();
4140

    
4141
    // Close the connection.
4142
    Database::closeConnection($this->target, $this->key);
4143
    // Wait 20ms to give the database engine sufficient time to react.
4144
    usleep(20000);
4145

    
4146
    // Verify that we are back to the original connection count.
4147
    $this->assertNoConnection($id);
4148
  }
4149

    
4150
  /**
4151
   * Tests Database::closeConnection() with a select query.
4152
   */
4153
  function testOpenSelectQueryClose() {
4154
    if ($this->skipTest) {
4155
      return;
4156
    }
4157
    // Add and open a new connection.
4158
    $this->addConnection();
4159
    $id = $this->getConnectionID();
4160
    Database::getConnection($this->target, $this->key);
4161

    
4162
    // Verify that there is a new connection.
4163
    $this->assertConnection($id);
4164

    
4165
    // Create a table.
4166
    $name = 'foo';
4167
    Database::getConnection($this->target, $this->key)->schema()->createTable($name, array(
4168
      'fields' => array(
4169
        'name' => array(
4170
          'type' => 'varchar',
4171
          'length' => 255,
4172
        ),
4173
      ),
4174
    ));
4175

    
4176
    // Execute a query.
4177
    Database::getConnection($this->target, $this->key)->select('foo', 'f')
4178
      ->fields('f', array('name'))
4179
      ->execute()
4180
      ->fetchAll();
4181

    
4182
    // Drop the table.
4183
    Database::getConnection($this->target, $this->key)->schema()->dropTable($name);
4184

    
4185
    // Close the connection.
4186
    Database::closeConnection($this->target, $this->key);
4187
    // Wait 20ms to give the database engine sufficient time to react.
4188
    usleep(20000);
4189

    
4190
    // Verify that we are back to the original connection count.
4191
    $this->assertNoConnection($id);
4192
  }
4193

    
4194
}