Projet

Général

Profil

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

root / drupal7 / modules / simpletest / tests / database_test.test @ 134c7813

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 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
    $connection = Database::getConnection();
1423
    foreach ($this->makeCommentsProvider() as $test_set) {
1424
      list($expected, $comments) = $test_set;
1425
      $this->assertEqual($expected, $connection->makeComment($comments));
1426
    }
1427
  }
1428

    
1429
  /**
1430
   * Provides expected and input values for testVulnerableComment().
1431
   */
1432
  function makeCommentsProvider() {
1433
    return array(
1434
      array(
1435
        '/*  */ ',
1436
        array(''),
1437
      ),
1438
      // Try and close the comment early.
1439
      array(
1440
        '/* Exploit  * / DROP TABLE node; -- */ ',
1441
        array('Exploit */ DROP TABLE node; --'),
1442
      ),
1443
      // Variations on comment closing.
1444
      array(
1445
        '/* Exploit  * / * / DROP TABLE node; -- */ ',
1446
        array('Exploit */*/ DROP TABLE node; --'),
1447
      ),
1448
      array(
1449
        '/* Exploit  *  * // DROP TABLE node; -- */ ',
1450
        array('Exploit **// DROP TABLE node; --'),
1451
      ),
1452
      // Try closing the comment in the second string which is appended.
1453
      array(
1454
        '/* Exploit  * / DROP TABLE node; --; Another try  * / DROP TABLE node; -- */ ',
1455
        array('Exploit */ DROP TABLE node; --', 'Another try */ DROP TABLE node; --'),
1456
      ),
1457
    );
1458
  }
1459

    
1460
  /**
1461
   * Test basic conditionals on SELECT statements.
1462
   */
1463
  function testSimpleSelectConditional() {
1464
    $query = db_select('test');
1465
    $name_field = $query->addField('test', 'name');
1466
    $age_field = $query->addField('test', 'age', 'age');
1467
    $query->condition('age', 27);
1468
    $result = $query->execute();
1469

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

    
1474
    // Ensure that we got the right record.
1475
    $record = $result->fetch();
1476
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1477
    $this->assertEqual($record->$age_field, 27, 'Fetched age is correct.');
1478
  }
1479

    
1480
  /**
1481
   * Test SELECT statements with expressions.
1482
   */
1483
  function testSimpleSelectExpression() {
1484
    $query = db_select('test');
1485
    $name_field = $query->addField('test', 'name');
1486
    $age_field = $query->addExpression("age*2", 'double_age');
1487
    $query->condition('age', 27);
1488
    $result = $query->execute();
1489

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

    
1494
    // Ensure that we got the right record.
1495
    $record = $result->fetch();
1496
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1497
    $this->assertEqual($record->$age_field, 27*2, 'Fetched age expression is correct.');
1498
  }
1499

    
1500
  /**
1501
   * Test SELECT statements with multiple expressions.
1502
   */
1503
  function testSimpleSelectExpressionMultiple() {
1504
    $query = db_select('test');
1505
    $name_field = $query->addField('test', 'name');
1506
    $age_double_field = $query->addExpression("age*2");
1507
    $age_triple_field = $query->addExpression("age*3");
1508
    $query->condition('age', 27);
1509
    $result = $query->execute();
1510

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

    
1515
    // Ensure that we got the right record.
1516
    $record = $result->fetch();
1517
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1518
    $this->assertEqual($record->$age_double_field, 27*2, 'Fetched double age expression is correct.');
1519
    $this->assertEqual($record->$age_triple_field, 27*3, 'Fetched triple age expression is correct.');
1520
  }
1521

    
1522
  /**
1523
   * Test adding multiple fields to a select statement at the same time.
1524
   */
1525
  function testSimpleSelectMultipleFields() {
1526
    $record = db_select('test')
1527
      ->fields('test', array('id', 'name', 'age', 'job'))
1528
      ->condition('age', 27)
1529
      ->execute()->fetchObject();
1530

    
1531
    // Check that all fields we asked for are present.
1532
    $this->assertNotNull($record->id, 'ID field is present.');
1533
    $this->assertNotNull($record->name, 'Name field is present.');
1534
    $this->assertNotNull($record->age, 'Age field is present.');
1535
    $this->assertNotNull($record->job, 'Job field is present.');
1536

    
1537
    // Ensure that we got the right record.
1538
    // Check that all fields we asked for are present.
1539
    $this->assertEqual($record->id, 2, 'ID field has the correct value.');
1540
    $this->assertEqual($record->name, 'George', 'Name field has the correct value.');
1541
    $this->assertEqual($record->age, 27, 'Age field has the correct value.');
1542
    $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
1543
  }
1544

    
1545
  /**
1546
   * Test adding all fields from a given table to a select statement.
1547
   */
1548
  function testSimpleSelectAllFields() {
1549
    $record = db_select('test')
1550
      ->fields('test')
1551
      ->condition('age', 27)
1552
      ->execute()->fetchObject();
1553

    
1554
    // Check that all fields we asked for are present.
1555
    $this->assertNotNull($record->id, 'ID field is present.');
1556
    $this->assertNotNull($record->name, 'Name field is present.');
1557
    $this->assertNotNull($record->age, 'Age field is present.');
1558
    $this->assertNotNull($record->job, 'Job field is present.');
1559

    
1560
    // Ensure that we got the right record.
1561
    // Check that all fields we asked for are present.
1562
    $this->assertEqual($record->id, 2, 'ID field has the correct value.');
1563
    $this->assertEqual($record->name, 'George', 'Name field has the correct value.');
1564
    $this->assertEqual($record->age, 27, 'Age field has the correct value.');
1565
    $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
1566
  }
1567

    
1568
  /**
1569
   * Test that we can find a record with a NULL value.
1570
   */
1571
  function testNullCondition() {
1572
    $this->ensureSampleDataNull();
1573

    
1574
    $names = db_select('test_null', 'tn')
1575
      ->fields('tn', array('name'))
1576
      ->isNull('age')
1577
      ->execute()->fetchCol();
1578

    
1579
    $this->assertEqual(count($names), 1, 'Correct number of records found with NULL age.');
1580
    $this->assertEqual($names[0], 'Fozzie', 'Correct record returned for NULL age.');
1581
  }
1582

    
1583
  /**
1584
   * Test that we can find a record without a NULL value.
1585
   */
1586
  function testNotNullCondition() {
1587
    $this->ensureSampleDataNull();
1588

    
1589
    $names = db_select('test_null', 'tn')
1590
      ->fields('tn', array('name'))
1591
      ->isNotNull('tn.age')
1592
      ->orderBy('name')
1593
      ->execute()->fetchCol();
1594

    
1595
    $this->assertEqual(count($names), 2, 'Correct number of records found withNOT NULL age.');
1596
    $this->assertEqual($names[0], 'Gonzo', 'Correct record returned for NOT NULL age.');
1597
    $this->assertEqual($names[1], 'Kermit', 'Correct record returned for NOT NULL age.');
1598
  }
1599

    
1600
  /**
1601
   * Test that we can UNION multiple Select queries together. This is
1602
   * semantically equal to UNION DISTINCT, so we don't explicity test that.
1603
   */
1604
  function testUnion() {
1605
    $query_1 = db_select('test', 't')
1606
      ->fields('t', array('name'))
1607
      ->condition('age', array(27, 28), 'IN');
1608

    
1609
    $query_2 = db_select('test', 't')
1610
      ->fields('t', array('name'))
1611
      ->condition('age', 28);
1612

    
1613
    $query_1->union($query_2);
1614

    
1615
    $names = $query_1->execute()->fetchCol();
1616

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

    
1620
    $this->assertEqual($names[0], 'George', 'First query returned correct name.');
1621
    $this->assertEqual($names[1], 'Ringo', 'Second query returned correct name.');
1622
  }
1623

    
1624
  /**
1625
   * Test that we can UNION ALL multiple Select queries together.
1626
   */
1627
  function testUnionAll() {
1628
    $query_1 = db_select('test', 't')
1629
      ->fields('t', array('name'))
1630
      ->condition('age', array(27, 28), 'IN');
1631

    
1632
    $query_2 = db_select('test', 't')
1633
      ->fields('t', array('name'))
1634
      ->condition('age', 28);
1635

    
1636
    $query_1->union($query_2, 'ALL');
1637

    
1638
    $names = $query_1->execute()->fetchCol();
1639

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

    
1643
    $this->assertEqual($names[0], 'George', 'First query returned correct first name.');
1644
    $this->assertEqual($names[1], 'Ringo', 'Second query returned correct second name.');
1645
    $this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.');
1646
  }
1647

    
1648
  /**
1649
   * Test that random ordering of queries works.
1650
   *
1651
   * We take the approach of testing the Drupal layer only, rather than trying
1652
   * to test that the database's random number generator actually produces
1653
   * random queries (which is very difficult to do without an unacceptable risk
1654
   * of the test failing by accident).
1655
   *
1656
   * Therefore, in this test we simply run the same query twice and assert that
1657
   * the two results are reordered versions of each other (as well as of the
1658
   * same query without the random ordering). It is reasonable to assume that
1659
   * if we run the same select query twice and the results are in a different
1660
   * order each time, the only way this could happen is if we have successfully
1661
   * triggered the database's random ordering functionality.
1662
   */
1663
  function testRandomOrder() {
1664
    // Use 52 items, so the chance that this test fails by accident will be the
1665
    // same as the chance that a deck of cards will come out in the same order
1666
    // after shuffling it (in other words, nearly impossible).
1667
    $number_of_items = 52;
1668
    while (db_query("SELECT MAX(id) FROM {test}")->fetchField() < $number_of_items) {
1669
      db_insert('test')->fields(array('name' => $this->randomName()))->execute();
1670
    }
1671

    
1672
    // First select the items in order and make sure we get an ordered list.
1673
    $expected_ids = range(1, $number_of_items);
1674
    $ordered_ids = db_select('test', 't')
1675
      ->fields('t', array('id'))
1676
      ->range(0, $number_of_items)
1677
      ->orderBy('id')
1678
      ->execute()
1679
      ->fetchCol();
1680
    $this->assertEqual($ordered_ids, $expected_ids, 'A query without random ordering returns IDs in the correct order.');
1681

    
1682
    // Now perform the same query, but instead choose a random ordering. We
1683
    // expect this to contain a differently ordered version of the original
1684
    // result.
1685
    $randomized_ids = db_select('test', 't')
1686
      ->fields('t', array('id'))
1687
      ->range(0, $number_of_items)
1688
      ->orderRandom()
1689
      ->execute()
1690
      ->fetchCol();
1691
    $this->assertNotEqual($randomized_ids, $ordered_ids, 'A query with random ordering returns an unordered set of IDs.');
1692
    $sorted_ids = $randomized_ids;
1693
    sort($sorted_ids);
1694
    $this->assertEqual($sorted_ids, $ordered_ids, 'After sorting the random list, the result matches the original query.');
1695

    
1696
    // Now perform the exact same query again, and make sure the order is
1697
    // different.
1698
    $randomized_ids_second_set = db_select('test', 't')
1699
      ->fields('t', array('id'))
1700
      ->range(0, $number_of_items)
1701
      ->orderRandom()
1702
      ->execute()
1703
      ->fetchCol();
1704
    $this->assertNotEqual($randomized_ids_second_set, $randomized_ids, 'Performing the query with random ordering a second time returns IDs in a different order.');
1705
    $sorted_ids_second_set = $randomized_ids_second_set;
1706
    sort($sorted_ids_second_set);
1707
    $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.');
1708
  }
1709

    
1710
  /**
1711
   * Test that aliases are renamed when duplicates.
1712
   */
1713
  function testSelectDuplicateAlias() {
1714
    $query = db_select('test', 't');
1715
    $alias1 = $query->addField('t', 'name', 'the_alias');
1716
    $alias2 = $query->addField('t', 'age', 'the_alias');
1717
    $this->assertNotIdentical($alias1, $alias2, 'Duplicate aliases are renamed.');
1718
  }
1719
}
1720

    
1721
/**
1722
 * Test case for subselects in a dynamic SELECT query.
1723
 */
1724
class DatabaseSelectSubqueryTestCase extends DatabaseTestCase {
1725

    
1726
  public static function getInfo() {
1727
    return array(
1728
      'name' => 'Select tests, subqueries',
1729
      'description' => 'Test the Select query builder.',
1730
      'group' => 'Database',
1731
    );
1732
  }
1733

    
1734
  /**
1735
   * Test that we can use a subquery in a FROM clause.
1736
   */
1737
  function testFromSubquerySelect() {
1738
    // Create a subquery, which is just a normal query object.
1739
    $subquery = db_select('test_task', 'tt');
1740
    $subquery->addField('tt', 'pid', 'pid');
1741
    $subquery->addField('tt', 'task', 'task');
1742
    $subquery->condition('priority', 1);
1743

    
1744
    for ($i = 0; $i < 2; $i++) {
1745
      // Create another query that joins against the virtual table resulting
1746
      // from the subquery.
1747
      $select = db_select($subquery, 'tt2');
1748
      $select->join('test', 't', 't.id=tt2.pid');
1749
      $select->addField('t', 'name');
1750
      if ($i) {
1751
        // Use a different number of conditions here to confuse the subquery
1752
        // placeholder counter, testing http://drupal.org/node/1112854.
1753
        $select->condition('name', 'John');
1754
      }
1755
      $select->condition('task', 'code');
1756

    
1757
      // The resulting query should be equivalent to:
1758
      // SELECT t.name
1759
      // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt
1760
      //   INNER JOIN test t ON t.id=tt.pid
1761
      // WHERE tt.task = 'code'
1762
      $people = $select->execute()->fetchCol();
1763

    
1764
      $this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
1765
    }
1766
  }
1767

    
1768
  /**
1769
   * Test that we can use a subquery in a FROM clause with a limit.
1770
   */
1771
  function testFromSubquerySelectWithLimit() {
1772
    // Create a subquery, which is just a normal query object.
1773
    $subquery = db_select('test_task', 'tt');
1774
    $subquery->addField('tt', 'pid', 'pid');
1775
    $subquery->addField('tt', 'task', 'task');
1776
    $subquery->orderBy('priority', 'DESC');
1777
    $subquery->range(0, 1);
1778

    
1779
    // Create another query that joins against the virtual table resulting
1780
    // from the subquery.
1781
    $select = db_select($subquery, 'tt2');
1782
    $select->join('test', 't', 't.id=tt2.pid');
1783
    $select->addField('t', 'name');
1784

    
1785
    // The resulting query should be equivalent to:
1786
    // SELECT t.name
1787
    // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt ORDER BY priority DESC LIMIT 1 OFFSET 0) tt
1788
    //   INNER JOIN test t ON t.id=tt.pid
1789
    $people = $select->execute()->fetchCol();
1790

    
1791
    $this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
1792
  }
1793

    
1794
  /**
1795
   * Test that we can use a subquery in a WHERE clause.
1796
   */
1797
  function testConditionSubquerySelect() {
1798
    // Create a subquery, which is just a normal query object.
1799
    $subquery = db_select('test_task', 'tt');
1800
    $subquery->addField('tt', 'pid', 'pid');
1801
    $subquery->condition('tt.priority', 1);
1802

    
1803
    // Create another query that joins against the virtual table resulting
1804
    // from the subquery.
1805
    $select = db_select('test_task', 'tt2');
1806
    $select->addField('tt2', 'task');
1807
    $select->condition('tt2.pid', $subquery, 'IN');
1808

    
1809
    // The resulting query should be equivalent to:
1810
    // SELECT tt2.name
1811
    // FROM test tt2
1812
    // WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1)
1813
    $people = $select->execute()->fetchCol();
1814
    $this->assertEqual(count($people), 5, 'Returned the correct number of rows.');
1815
  }
1816

    
1817
  /**
1818
   * Test that we can use a subquery in a JOIN clause.
1819
   */
1820
  function testJoinSubquerySelect() {
1821
    // Create a subquery, which is just a normal query object.
1822
    $subquery = db_select('test_task', 'tt');
1823
    $subquery->addField('tt', 'pid', 'pid');
1824
    $subquery->condition('priority', 1);
1825

    
1826
    // Create another query that joins against the virtual table resulting
1827
    // from the subquery.
1828
    $select = db_select('test', 't');
1829
    $select->join($subquery, 'tt', 't.id=tt.pid');
1830
    $select->addField('t', 'name');
1831

    
1832
    // The resulting query should be equivalent to:
1833
    // SELECT t.name
1834
    // FROM test t
1835
    //   INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid
1836
    $people = $select->execute()->fetchCol();
1837

    
1838
    $this->assertEqual(count($people), 2, 'Returned the correct number of rows.');
1839
  }
1840

    
1841
  /**
1842
   * Test EXISTS subquery conditionals on SELECT statements.
1843
   *
1844
   * We essentially select all rows from the {test} table that have matching
1845
   * rows in the {test_people} table based on the shared name column.
1846
   */
1847
  function testExistsSubquerySelect() {
1848
    // Put George into {test_people}.
1849
    db_insert('test_people')
1850
      ->fields(array(
1851
        'name' => 'George',
1852
        'age' => 27,
1853
        'job' => 'Singer',
1854
      ))
1855
      ->execute();
1856
    // Base query to {test}.
1857
    $query = db_select('test', 't')
1858
      ->fields('t', array('name'));
1859
    // Subquery to {test_people}.
1860
    $subquery = db_select('test_people', 'tp')
1861
      ->fields('tp', array('name'))
1862
      ->where('tp.name = t.name');
1863
    $query->exists($subquery);
1864
    $result = $query->execute();
1865

    
1866
    // Ensure that we got the right record.
1867
    $record = $result->fetch();
1868
    $this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.');
1869
  }
1870

    
1871
  /**
1872
   * Test NOT EXISTS subquery conditionals on SELECT statements.
1873
   *
1874
   * We essentially select all rows from the {test} table that don't have
1875
   * matching rows in the {test_people} table based on the shared name column.
1876
   */
1877
  function testNotExistsSubquerySelect() {
1878
    // Put George into {test_people}.
1879
    db_insert('test_people')
1880
      ->fields(array(
1881
        'name' => 'George',
1882
        'age' => 27,
1883
        'job' => 'Singer',
1884
      ))
1885
      ->execute();
1886

    
1887
    // Base query to {test}.
1888
    $query = db_select('test', 't')
1889
      ->fields('t', array('name'));
1890
    // Subquery to {test_people}.
1891
    $subquery = db_select('test_people', 'tp')
1892
      ->fields('tp', array('name'))
1893
      ->where('tp.name = t.name');
1894
    $query->notExists($subquery);
1895

    
1896
    // Ensure that we got the right number of records.
1897
    $people = $query->execute()->fetchCol();
1898
    $this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.');
1899
  }
1900
}
1901

    
1902
/**
1903
 * Test select with order by clauses.
1904
 */
1905
class DatabaseSelectOrderedTestCase extends DatabaseTestCase {
1906

    
1907
  public static function getInfo() {
1908
    return array(
1909
      'name' => 'Select tests, ordered',
1910
      'description' => 'Test the Select query builder.',
1911
      'group' => 'Database',
1912
    );
1913
  }
1914

    
1915
  /**
1916
   * Test basic order by.
1917
   */
1918
  function testSimpleSelectOrdered() {
1919
    $query = db_select('test');
1920
    $name_field = $query->addField('test', 'name');
1921
    $age_field = $query->addField('test', 'age', 'age');
1922
    $query->orderBy($age_field);
1923
    $result = $query->execute();
1924

    
1925
    $num_records = 0;
1926
    $last_age = 0;
1927
    foreach ($result as $record) {
1928
      $num_records++;
1929
      $this->assertTrue($record->age >= $last_age, 'Results returned in correct order.');
1930
      $last_age = $record->age;
1931
    }
1932

    
1933
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1934
  }
1935

    
1936
  /**
1937
   * Test multiple order by.
1938
   */
1939
  function testSimpleSelectMultiOrdered() {
1940
    $query = db_select('test');
1941
    $name_field = $query->addField('test', 'name');
1942
    $age_field = $query->addField('test', 'age', 'age');
1943
    $job_field = $query->addField('test', 'job');
1944
    $query->orderBy($job_field);
1945
    $query->orderBy($age_field);
1946
    $result = $query->execute();
1947

    
1948
    $num_records = 0;
1949
    $expected = array(
1950
      array('Ringo', 28, 'Drummer'),
1951
      array('John', 25, 'Singer'),
1952
      array('George', 27, 'Singer'),
1953
      array('Paul', 26, 'Songwriter'),
1954
    );
1955
    $results = $result->fetchAll(PDO::FETCH_NUM);
1956
    foreach ($expected as $k => $record) {
1957
      $num_records++;
1958
      foreach ($record as $kk => $col) {
1959
        if ($expected[$k][$kk] != $results[$k][$kk]) {
1960
          $this->assertTrue(FALSE, 'Results returned in correct order.');
1961
        }
1962
      }
1963
    }
1964
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1965
  }
1966

    
1967
  /**
1968
   * Test order by descending.
1969
   */
1970
  function testSimpleSelectOrderedDesc() {
1971
    $query = db_select('test');
1972
    $name_field = $query->addField('test', 'name');
1973
    $age_field = $query->addField('test', 'age', 'age');
1974
    $query->orderBy($age_field, 'DESC');
1975
    $result = $query->execute();
1976

    
1977
    $num_records = 0;
1978
    $last_age = 100000000;
1979
    foreach ($result as $record) {
1980
      $num_records++;
1981
      $this->assertTrue($record->age <= $last_age, 'Results returned in correct order.');
1982
      $last_age = $record->age;
1983
    }
1984

    
1985
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1986
  }
1987

    
1988
  /**
1989
   * Tests that the sort direction is sanitized properly.
1990
   */
1991
  function testOrderByEscaping() {
1992
    $query = db_select('test')->orderBy('name', 'invalid direction');
1993
    $order_bys = $query->getOrderBy();
1994
    $this->assertEqual($order_bys['name'], 'ASC', 'Invalid order by direction is converted to ASC.');
1995
  }
1996
}
1997

    
1998
/**
1999
 * Test more complex select statements.
2000
 */
2001
class DatabaseSelectComplexTestCase extends DatabaseTestCase {
2002

    
2003
  public static function getInfo() {
2004
    return array(
2005
      'name' => 'Select tests, complex',
2006
      'description' => 'Test the Select query builder with more complex queries.',
2007
      'group' => 'Database',
2008
    );
2009
  }
2010

    
2011
  /**
2012
   * Test simple JOIN statements.
2013
   */
2014
  function testDefaultJoin() {
2015
    $query = db_select('test_task', 't');
2016
    $people_alias = $query->join('test', 'p', 't.pid = p.id');
2017
    $name_field = $query->addField($people_alias, 'name', 'name');
2018
    $task_field = $query->addField('t', 'task', 'task');
2019
    $priority_field = $query->addField('t', 'priority', 'priority');
2020

    
2021
    $query->orderBy($priority_field);
2022
    $result = $query->execute();
2023

    
2024
    $num_records = 0;
2025
    $last_priority = 0;
2026
    foreach ($result as $record) {
2027
      $num_records++;
2028
      $this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.');
2029
      $this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.');
2030
      $last_priority = $record->$priority_field;
2031
    }
2032

    
2033
    $this->assertEqual($num_records, 7, 'Returned the correct number of rows.');
2034
  }
2035

    
2036
  /**
2037
   * Test LEFT OUTER joins.
2038
   */
2039
  function testLeftOuterJoin() {
2040
    $query = db_select('test', 'p');
2041
    $people_alias = $query->leftJoin('test_task', 't', 't.pid = p.id');
2042
    $name_field = $query->addField('p', 'name', 'name');
2043
    $task_field = $query->addField($people_alias, 'task', 'task');
2044
    $priority_field = $query->addField($people_alias, 'priority', 'priority');
2045

    
2046
    $query->orderBy($name_field);
2047
    $result = $query->execute();
2048

    
2049
    $num_records = 0;
2050
    $last_name = 0;
2051

    
2052
    foreach ($result as $record) {
2053
      $num_records++;
2054
      $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.');
2055
      $last_priority = $record->$name_field;
2056
    }
2057

    
2058
    $this->assertEqual($num_records, 8, 'Returned the correct number of rows.');
2059
  }
2060

    
2061
  /**
2062
   * Test GROUP BY clauses.
2063
   */
2064
  function testGroupBy() {
2065
    $query = db_select('test_task', 't');
2066
    $count_field = $query->addExpression('COUNT(task)', 'num');
2067
    $task_field = $query->addField('t', 'task');
2068
    $query->orderBy($count_field);
2069
    $query->groupBy($task_field);
2070
    $result = $query->execute();
2071

    
2072
    $num_records = 0;
2073
    $last_count = 0;
2074
    $records = array();
2075
    foreach ($result as $record) {
2076
      $num_records++;
2077
      $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
2078
      $last_count = $record->$count_field;
2079
      $records[$record->$task_field] = $record->$count_field;
2080
    }
2081

    
2082
    $correct_results = array(
2083
      'eat' => 1,
2084
      'sleep' => 2,
2085
      'code' => 1,
2086
      'found new band' => 1,
2087
      'perform at superbowl' => 1,
2088
    );
2089

    
2090
    foreach ($correct_results as $task => $count) {
2091
      $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
2092
    }
2093

    
2094
    $this->assertEqual($num_records, 6, 'Returned the correct number of total rows.');
2095
  }
2096

    
2097
  /**
2098
   * Test GROUP BY and HAVING clauses together.
2099
   */
2100
  function testGroupByAndHaving() {
2101
    $query = db_select('test_task', 't');
2102
    $count_field = $query->addExpression('COUNT(task)', 'num');
2103
    $task_field = $query->addField('t', 'task');
2104
    $query->orderBy($count_field);
2105
    $query->groupBy($task_field);
2106
    $query->having('COUNT(task) >= 2');
2107
    $result = $query->execute();
2108

    
2109
    $num_records = 0;
2110
    $last_count = 0;
2111
    $records = array();
2112
    foreach ($result as $record) {
2113
      $num_records++;
2114
      $this->assertTrue($record->$count_field >= 2, 'Record has the minimum count.');
2115
      $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
2116
      $last_count = $record->$count_field;
2117
      $records[$record->$task_field] = $record->$count_field;
2118
    }
2119

    
2120
    $correct_results = array(
2121
      'sleep' => 2,
2122
    );
2123

    
2124
    foreach ($correct_results as $task => $count) {
2125
      $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
2126
    }
2127

    
2128
    $this->assertEqual($num_records, 1, 'Returned the correct number of total rows.');
2129
  }
2130

    
2131
  /**
2132
   * Test range queries. The SQL clause varies with the database.
2133
   */
2134
  function testRange() {
2135
    $query = db_select('test');
2136
    $name_field = $query->addField('test', 'name');
2137
    $age_field = $query->addField('test', 'age', 'age');
2138
    $query->range(0, 2);
2139
    $result = $query->execute();
2140

    
2141
    $num_records = 0;
2142
    foreach ($result as $record) {
2143
      $num_records++;
2144
    }
2145

    
2146
    $this->assertEqual($num_records, 2, 'Returned the correct number of rows.');
2147
  }
2148

    
2149
  /**
2150
   * Test distinct queries.
2151
   */
2152
  function testDistinct() {
2153
    $query = db_select('test_task');
2154
    $task_field = $query->addField('test_task', 'task');
2155
    $query->distinct();
2156
    $result = $query->execute();
2157

    
2158
    $num_records = 0;
2159
    foreach ($result as $record) {
2160
      $num_records++;
2161
    }
2162

    
2163
    $this->assertEqual($num_records, 6, 'Returned the correct number of rows.');
2164
  }
2165

    
2166
  /**
2167
   * Test that we can generate a count query from a built query.
2168
   */
2169
  function testCountQuery() {
2170
    $query = db_select('test');
2171
    $name_field = $query->addField('test', 'name');
2172
    $age_field = $query->addField('test', 'age', 'age');
2173
    $query->orderBy('name');
2174

    
2175
    $count = $query->countQuery()->execute()->fetchField();
2176

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

    
2179
    // Now make sure we didn't break the original query!  We should still have
2180
    // all of the fields we asked for.
2181
    $record = $query->execute()->fetch();
2182
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2183
    $this->assertEqual($record->$age_field, 27, 'Correct data retrieved.');
2184
  }
2185

    
2186
  function testHavingCountQuery() {
2187
    $query = db_select('test')
2188
      ->extend('PagerDefault')
2189
      ->groupBy('age')
2190
      ->having('age + 1 > 0');
2191
    $query->addField('test', 'age');
2192
    $query->addExpression('age + 1');
2193
    $count = count($query->execute()->fetchCol());
2194
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2195
  }
2196

    
2197
  /**
2198
   * Test that countQuery properly removes 'all_fields' statements and
2199
   * ordering clauses.
2200
   */
2201
  function testCountQueryRemovals() {
2202
    $query = db_select('test');
2203
    $query->fields('test');
2204
    $query->orderBy('name');
2205
    $count = $query->countQuery();
2206

    
2207
    // Check that the 'all_fields' statement is handled properly.
2208
    $tables = $query->getTables();
2209
    $this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.');
2210
    $tables = $count->getTables();
2211
    $this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.');
2212

    
2213
    // Check that the ordering clause is handled properly.
2214
    $orderby = $query->getOrderBy();
2215
    $this->assertEqual($orderby['name'], 'ASC', 'Query correctly sets ordering clause.');
2216
    $orderby = $count->getOrderBy();
2217
    $this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering caluse.');
2218

    
2219
    // Make sure that the count query works.
2220
    $count = $count->execute()->fetchField();
2221

    
2222
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2223
  }
2224

    
2225

    
2226
  /**
2227
   * Test that countQuery properly removes fields and expressions.
2228
   */
2229
  function testCountQueryFieldRemovals() {
2230
    // countQuery should remove all fields and expressions, so this can be
2231
    // tested by adding a non-existent field and expression: if it ends
2232
    // up in the query, an error will be thrown. If not, it will return the
2233
    // number of records, which in this case happens to be 4 (there are four
2234
    // records in the {test} table).
2235
    $query = db_select('test');
2236
    $query->fields('test', array('fail'));
2237
    $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed fields');
2238

    
2239
    $query = db_select('test');
2240
    $query->addExpression('fail');
2241
    $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions');
2242
  }
2243

    
2244
  /**
2245
   * Test that we can generate a count query from a query with distinct.
2246
   */
2247
  function testCountQueryDistinct() {
2248
    $query = db_select('test_task');
2249
    $task_field = $query->addField('test_task', 'task');
2250
    $query->distinct();
2251

    
2252
    $count = $query->countQuery()->execute()->fetchField();
2253

    
2254
    $this->assertEqual($count, 6, 'Counted the correct number of records.');
2255
  }
2256

    
2257
  /**
2258
   * Test that we can generate a count query from a query with GROUP BY.
2259
   */
2260
  function testCountQueryGroupBy() {
2261
    $query = db_select('test_task');
2262
    $pid_field = $query->addField('test_task', 'pid');
2263
    $query->groupBy('pid');
2264

    
2265
    $count = $query->countQuery()->execute()->fetchField();
2266

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

    
2269
    // Use a column alias as, without one, the query can succeed for the wrong
2270
    // reason.
2271
    $query = db_select('test_task');
2272
    $pid_field = $query->addField('test_task', 'pid', 'pid_alias');
2273
    $query->addExpression('COUNT(test_task.task)', 'count');
2274
    $query->groupBy('pid_alias');
2275
    $query->orderBy('pid_alias', 'asc');
2276

    
2277
    $count = $query->countQuery()->execute()->fetchField();
2278

    
2279
    $this->assertEqual($count, 3, 'Counted the correct number of records.');
2280
  }
2281

    
2282
  /**
2283
   * Confirm that we can properly nest conditional clauses.
2284
   */
2285
  function testNestedConditions() {
2286
    // This query should translate to:
2287
    // "SELECT job FROM {test} WHERE name = 'Paul' AND (age = 26 OR age = 27)"
2288
    // That should find only one record. Yes it's a non-optimal way of writing
2289
    // that query but that's not the point!
2290
    $query = db_select('test');
2291
    $query->addField('test', 'job');
2292
    $query->condition('name', 'Paul');
2293
    $query->condition(db_or()->condition('age', 26)->condition('age', 27));
2294

    
2295
    $job = $query->execute()->fetchField();
2296
    $this->assertEqual($job, 'Songwriter', 'Correct data retrieved.');
2297
  }
2298

    
2299
  /**
2300
   * Confirm we can join on a single table twice with a dynamic alias.
2301
   */
2302
  function testJoinTwice() {
2303
    $query = db_select('test')->fields('test');
2304
    $alias = $query->join('test', 'test', 'test.job = %alias.job');
2305
    $query->addField($alias, 'name', 'othername');
2306
    $query->addField($alias, 'job', 'otherjob');
2307
    $query->where("$alias.name <> test.name");
2308
    $crowded_job = $query->execute()->fetch();
2309
    $this->assertEqual($crowded_job->job, $crowded_job->otherjob, 'Correctly joined same table twice.');
2310
    $this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.');
2311
  }
2312

    
2313
}
2314

    
2315
/**
2316
 * Test more complex select statements, part 2.
2317
 */
2318
class DatabaseSelectComplexTestCase2 extends DatabaseTestCase {
2319

    
2320
  public static function getInfo() {
2321
    return array(
2322
      'name' => 'Select tests, complex 2',
2323
      'description' => 'Test the Select query builder with even more complex queries.',
2324
      'group' => 'Database',
2325
    );
2326
  }
2327

    
2328
  function setUp() {
2329
    DrupalWebTestCase::setUp('database_test', 'node_access_test');
2330

    
2331
    $schema['test'] = drupal_get_schema('test');
2332
    $schema['test_people'] = drupal_get_schema('test_people');
2333
    $schema['test_one_blob'] = drupal_get_schema('test_one_blob');
2334
    $schema['test_two_blobs'] = drupal_get_schema('test_two_blobs');
2335
    $schema['test_task'] = drupal_get_schema('test_task');
2336

    
2337
    $this->installTables($schema);
2338

    
2339
    $this->addSampleData();
2340
  }
2341

    
2342
  /**
2343
   * Test that we can join on a query.
2344
   */
2345
  function testJoinSubquery() {
2346
    $acct = $this->drupalCreateUser(array('access content'));
2347
    $this->drupalLogin($acct);
2348

    
2349
    $query = db_select('test_task', 'tt', array('target' => 'slave'));
2350
    $query->addExpression('tt.pid + 1', 'abc');
2351
    $query->condition('priority', 1, '>');
2352
    $query->condition('priority', 100, '<');
2353

    
2354
    $subquery = db_select('test', 'tp');
2355
    $subquery->join('test_one_blob', 'tpb', 'tp.id = tpb.id');
2356
    $subquery->join('node', 'n', 'tp.id = n.nid');
2357
    $subquery->addTag('node_access');
2358
    $subquery->addMetaData('account', $acct);
2359
    $subquery->addField('tp', 'id');
2360
    $subquery->condition('age', 5, '>');
2361
    $subquery->condition('age', 500, '<');
2362

    
2363
    $query->leftJoin($subquery, 'sq', 'tt.pid = sq.id');
2364
    $query->join('test_one_blob', 'tb3', 'tt.pid = tb3.id');
2365

    
2366
    // Construct the query string.
2367
    // This is the same sequence that SelectQuery::execute() goes through.
2368
    $query->preExecute();
2369
    $query->getArguments();
2370
    $str = (string) $query;
2371

    
2372
    // Verify that the string only has one copy of condition placeholder 0.
2373
    $pos = strpos($str, 'db_condition_placeholder_0', 0);
2374
    $pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1);
2375
    $this->assertFalse($pos2, 'Condition placeholder is not repeated.');
2376
  }
2377
}
2378

    
2379
class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase {
2380

    
2381
  public static function getInfo() {
2382
    return array(
2383
      'name' => 'Pager query tests',
2384
      'description' => 'Test the pager query extender.',
2385
      'group' => 'Database',
2386
    );
2387
  }
2388

    
2389
  /**
2390
   * Confirm that a pager query returns the correct results.
2391
   *
2392
   * Note that we have to make an HTTP request to a test page handler
2393
   * because the pager depends on GET parameters.
2394
   */
2395
  function testEvenPagerQuery() {
2396
    // To keep the test from being too brittle, we determine up front
2397
    // what the page count should be dynamically, and pass the control
2398
    // information forward to the actual query on the other side of the
2399
    // HTTP request.
2400
    $limit = 2;
2401
    $count = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
2402

    
2403
    $correct_number = $limit;
2404
    $num_pages = floor($count / $limit);
2405

    
2406
    // If there is no remainder from rounding, subtract 1 since we index from 0.
2407
    if (!($num_pages * $limit < $count)) {
2408
      $num_pages--;
2409
    }
2410

    
2411
    for ($page = 0; $page <= $num_pages; ++$page) {
2412
      $this->drupalGet('database_test/pager_query_even/' . $limit, array('query' => array('page' => $page)));
2413
      $data = json_decode($this->drupalGetContent());
2414

    
2415
      if ($page == $num_pages) {
2416
        $correct_number = $count - ($limit * $page);
2417
      }
2418

    
2419
      $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
2420
    }
2421
  }
2422

    
2423
  /**
2424
   * Confirm that a pager query returns the correct results.
2425
   *
2426
   * Note that we have to make an HTTP request to a test page handler
2427
   * because the pager depends on GET parameters.
2428
   */
2429
  function testOddPagerQuery() {
2430
    // To keep the test from being too brittle, we determine up front
2431
    // what the page count should be dynamically, and pass the control
2432
    // information forward to the actual query on the other side of the
2433
    // HTTP request.
2434
    $limit = 2;
2435
    $count = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
2436

    
2437
    $correct_number = $limit;
2438
    $num_pages = floor($count / $limit);
2439

    
2440
    // If there is no remainder from rounding, subtract 1 since we index from 0.
2441
    if (!($num_pages * $limit < $count)) {
2442
      $num_pages--;
2443
    }
2444

    
2445
    for ($page = 0; $page <= $num_pages; ++$page) {
2446
      $this->drupalGet('database_test/pager_query_odd/' . $limit, array('query' => array('page' => $page)));
2447
      $data = json_decode($this->drupalGetContent());
2448

    
2449
      if ($page == $num_pages) {
2450
        $correct_number = $count - ($limit * $page);
2451
      }
2452

    
2453
      $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
2454
    }
2455
  }
2456

    
2457
  /**
2458
   * Confirm that a pager query with inner pager query returns valid results.
2459
   *
2460
   * This is a regression test for #467984.
2461
   */
2462
  function testInnerPagerQuery() {
2463
    $query = db_select('test', 't')->extend('PagerDefault');
2464
    $query
2465
      ->fields('t', array('age'))
2466
      ->orderBy('age')
2467
      ->limit(5);
2468

    
2469
    $outer_query = db_select($query);
2470
    $outer_query->addField('subquery', 'age');
2471

    
2472
    $ages = $outer_query
2473
      ->execute()
2474
      ->fetchCol();
2475
    $this->assertEqual($ages, array(25, 26, 27, 28), 'Inner pager query returned the correct ages.');
2476
  }
2477

    
2478
  /**
2479
   * Confirm that a paging query with a having expression returns valid results.
2480
   *
2481
   * This is a regression test for #467984.
2482
   */
2483
  function testHavingPagerQuery() {
2484
    $query = db_select('test', 't')->extend('PagerDefault');
2485
    $query
2486
      ->fields('t', array('name'))
2487
      ->orderBy('name')
2488
      ->groupBy('name')
2489
      ->having('MAX(age) > :count', array(':count' => 26))
2490
      ->limit(5);
2491

    
2492
    $ages = $query
2493
      ->execute()
2494
      ->fetchCol();
2495
    $this->assertEqual($ages, array('George', 'Ringo'), 'Pager query with having expression returned the correct ages.');
2496
  }
2497

    
2498
  /**
2499
   * Confirm that every pager gets a valid non-overlaping element ID.
2500
   */
2501
  function testElementNumbers() {
2502
    $_GET['page'] = '3, 2, 1, 0';
2503

    
2504
    $name = db_select('test', 't')->extend('PagerDefault')
2505
      ->element(2)
2506
      ->fields('t', array('name'))
2507
      ->orderBy('age')
2508
      ->limit(1)
2509
      ->execute()
2510
      ->fetchField();
2511
    $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.');
2512

    
2513
    // Setting an element smaller than the previous one
2514
    // should not overwrite the pager $maxElement with a smaller value.
2515
    $name = db_select('test', 't')->extend('PagerDefault')
2516
      ->element(1)
2517
      ->fields('t', array('name'))
2518
      ->orderBy('age')
2519
      ->limit(1)
2520
      ->execute()
2521
      ->fetchField();
2522
    $this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.');
2523

    
2524
    $name = db_select('test', 't')->extend('PagerDefault')
2525
      ->fields('t', array('name'))
2526
      ->orderBy('age')
2527
      ->limit(1)
2528
      ->execute()
2529
      ->fetchField();
2530
    $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.');
2531

    
2532
    unset($_GET['page']);
2533
  }
2534
}
2535

    
2536

    
2537
class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase {
2538

    
2539
  public static function getInfo() {
2540
    return array(
2541
      'name' => 'Tablesort query tests',
2542
      'description' => 'Test the tablesort query extender.',
2543
      'group' => 'Database',
2544
    );
2545
  }
2546

    
2547
  /**
2548
   * Confirm that a tablesort query returns the correct results.
2549
   *
2550
   * Note that we have to make an HTTP request to a test page handler
2551
   * because the pager depends on GET parameters.
2552
   */
2553
  function testTableSortQuery() {
2554
    $sorts = array(
2555
      array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
2556
      array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
2557
      array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
2558
      array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
2559
      // more elements here
2560

    
2561
    );
2562

    
2563
    foreach ($sorts as $sort) {
2564
      $this->drupalGet('database_test/tablesort/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
2565
      $data = json_decode($this->drupalGetContent());
2566

    
2567
      $first = array_shift($data->tasks);
2568
      $last = array_pop($data->tasks);
2569

    
2570
      $this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.');
2571
      $this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.');
2572
    }
2573
  }
2574

    
2575
  /**
2576
   * Confirm that if a tablesort's orderByHeader is called before another orderBy, that the header happens first.
2577
   *
2578
   */
2579
  function testTableSortQueryFirst() {
2580
    $sorts = array(
2581
      array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
2582
      array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
2583
      array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
2584
      array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
2585
      // more elements here
2586

    
2587
    );
2588

    
2589
    foreach ($sorts as $sort) {
2590
      $this->drupalGet('database_test/tablesort_first/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
2591
      $data = json_decode($this->drupalGetContent());
2592

    
2593
      $first = array_shift($data->tasks);
2594
      $last = array_pop($data->tasks);
2595

    
2596
      $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'])));
2597
      $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'])));
2598
    }
2599
  }
2600

    
2601
  /**
2602
   * Confirm that if a sort is not set in a tableselect form there is no error thrown when using the default.
2603
   */
2604
  function testTableSortDefaultSort() {
2605
    $this->drupalGet('database_test/tablesort_default_sort');
2606
    // Any PHP errors or notices thrown would trigger a simpletest exception, so
2607
    // no additional assertions are needed.
2608
  }
2609
}
2610

    
2611
/**
2612
 * Select tagging tests.
2613
 *
2614
 * Tags are a way to flag queries for alter hooks so they know
2615
 * what type of query it is, such as "node_access".
2616
 */
2617
class DatabaseTaggingTestCase extends DatabaseTestCase {
2618

    
2619
  public static function getInfo() {
2620
    return array(
2621
      'name' => 'Query tagging tests',
2622
      'description' => 'Test the tagging capabilities of the Select builder.',
2623
      'group' => 'Database',
2624
    );
2625
  }
2626

    
2627
  /**
2628
   * Confirm that a query has a "tag" added to it.
2629
   */
2630
  function testHasTag() {
2631
    $query = db_select('test');
2632
    $query->addField('test', 'name');
2633
    $query->addField('test', 'age', 'age');
2634

    
2635
    $query->addTag('test');
2636

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

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

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

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

    
2656
  /**
2657
   * Test query tagging "has at least one of these tags" functionality.
2658
   */
2659
  function testHasAnyTag() {
2660
    $query = db_select('test');
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
   * Confirm that an extended query has a "tag" added to it.
2672
   */
2673
  function testExtenderHasTag() {
2674
    $query = db_select('test')
2675
      ->extend('SelectQueryExtender');
2676
    $query->addField('test', 'name');
2677
    $query->addField('test', 'age', 'age');
2678

    
2679
    $query->addTag('test');
2680

    
2681
    $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
2682
    $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
2683
  }
2684

    
2685
  /**
2686
   * Test extended query tagging "has all of these tags" functionality.
2687
   */
2688
  function testExtenderHasAllTags() {
2689
    $query = db_select('test')
2690
      ->extend('SelectQueryExtender');
2691
    $query->addField('test', 'name');
2692
    $query->addField('test', 'age', 'age');
2693

    
2694
    $query->addTag('test');
2695
    $query->addTag('other');
2696

    
2697
    $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
2698
    $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
2699
  }
2700

    
2701
  /**
2702
   * Test extended query tagging "has at least one of these tags" functionality.
2703
   */
2704
  function testExtenderHasAnyTag() {
2705
    $query = db_select('test')
2706
      ->extend('SelectQueryExtender');
2707
    $query->addField('test', 'name');
2708
    $query->addField('test', 'age', 'age');
2709

    
2710
    $query->addTag('test');
2711

    
2712
    $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
2713
    $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
2714
  }
2715

    
2716
  /**
2717
   * Test that we can attach meta data to a query object.
2718
   *
2719
   * This is how we pass additional context to alter hooks.
2720
   */
2721
  function testMetaData() {
2722
    $query = db_select('test');
2723
    $query->addField('test', 'name');
2724
    $query->addField('test', 'age', 'age');
2725

    
2726
    $data = array(
2727
      'a' => 'A',
2728
      'b' => 'B',
2729
    );
2730

    
2731
    $query->addMetaData('test', $data);
2732

    
2733
    $return = $query->getMetaData('test');
2734
    $this->assertEqual($data, $return, 'Corect metadata returned.');
2735

    
2736
    $return = $query->getMetaData('nothere');
2737
    $this->assertNull($return, 'Non-existent key returned NULL.');
2738
  }
2739
}
2740

    
2741
/**
2742
 * Select alter tests.
2743
 *
2744
 * @see database_test_query_alter()
2745
 */
2746
class DatabaseAlterTestCase extends DatabaseTestCase {
2747

    
2748
  public static function getInfo() {
2749
    return array(
2750
      'name' => 'Query altering tests',
2751
      'description' => 'Test the hook_query_alter capabilities of the Select builder.',
2752
      'group' => 'Database',
2753
    );
2754
  }
2755

    
2756
  /**
2757
   * Test that we can do basic alters.
2758
   */
2759
  function testSimpleAlter() {
2760
    $query = db_select('test');
2761
    $query->addField('test', 'name');
2762
    $query->addField('test', 'age', 'age');
2763
    $query->addTag('database_test_alter_add_range');
2764

    
2765
    $result = $query->execute();
2766

    
2767
    $num_records = 0;
2768
    foreach ($result as $record) {
2769
      $num_records++;
2770
    }
2771

    
2772
    $this->assertEqual($num_records, 2, 'Returned the correct number of rows.');
2773
  }
2774

    
2775
  /**
2776
   * Test that we can alter the joins on a query.
2777
   */
2778
  function testAlterWithJoin() {
2779
    $query = db_select('test_task');
2780
    $tid_field = $query->addField('test_task', 'tid');
2781
    $task_field = $query->addField('test_task', 'task');
2782
    $query->orderBy($task_field);
2783
    $query->addTag('database_test_alter_add_join');
2784

    
2785
    $result = $query->execute();
2786

    
2787
    $records = $result->fetchAll();
2788

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

    
2791
    $this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.');
2792
    $this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.');
2793
    $this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.');
2794
    $this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.');
2795
    $this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.');
2796
    $this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.');
2797
  }
2798

    
2799
  /**
2800
   * Test that we can alter a query's conditionals.
2801
   */
2802
  function testAlterChangeConditional() {
2803
    $query = db_select('test_task');
2804
    $tid_field = $query->addField('test_task', 'tid');
2805
    $pid_field = $query->addField('test_task', 'pid');
2806
    $task_field = $query->addField('test_task', 'task');
2807
    $people_alias = $query->join('test', 'people', "test_task.pid = people.id");
2808
    $name_field = $query->addField($people_alias, 'name', 'name');
2809
    $query->condition('test_task.tid', '1');
2810
    $query->orderBy($tid_field);
2811
    $query->addTag('database_test_alter_change_conditional');
2812

    
2813
    $result = $query->execute();
2814

    
2815
    $records = $result->fetchAll();
2816

    
2817
    $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
2818
    $this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.');
2819
    $this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.');
2820
    $this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.');
2821
    $this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.');
2822
  }
2823

    
2824
  /**
2825
   * Test that we can alter the fields of a query.
2826
   */
2827
  function testAlterChangeFields() {
2828
    $query = db_select('test');
2829
    $name_field = $query->addField('test', 'name');
2830
    $age_field = $query->addField('test', 'age', 'age');
2831
    $query->orderBy('name');
2832
    $query->addTag('database_test_alter_change_fields');
2833

    
2834
    $record = $query->execute()->fetch();
2835
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2836
    $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.');
2837
  }
2838

    
2839
  /**
2840
   * Test that we can alter expressions in the query.
2841
   */
2842
  function testAlterExpression() {
2843
    $query = db_select('test');
2844
    $name_field = $query->addField('test', 'name');
2845
    $age_field = $query->addExpression("age*2", 'double_age');
2846
    $query->condition('age', 27);
2847
    $query->addTag('database_test_alter_change_expressions');
2848
    $result = $query->execute();
2849

    
2850
    // Ensure that we got the right record.
2851
    $record = $result->fetch();
2852

    
2853
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2854
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2855
  }
2856

    
2857
  /**
2858
   * Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter().
2859
   */
2860
  function testAlterRemoveRange() {
2861
    $query = db_select('test');
2862
    $query->addField('test', 'name');
2863
    $query->addField('test', 'age', 'age');
2864
    $query->range(0, 2);
2865
    $query->addTag('database_test_alter_remove_range');
2866

    
2867
    $num_records = count($query->execute()->fetchAll());
2868

    
2869
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
2870
  }
2871

    
2872
  /**
2873
   * Test that we can do basic alters on subqueries.
2874
   */
2875
  function testSimpleAlterSubquery() {
2876
    // Create a sub-query with an alter tag.
2877
    $subquery = db_select('test', 'p');
2878
    $subquery->addField('p', 'name');
2879
    $subquery->addField('p', 'id');
2880
    // Pick out George.
2881
    $subquery->condition('age', 27);
2882
    $subquery->addExpression("age*2", 'double_age');
2883
    // This query alter should change it to age * 3.
2884
    $subquery->addTag('database_test_alter_change_expressions');
2885

    
2886
    // Create a main query and join to sub-query.
2887
    $query = db_select('test_task', 'tt');
2888
    $query->join($subquery, 'pq', 'pq.id = tt.pid');
2889
    $age_field = $query->addField('pq', 'double_age');
2890
    $name_field = $query->addField('pq', 'name');
2891

    
2892
    $record = $query->execute()->fetch();
2893
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2894
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2895
  }
2896
}
2897

    
2898
/**
2899
 * Regression tests.
2900
 */
2901
class DatabaseRegressionTestCase extends DatabaseTestCase {
2902

    
2903
  public static function getInfo() {
2904
    return array(
2905
      'name' => 'Regression tests',
2906
      'description' => 'Regression tests cases for the database layer.',
2907
      'group' => 'Database',
2908
    );
2909
  }
2910

    
2911
  /**
2912
   * Regression test for #310447.
2913
   *
2914
   * Tries to insert non-ascii UTF-8 data in a database column and checks
2915
   * if its stored properly.
2916
   */
2917
  function testRegression_310447() {
2918
    // That's a 255 character UTF-8 string.
2919
    $name = str_repeat("é", 255);
2920
    db_insert('test')
2921
      ->fields(array(
2922
        'name' => $name,
2923
        'age' => 20,
2924
        'job' => 'Dancer',
2925
      ))->execute();
2926

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

    
2931
  /**
2932
   * Test the db_table_exists() function.
2933
   */
2934
  function testDBTableExists() {
2935
    $this->assertIdentical(TRUE, db_table_exists('node'), 'Returns true for existent table.');
2936
    $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.');
2937
  }
2938

    
2939
  /**
2940
   * Test the db_field_exists() function.
2941
   */
2942
  function testDBFieldExists() {
2943
    $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), 'Returns true for existent column.');
2944
    $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), 'Returns false for nonexistent column.');
2945
  }
2946

    
2947
  /**
2948
   * Test the db_index_exists() function.
2949
   */
2950
  function testDBIndexExists() {
2951
    $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.');
2952
    $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.');
2953
  }
2954
}
2955

    
2956
/**
2957
 * Query logging tests.
2958
 */
2959
class DatabaseLoggingTestCase extends DatabaseTestCase {
2960

    
2961
  public static function getInfo() {
2962
    return array(
2963
      'name' => 'Query logging',
2964
      'description' => 'Test the query logging facility.',
2965
      'group' => 'Database',
2966
    );
2967
  }
2968

    
2969
  /**
2970
   * Test that we can log the existence of a query.
2971
   */
2972
  function testEnableLogging() {
2973
    $log = Database::startLog('testing');
2974

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

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

    
2981
    $queries = Database::getLog('testing', 'default');
2982

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

    
2985
    foreach ($queries as $query) {
2986
      $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.');
2987
    }
2988
  }
2989

    
2990
  /**
2991
   * Test that we can run two logs in parallel.
2992
   */
2993
  function testEnableMultiLogging() {
2994
    Database::startLog('testing1');
2995

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

    
2998
    Database::startLog('testing2');
2999

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

    
3002
    $queries1 = Database::getLog('testing1');
3003
    $queries2 = Database::getLog('testing2');
3004

    
3005
    $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.');
3006
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.');
3007
  }
3008

    
3009
  /**
3010
   * Test that we can log queries against multiple targets on the same connection.
3011
   */
3012
  function testEnableTargetLogging() {
3013
    // Clone the master credentials to a slave connection and to another fake
3014
    // connection.
3015
    $connection_info = Database::getConnectionInfo('default');
3016
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
3017

    
3018
    Database::startLog('testing1');
3019

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

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

    
3024
    $queries1 = Database::getLog('testing1');
3025

    
3026
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
3027
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
3028
    $this->assertEqual($queries1[1]['target'], 'slave', 'Second query used slave target.');
3029
  }
3030

    
3031
  /**
3032
   * Test that logs to separate targets collapse to the same connection properly.
3033
   *
3034
   * This test is identical to the one above, except that it doesn't create
3035
   * a fake target so the query should fall back to running on the default
3036
   * target.
3037
   */
3038
  function testEnableTargetLoggingNoTarget() {
3039
    Database::startLog('testing1');
3040

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

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

    
3050
    $queries1 = Database::getLog('testing1');
3051

    
3052
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
3053
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
3054
    $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.');
3055
  }
3056

    
3057
  /**
3058
   * Test that we can log queries separately on different connections.
3059
   */
3060
  function testEnableMultiConnectionLogging() {
3061
    // Clone the master credentials to a fake connection.
3062
    // That both connections point to the same physical database is irrelevant.
3063
    $connection_info = Database::getConnectionInfo('default');
3064
    Database::addConnectionInfo('test2', 'default', $connection_info['default']);
3065

    
3066
    Database::startLog('testing1');
3067
    Database::startLog('testing1', 'test2');
3068

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

    
3071
    $old_key = db_set_active('test2');
3072

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

    
3075
    db_set_active($old_key);
3076

    
3077
    $queries1 = Database::getLog('testing1');
3078
    $queries2 = Database::getLog('testing1', 'test2');
3079

    
3080
    $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.');
3081
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
3082
  }
3083
}
3084

    
3085
/**
3086
 * Query serialization tests.
3087
 */
3088
class DatabaseSerializeQueryTestCase extends DatabaseTestCase {
3089
  public static function getInfo() {
3090
    return array(
3091
      'name' => 'Serialize query',
3092
      'description' => 'Test serializing and unserializing a query.',
3093
      'group' => 'Database',
3094
    );
3095
  }
3096

    
3097
  /**
3098
   * Confirm that a query can be serialized and unserialized.
3099
   */
3100
  function testSerializeQuery() {
3101
    $query = db_select('test');
3102
    $query->addField('test', 'age');
3103
    $query->condition('name', 'Ringo');
3104
    // If this doesn't work, it will throw an exception, so no need for an
3105
    // assertion.
3106
    $query = unserialize(serialize($query));
3107
    $results = $query->execute()->fetchCol();
3108
    $this->assertEqual($results[0], 28, 'Query properly executed after unserialization.');
3109
  }
3110
}
3111

    
3112
/**
3113
 * Range query tests.
3114
 */
3115
class DatabaseRangeQueryTestCase extends DrupalWebTestCase {
3116
  public static function getInfo() {
3117
    return array(
3118
      'name' => 'Range query test',
3119
      'description' => 'Test the Range query functionality.',
3120
      'group' => 'Database',
3121
    );
3122
  }
3123

    
3124
  function setUp() {
3125
    parent::setUp('database_test');
3126
  }
3127

    
3128
  /**
3129
   * Confirm that range query work and return correct result.
3130
   */
3131
  function testRangeQuery() {
3132
    // Test if return correct number of rows.
3133
    $range_rows = db_query_range("SELECT name FROM {system} ORDER BY name", 2, 3)->fetchAll();
3134
    $this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.');
3135

    
3136
    // Test if return target data.
3137
    $raw_rows = db_query('SELECT name FROM {system} ORDER BY name')->fetchAll();
3138
    $raw_rows = array_slice($raw_rows, 2, 3);
3139
    $this->assertEqual($range_rows, $raw_rows, 'Range query work and return target data.');
3140
  }
3141
}
3142

    
3143
/**
3144
 * Temporary query tests.
3145
 */
3146
class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase {
3147
  public static function getInfo() {
3148
    return array(
3149
      'name' => 'Temporary query test',
3150
      'description' => 'Test the temporary query functionality.',
3151
      'group' => 'Database',
3152
    );
3153
  }
3154

    
3155
  function setUp() {
3156
    parent::setUp('database_test');
3157
  }
3158

    
3159
  /**
3160
   * Return the number of rows of a table.
3161
   */
3162
  function countTableRows($table_name) {
3163
    return db_select($table_name)->countQuery()->execute()->fetchField();
3164
  }
3165

    
3166
  /**
3167
   * Confirm that temporary tables work and are limited to one request.
3168
   */
3169
  function testTemporaryQuery() {
3170
    $this->drupalGet('database_test/db_query_temporary');
3171
    $data = json_decode($this->drupalGetContent());
3172
    if ($data) {
3173
      $this->assertEqual($this->countTableRows("system"), $data->row_count, 'The temporary table contains the correct amount of rows.');
3174
      $this->assertFalse(db_table_exists($data->table_name), 'The temporary table is, indeed, temporary.');
3175
    }
3176
    else {
3177
      $this->fail("The creation of the temporary table failed.");
3178
    }
3179

    
3180
    // Now try to run two db_query_temporary() in the same request.
3181
    $table_name_system = db_query_temporary('SELECT status FROM {system}', array());
3182
    $table_name_users = db_query_temporary('SELECT uid FROM {users}', array());
3183

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

    
3187
    // Check that leading whitespace and comments do not cause problems
3188
    // in the modified query.
3189
    $sql = "
3190
      -- Let's select some rows into a temporary table
3191
      SELECT name FROM {test}
3192
    ";
3193
    $table_name_test = db_query_temporary($sql, array());
3194
    $this->assertEqual($this->countTableRows($table_name_test), $this->countTableRows('test'), 'Leading white space and comments do not interfere with temporary table creation.');
3195
  }
3196
}
3197

    
3198
/**
3199
 * Test how the current database driver interprets the SQL syntax.
3200
 *
3201
 * In order to ensure consistent SQL handling throughout Drupal
3202
 * across multiple kinds of database systems, we test that the
3203
 * database system interprets SQL syntax in an expected fashion.
3204
 */
3205
class DatabaseBasicSyntaxTestCase extends DatabaseTestCase {
3206
  public static function getInfo() {
3207
    return array(
3208
      'name' => 'Basic SQL syntax tests',
3209
      'description' => 'Test SQL syntax interpretation.',
3210
      'group' => 'Database',
3211
    );
3212
  }
3213

    
3214
  function setUp() {
3215
    parent::setUp('database_test');
3216
  }
3217

    
3218
  /**
3219
   * Test for string concatenation.
3220
   */
3221
  function testBasicConcat() {
3222
    $result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', array(
3223
      ':a1' => 'This',
3224
      ':a2' => ' ',
3225
      ':a3' => 'is',
3226
      ':a4' => ' a ',
3227
      ':a5' => 'test.',
3228
    ));
3229
    $this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.');
3230
  }
3231

    
3232
  /**
3233
   * Test for string concatenation with field values.
3234
   */
3235
  function testFieldConcat() {
3236
    $result = db_query('SELECT CONCAT(:a1, CONCAT(name, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', array(
3237
      ':a1' => 'The age of ',
3238
      ':a2' => ' is ',
3239
      ':a3' => '.',
3240
      ':age' => 25,
3241
    ));
3242
    $this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.');
3243
  }
3244

    
3245
  /**
3246
   * Test escaping of LIKE wildcards.
3247
   */
3248
  function testLikeEscape() {
3249
    db_insert('test')
3250
      ->fields(array(
3251
        'name' => 'Ring_',
3252
      ))
3253
      ->execute();
3254

    
3255
    // Match both "Ringo" and "Ring_".
3256
    $num_matches = db_select('test', 't')
3257
      ->condition('name', 'Ring_', 'LIKE')
3258
      ->countQuery()
3259
      ->execute()
3260
      ->fetchField();
3261
    $this->assertIdentical($num_matches, '2', 'Found 2 records.');
3262
    // Match only "Ring_" using a LIKE expression with no wildcards.
3263
    $num_matches = db_select('test', 't')
3264
      ->condition('name', db_like('Ring_'), 'LIKE')
3265
      ->countQuery()
3266
      ->execute()
3267
      ->fetchField();
3268
    $this->assertIdentical($num_matches, '1', 'Found 1 record.');
3269
  }
3270

    
3271
  /**
3272
   * Test LIKE query containing a backslash.
3273
   */
3274
  function testLikeBackslash() {
3275
    db_insert('test')
3276
      ->fields(array('name'))
3277
      ->values(array(
3278
        'name' => 'abcde\f',
3279
      ))
3280
      ->values(array(
3281
        'name' => 'abc%\_',
3282
      ))
3283
      ->execute();
3284

    
3285
    // Match both rows using a LIKE expression with two wildcards and a verbatim
3286
    // backslash.
3287
    $num_matches = db_select('test', 't')
3288
      ->condition('name', 'abc%\\\\_', 'LIKE')
3289
      ->countQuery()
3290
      ->execute()
3291
      ->fetchField();
3292
    $this->assertIdentical($num_matches, '2', 'Found 2 records.');
3293
    // Match only the former using a LIKE expression with no wildcards.
3294
    $num_matches = db_select('test', 't')
3295
      ->condition('name', db_like('abc%\_'), 'LIKE')
3296
      ->countQuery()
3297
      ->execute()
3298
      ->fetchField();
3299
    $this->assertIdentical($num_matches, '1', 'Found 1 record.');
3300
  }
3301
}
3302

    
3303
/**
3304
 * Test case sensitivity handling.
3305
 */
3306
class DatabaseCaseSensitivityTestCase extends DatabaseTestCase {
3307
  public static function getInfo() {
3308
    return array(
3309
      'name' => 'Case sensitivity',
3310
      'description' => 'Test handling case sensitive collation.',
3311
      'group' => 'Database',
3312
    );
3313
  }
3314

    
3315
  /**
3316
   * Test BINARY collation in MySQL.
3317
   */
3318
  function testCaseSensitiveInsert() {
3319
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3320

    
3321
    $john = db_insert('test')
3322
      ->fields(array(
3323
        'name' => 'john', // <- A record already exists with name 'John'.
3324
        'age' => 2,
3325
        'job' => 'Baby',
3326
      ))
3327
      ->execute();
3328

    
3329
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3330
    $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
3331
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
3332
    $this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.');
3333
  }
3334
}
3335

    
3336
/**
3337
 * Test invalid data handling.
3338
 */
3339
class DatabaseInvalidDataTestCase extends DatabaseTestCase {
3340
  public static function getInfo() {
3341
    return array(
3342
      'name' => 'Invalid data',
3343
      'description' => 'Test handling of some invalid data.',
3344
      'group' => 'Database',
3345
    );
3346
  }
3347

    
3348
  function setUp() {
3349
    parent::setUp('database_test');
3350
  }
3351

    
3352
  /**
3353
   * Traditional SQL database systems abort inserts when invalid data is encountered.
3354
   */
3355
  function testInsertDuplicateData() {
3356
    // Try to insert multiple records where at least one has bad data.
3357
    try {
3358
      db_insert('test')
3359
        ->fields(array('name', 'age', 'job'))
3360
        ->values(array(
3361
          'name' => 'Elvis',
3362
          'age' => 63,
3363
          'job' => 'Singer',
3364
        ))->values(array(
3365
          'name' => 'John', // <-- Duplicate value on unique field.
3366
          'age' => 17,
3367
          'job' => 'Consultant',
3368
        ))
3369
        ->values(array(
3370
          'name' => 'Frank',
3371
          'age' => 75,
3372
          'job' => 'Singer',
3373
        ))
3374
        ->execute();
3375
      $this->fail('Insert succeedded when it should not have.');
3376
    }
3377
    catch (Exception $e) {
3378
      // Check if the first record was inserted.
3379
      $name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField();
3380

    
3381
      if ($name == 'Elvis') {
3382
        if (!Database::getConnection()->supportsTransactions()) {
3383
          // This is an expected fail.
3384
          // Database engines that don't support transactions can leave partial
3385
          // inserts in place when an error occurs. This is the case for MySQL
3386
          // when running on a MyISAM table.
3387
          $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");
3388
        }
3389
        else {
3390
          $this->fail('The whole transaction is rolled back when a duplicate key insert occurs.');
3391
        }
3392
      }
3393
      else {
3394
        $this->pass('The whole transaction is rolled back when a duplicate key insert occurs.');
3395
      }
3396

    
3397
      // Ensure the other values were not inserted.
3398
      $record = db_select('test')
3399
        ->fields('test', array('name', 'age'))
3400
        ->condition('age', array(17, 75), 'IN')
3401
        ->execute()->fetchObject();
3402

    
3403
      $this->assertFalse($record, 'The rest of the insert aborted as expected.');
3404
    }
3405
  }
3406

    
3407
}
3408

    
3409
/**
3410
 * Drupal-specific SQL syntax tests.
3411
 */
3412
class DatabaseQueryTestCase extends DatabaseTestCase {
3413
  public static function getInfo() {
3414
    return array(
3415
      'name' => 'Custom query syntax tests',
3416
      'description' => 'Test Drupal\'s extended prepared statement syntax..',
3417
      'group' => 'Database',
3418
    );
3419
  }
3420

    
3421
  function setUp() {
3422
    parent::setUp('database_test');
3423
  }
3424

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

    
3431
    $this->assertEqual(count($names), 3, 'Correct number of names returned');
3432
  }
3433

    
3434
  /**
3435
   * Test SQL injection via database query array arguments.
3436
   */
3437
  public function testArrayArgumentsSQLInjection() {
3438
    // Attempt SQL injection and verify that it does not work.
3439
    $condition = array(
3440
      "1 ;INSERT INTO {test} (name) VALUES ('test12345678'); -- " => '',
3441
      '1' => '',
3442
    );
3443
    try {
3444
      db_query("SELECT * FROM {test} WHERE name = :name", array(':name' => $condition))->fetchObject();
3445
      $this->fail('SQL injection attempt via array arguments should result in a PDOException.');
3446
    }
3447
    catch (PDOException $e) {
3448
      $this->pass('SQL injection attempt via array arguments should result in a PDOException.');
3449
    }
3450

    
3451
    // Test that the insert query that was used in the SQL injection attempt did
3452
    // not result in a row being inserted in the database.
3453
    $result = db_select('test')
3454
      ->condition('name', 'test12345678')
3455
      ->countQuery()
3456
      ->execute()
3457
      ->fetchField();
3458
    $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
3459
  }
3460

    
3461
}
3462

    
3463
/**
3464
 * Test transaction support, particularly nesting.
3465
 *
3466
 * We test nesting by having two transaction layers, an outer and inner. The
3467
 * outer layer encapsulates the inner layer. Our transaction nesting abstraction
3468
 * should allow the outer layer function to call any function it wants,
3469
 * especially the inner layer that starts its own transaction, and be
3470
 * confident that, when the function it calls returns, its own transaction
3471
 * is still "alive."
3472
 *
3473
 * Call structure:
3474
 *   transactionOuterLayer()
3475
 *     Start transaction
3476
 *     transactionInnerLayer()
3477
 *       Start transaction (does nothing in database)
3478
 *       [Maybe decide to roll back]
3479
 *     Do more stuff
3480
 *     Should still be in transaction A
3481
 *
3482
 */
3483
class DatabaseTransactionTestCase extends DatabaseTestCase {
3484

    
3485
  public static function getInfo() {
3486
    return array(
3487
      'name' => 'Transaction tests',
3488
      'description' => 'Test the transaction abstraction system.',
3489
      'group' => 'Database',
3490
    );
3491
  }
3492

    
3493
  /**
3494
   * Helper method for transaction unit test.
3495
   *
3496
   * This "outer layer" transaction starts and then encapsulates the
3497
   * "inner layer" transaction. This nesting is used to evaluate whether the
3498
   * database transaction API properly supports nesting. By "properly supports,"
3499
   * we mean the outer transaction continues to exist regardless of what
3500
   * functions are called and whether those functions start their own
3501
   * transactions.
3502
   *
3503
   * In contrast, a typical database would commit the outer transaction, start
3504
   * a new transaction for the inner layer, commit the inner layer transaction,
3505
   * and then be confused when the outer layer transaction tries to commit its
3506
   * transaction (which was already committed when the inner transaction
3507
   * started).
3508
   *
3509
   * @param $suffix
3510
   *   Suffix to add to field values to differentiate tests.
3511
   * @param $rollback
3512
   *   Whether or not to try rolling back the transaction when we're done.
3513
   * @param $ddl_statement
3514
   *   Whether to execute a DDL statement during the inner transaction.
3515
   */
3516
  protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
3517
    $connection = Database::getConnection();
3518
    $depth = $connection->transactionDepth();
3519
    $txn = db_transaction();
3520

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

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

    
3531
    // We're already in a transaction, but we call ->transactionInnerLayer
3532
    // to nest another transaction inside the current one.
3533
    $this->transactionInnerLayer($suffix, $rollback, $ddl_statement);
3534

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

    
3537
    if ($rollback) {
3538
      // Roll back the transaction, if requested.
3539
      // This rollback should propagate to the last savepoint.
3540
      $txn->rollback();
3541
      $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
3542
    }
3543
  }
3544

    
3545
  /**
3546
   * Helper method for transaction unit tests. This "inner layer" transaction
3547
   * is either used alone or nested inside of the "outer layer" transaction.
3548
   *
3549
   * @param $suffix
3550
   *   Suffix to add to field values to differentiate tests.
3551
   * @param $rollback
3552
   *   Whether or not to try rolling back the transaction when we're done.
3553
   * @param $ddl_statement
3554
   *   Whether to execute a DDL statement during the transaction.
3555
   */
3556
  protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
3557
    $connection = Database::getConnection();
3558

    
3559
    $depth = $connection->transactionDepth();
3560
    // Start a transaction. If we're being called from ->transactionOuterLayer,
3561
    // then we're already in a transaction. Normally, that would make starting
3562
    // a transaction here dangerous, but the database API handles this problem
3563
    // for us by tracking the nesting and avoiding the danger.
3564
    $txn = db_transaction();
3565

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

    
3569
    // Insert a single row into the testing table.
3570
    db_insert('test')
3571
      ->fields(array(
3572
        'name' => 'Daniel' . $suffix,
3573
        'age' => '19',
3574
      ))
3575
      ->execute();
3576

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

    
3579
    if ($ddl_statement) {
3580
      $table = array(
3581
        'fields' => array(
3582
          'id' => array(
3583
            'type' => 'serial',
3584
            'unsigned' => TRUE,
3585
            'not null' => TRUE,
3586
          ),
3587
        ),
3588
        'primary key' => array('id'),
3589
      );
3590
      db_create_table('database_test_1', $table);
3591

    
3592
      $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
3593
    }
3594

    
3595
    if ($rollback) {
3596
      // Roll back the transaction, if requested.
3597
      // This rollback should propagate to the last savepoint.
3598
      $txn->rollback();
3599
      $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
3600
    }
3601
  }
3602

    
3603
  /**
3604
   * Test transaction rollback on a database that supports transactions.
3605
   *
3606
   * If the active connection does not support transactions, this test does nothing.
3607
   */
3608
  function testTransactionRollBackSupported() {
3609
    // This test won't work right if transactions are not supported.
3610
    if (!Database::getConnection()->supportsTransactions()) {
3611
      return;
3612
    }
3613
    try {
3614
      // Create two nested transactions. Roll back from the inner one.
3615
      $this->transactionOuterLayer('B', TRUE);
3616

    
3617
      // Neither of the rows we inserted in the two transaction layers
3618
      // should be present in the tables post-rollback.
3619
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
3620
      $this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.');
3621
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
3622
      $this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.');
3623
    }
3624
    catch (Exception $e) {
3625
      $this->fail($e->getMessage());
3626
    }
3627
  }
3628

    
3629
  /**
3630
   * Test transaction rollback on a database that does not support transactions.
3631
   *
3632
   * If the active driver supports transactions, this test does nothing.
3633
   */
3634
  function testTransactionRollBackNotSupported() {
3635
    // This test won't work right if transactions are supported.
3636
    if (Database::getConnection()->supportsTransactions()) {
3637
      return;
3638
    }
3639
    try {
3640
      // Create two nested transactions. Attempt to roll back from the inner one.
3641
      $this->transactionOuterLayer('B', TRUE);
3642

    
3643
      // Because our current database claims to not support transactions,
3644
      // the inserted rows should be present despite the attempt to roll back.
3645
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
3646
      $this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.');
3647
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
3648
      $this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.');
3649
    }
3650
    catch (Exception $e) {
3651
      $this->fail($e->getMessage());
3652
    }
3653
  }
3654

    
3655
  /**
3656
   * Test committed transaction.
3657
   *
3658
   * The behavior of this test should be identical for connections that support
3659
   * transactions and those that do not.
3660
   */
3661
  function testCommittedTransaction() {
3662
    try {
3663
      // Create two nested transactions. The changes should be committed.
3664
      $this->transactionOuterLayer('A');
3665

    
3666
      // Because we committed, both of the inserted rows should be present.
3667
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField();
3668
      $this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.');
3669
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField();
3670
      $this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.');
3671
    }
3672
    catch (Exception $e) {
3673
      $this->fail($e->getMessage());
3674
    }
3675
  }
3676

    
3677
  /**
3678
   * Test the compatibility of transactions with DDL statements.
3679
   */
3680
  function testTransactionWithDdlStatement() {
3681
    // First, test that a commit works normally, even with DDL statements.
3682
    $transaction = db_transaction();
3683
    $this->insertRow('row');
3684
    $this->executeDDLStatement();
3685
    unset($transaction);
3686
    $this->assertRowPresent('row');
3687

    
3688
    // Even in different order.
3689
    $this->cleanUp();
3690
    $transaction = db_transaction();
3691
    $this->executeDDLStatement();
3692
    $this->insertRow('row');
3693
    unset($transaction);
3694
    $this->assertRowPresent('row');
3695

    
3696
    // Even with stacking.
3697
    $this->cleanUp();
3698
    $transaction = db_transaction();
3699
    $transaction2 = db_transaction();
3700
    $this->executeDDLStatement();
3701
    unset($transaction2);
3702
    $transaction3 = db_transaction();
3703
    $this->insertRow('row');
3704
    unset($transaction3);
3705
    unset($transaction);
3706
    $this->assertRowPresent('row');
3707

    
3708
    // A transaction after a DDL statement should still work the same.
3709
    $this->cleanUp();
3710
    $transaction = db_transaction();
3711
    $transaction2 = db_transaction();
3712
    $this->executeDDLStatement();
3713
    unset($transaction2);
3714
    $transaction3 = db_transaction();
3715
    $this->insertRow('row');
3716
    $transaction3->rollback();
3717
    unset($transaction3);
3718
    unset($transaction);
3719
    $this->assertRowAbsent('row');
3720

    
3721
    // The behavior of a rollback depends on the type of database server.
3722
    if (Database::getConnection()->supportsTransactionalDDL()) {
3723
      // For database servers that support transactional DDL, a rollback
3724
      // of a transaction including DDL statements should be possible.
3725
      $this->cleanUp();
3726
      $transaction = db_transaction();
3727
      $this->insertRow('row');
3728
      $this->executeDDLStatement();
3729
      $transaction->rollback();
3730
      unset($transaction);
3731
      $this->assertRowAbsent('row');
3732

    
3733
      // Including with stacking.
3734
      $this->cleanUp();
3735
      $transaction = db_transaction();
3736
      $transaction2 = db_transaction();
3737
      $this->executeDDLStatement();
3738
      unset($transaction2);
3739
      $transaction3 = db_transaction();
3740
      $this->insertRow('row');
3741
      unset($transaction3);
3742
      $transaction->rollback();
3743
      unset($transaction);
3744
      $this->assertRowAbsent('row');
3745
    }
3746
    else {
3747
      // For database servers that do not support transactional DDL,
3748
      // the DDL statement should commit the transaction stack.
3749
      $this->cleanUp();
3750
      $transaction = db_transaction();
3751
      $this->insertRow('row');
3752
      $this->executeDDLStatement();
3753
      // Rollback the outer transaction.
3754
      try {
3755
        $transaction->rollback();
3756
        unset($transaction);
3757
        // @TODO: an exception should be triggered here, but is not, because
3758
        // "ROLLBACK" fails silently in MySQL if there is no transaction active.
3759
        // $this->fail(t('Rolling back a transaction containing DDL should fail.'));
3760
      }
3761
      catch (DatabaseTransactionNoActiveException $e) {
3762
        $this->pass('Rolling back a transaction containing DDL should fail.');
3763
      }
3764
      $this->assertRowPresent('row');
3765
    }
3766
  }
3767

    
3768
  /**
3769
   * Insert a single row into the testing table.
3770
   */
3771
  protected function insertRow($name) {
3772
    db_insert('test')
3773
      ->fields(array(
3774
        'name' => $name,
3775
      ))
3776
      ->execute();
3777
  }
3778

    
3779
  /**
3780
   * Execute a DDL statement.
3781
   */
3782
  protected function executeDDLStatement() {
3783
    static $count = 0;
3784
    $table = array(
3785
      'fields' => array(
3786
        'id' => array(
3787
          'type' => 'serial',
3788
          'unsigned' => TRUE,
3789
          'not null' => TRUE,
3790
        ),
3791
      ),
3792
      'primary key' => array('id'),
3793
    );
3794
    db_create_table('database_test_' . ++$count, $table);
3795
  }
3796

    
3797
  /**
3798
   * Start over for a new test.
3799
   */
3800
  protected function cleanUp() {
3801
    db_truncate('test')
3802
      ->execute();
3803
  }
3804

    
3805
  /**
3806
   * Assert that a given row is present in the test table.
3807
   *
3808
   * @param $name
3809
   *   The name of the row.
3810
   * @param $message
3811
   *   The message to log for the assertion.
3812
   */
3813
  function assertRowPresent($name, $message = NULL) {
3814
    if (!isset($message)) {
3815
      $message = format_string('Row %name is present.', array('%name' => $name));
3816
    }
3817
    $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
3818
    return $this->assertTrue($present, $message);
3819
  }
3820

    
3821
  /**
3822
   * Assert that a given row is absent from the test table.
3823
   *
3824
   * @param $name
3825
   *   The name of the row.
3826
   * @param $message
3827
   *   The message to log for the assertion.
3828
   */
3829
  function assertRowAbsent($name, $message = NULL) {
3830
    if (!isset($message)) {
3831
      $message = format_string('Row %name is absent.', array('%name' => $name));
3832
    }
3833
    $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
3834
    return $this->assertFalse($present, $message);
3835
  }
3836

    
3837
  /**
3838
   * Test transaction stacking and commit / rollback.
3839
   */
3840
  function testTransactionStacking() {
3841
    // This test won't work right if transactions are not supported.
3842
    if (!Database::getConnection()->supportsTransactions()) {
3843
      return;
3844
    }
3845

    
3846
    $database = Database::getConnection();
3847

    
3848
    // Standard case: pop the inner transaction before the outer transaction.
3849
    $transaction = db_transaction();
3850
    $this->insertRow('outer');
3851
    $transaction2 = db_transaction();
3852
    $this->insertRow('inner');
3853
    // Pop the inner transaction.
3854
    unset($transaction2);
3855
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction');
3856
    // Pop the outer transaction.
3857
    unset($transaction);
3858
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction');
3859
    $this->assertRowPresent('outer');
3860
    $this->assertRowPresent('inner');
3861

    
3862
    // Pop the transaction in a different order they have been pushed.
3863
    $this->cleanUp();
3864
    $transaction = db_transaction();
3865
    $this->insertRow('outer');
3866
    $transaction2 = db_transaction();
3867
    $this->insertRow('inner');
3868
    // Pop the outer transaction, nothing should happen.
3869
    unset($transaction);
3870
    $this->insertRow('inner-after-outer-commit');
3871
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3872
    // Pop the inner transaction, the whole transaction should commit.
3873
    unset($transaction2);
3874
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3875
    $this->assertRowPresent('outer');
3876
    $this->assertRowPresent('inner');
3877
    $this->assertRowPresent('inner-after-outer-commit');
3878

    
3879
    // Rollback the inner transaction.
3880
    $this->cleanUp();
3881
    $transaction = db_transaction();
3882
    $this->insertRow('outer');
3883
    $transaction2 = db_transaction();
3884
    $this->insertRow('inner');
3885
    // Now rollback the inner transaction.
3886
    $transaction2->rollback();
3887
    unset($transaction2);
3888
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3889
    // Pop the outer transaction, it should commit.
3890
    $this->insertRow('outer-after-inner-rollback');
3891
    unset($transaction);
3892
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3893
    $this->assertRowPresent('outer');
3894
    $this->assertRowAbsent('inner');
3895
    $this->assertRowPresent('outer-after-inner-rollback');
3896

    
3897
    // Rollback the inner transaction after committing the outer one.
3898
    $this->cleanUp();
3899
    $transaction = db_transaction();
3900
    $this->insertRow('outer');
3901
    $transaction2 = db_transaction();
3902
    $this->insertRow('inner');
3903
    // Pop the outer transaction, nothing should happen.
3904
    unset($transaction);
3905
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3906
    // Now rollback the inner transaction, it should rollback.
3907
    $transaction2->rollback();
3908
    unset($transaction2);
3909
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3910
    $this->assertRowPresent('outer');
3911
    $this->assertRowAbsent('inner');
3912

    
3913
    // Rollback the outer transaction while the inner transaction is active.
3914
    // In that case, an exception will be triggered because we cannot
3915
    // ensure that the final result will have any meaning.
3916
    $this->cleanUp();
3917
    $transaction = db_transaction();
3918
    $this->insertRow('outer');
3919
    $transaction2 = db_transaction();
3920
    $this->insertRow('inner');
3921
    $transaction3 = db_transaction();
3922
    $this->insertRow('inner2');
3923
    // Rollback the outer transaction.
3924
    try {
3925
      $transaction->rollback();
3926
      unset($transaction);
3927
      $this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
3928
    }
3929
    catch (DatabaseTransactionOutOfOrderException $e) {
3930
      $this->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.');
3931
    }
3932
    $this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction');
3933
    // Try to commit one inner transaction.
3934
    unset($transaction3);
3935
    $this->pass('Trying to commit an inner transaction resulted in an exception.');
3936
    // Try to rollback one inner transaction.
3937
    try {
3938
      $transaction->rollback();
3939
      unset($transaction2);
3940
      $this->fail('Trying to commit an inner transaction resulted in an exception.');
3941
    }
3942
    catch (DatabaseTransactionNoActiveException $e) {
3943
      $this->pass('Trying to commit an inner transaction resulted in an exception.');
3944
    }
3945
    $this->assertRowAbsent('outer');
3946
    $this->assertRowAbsent('inner');
3947
    $this->assertRowAbsent('inner2');
3948
  }
3949
}
3950

    
3951

    
3952
/**
3953
 * Check the sequences API.
3954
 */
3955
class DatabaseNextIdCase extends DrupalWebTestCase {
3956
  public static function getInfo() {
3957
    return array(
3958
      'name' => 'Sequences API',
3959
      'description' => 'Test the secondary sequences API.',
3960
      'group' => 'Database',
3961
    );
3962
  }
3963

    
3964
  /**
3965
   * Test that the sequences API work.
3966
   */
3967
  function testDbNextId() {
3968
    $first = db_next_id();
3969
    $second = db_next_id();
3970
    // We can test for exact increase in here because we know there is no
3971
    // other process operating on these tables -- normally we could only
3972
    // expect $second > $first.
3973
    $this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.');
3974
    $result = db_next_id(1000);
3975
    $this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.');
3976
  }
3977
}
3978

    
3979
/**
3980
 * Tests the empty pseudo-statement class.
3981
 */
3982
class DatabaseEmptyStatementTestCase extends DrupalWebTestCase {
3983
  public static function getInfo() {
3984
    return array(
3985
      'name' => 'Empty statement',
3986
      'description' => 'Test the empty pseudo-statement class.',
3987
      'group' => 'Database',
3988
    );
3989
  }
3990

    
3991
  /**
3992
   * Test that the empty result set behaves as empty.
3993
   */
3994
  function testEmpty() {
3995
    $result = new DatabaseStatementEmpty();
3996

    
3997
    $this->assertTrue($result instanceof DatabaseStatementInterface, 'Class implements expected interface');
3998
    $this->assertNull($result->fetchObject(), 'Null result returned.');
3999
  }
4000

    
4001
  /**
4002
   * Test that the empty result set iterates safely.
4003
   */
4004
  function testEmptyIteration() {
4005
    $result = new DatabaseStatementEmpty();
4006

    
4007
    foreach ($result as $record) {
4008
      $this->fail('Iterating empty result set should not iterate.');
4009
      return;
4010
    }
4011

    
4012
    $this->pass('Iterating empty result set skipped iteration.');
4013
  }
4014

    
4015
  /**
4016
   * Test that the empty result set mass-fetches in an expected way.
4017
   */
4018
  function testEmptyFetchAll() {
4019
    $result = new DatabaseStatementEmpty();
4020

    
4021
    $this->assertEqual($result->fetchAll(), array(), 'Empty array returned from empty result set.');
4022
  }
4023
}
4024

    
4025
/**
4026
 * Tests management of database connections.
4027
 */
4028
class ConnectionUnitTest extends DrupalUnitTestCase {
4029

    
4030
  protected $key;
4031
  protected $target;
4032

    
4033
  protected $monitor;
4034
  protected $originalCount;
4035

    
4036
  public static function getInfo() {
4037
    return array(
4038
      'name' => 'Connection unit tests',
4039
      'description' => 'Tests management of database connections.',
4040
      'group' => 'Database',
4041
    );
4042
  }
4043

    
4044
  function setUp() {
4045
    parent::setUp();
4046

    
4047
    $this->key = 'default';
4048
    $this->originalTarget = 'default';
4049
    $this->target = 'DatabaseConnectionUnitTest';
4050

    
4051
    // Determine whether the database driver is MySQL. If it is not, the test
4052
    // methods will not be executed.
4053
    // @todo Make this test driver-agnostic, or find a proper way to skip it.
4054
    // @see http://drupal.org/node/1273478
4055
    $connection_info = Database::getConnectionInfo('default');
4056
    $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql';
4057
    if ($this->skipTest) {
4058
      // Insert an assertion to prevent Simpletest from interpreting the test
4059
      // as failure.
4060
      $this->pass('This test is only compatible with MySQL.');
4061
    }
4062

    
4063
    // Create an additional connection to monitor the connections being opened
4064
    // and closed in this test.
4065
    // @see TestBase::changeDatabasePrefix()
4066
    $connection_info = Database::getConnectionInfo('default');
4067
    Database::addConnectionInfo('default', 'monitor', $connection_info['default']);
4068
    global $databases;
4069
    $databases['default']['monitor'] = $connection_info['default'];
4070
    $this->monitor = Database::getConnection('monitor');
4071
  }
4072

    
4073
  /**
4074
   * Adds a new database connection info to Database.
4075
   */
4076
  protected function addConnection() {
4077
    // Add a new target to the connection, by cloning the current connection.
4078
    $connection_info = Database::getConnectionInfo($this->key);
4079
    Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]);
4080

    
4081
    // Verify that the new target exists.
4082
    $info = Database::getConnectionInfo($this->key);
4083
    // Note: Custom assertion message to not expose database credentials.
4084
    $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.');
4085
  }
4086

    
4087
  /**
4088
   * Returns the connection ID of the current test connection.
4089
   *
4090
   * @return integer
4091
   */
4092
  protected function getConnectionID() {
4093
    return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField();
4094
  }
4095

    
4096
  /**
4097
   * Asserts that a connection ID exists.
4098
   *
4099
   * @param integer $id
4100
   *   The connection ID to verify.
4101
   */
4102
  protected function assertConnection($id) {
4103
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
4104
    return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id)));
4105
  }
4106

    
4107
  /**
4108
   * Asserts that a connection ID does not exist.
4109
   *
4110
   * @param integer $id
4111
   *   The connection ID to verify.
4112
   */
4113
  protected function assertNoConnection($id) {
4114
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
4115
    return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id)));
4116
  }
4117

    
4118
  /**
4119
   * Tests Database::closeConnection() without query.
4120
   *
4121
   * @todo getConnectionID() executes a query.
4122
   */
4123
  function testOpenClose() {
4124
    if ($this->skipTest) {
4125
      return;
4126
    }
4127
    // Add and open a new connection.
4128
    $this->addConnection();
4129
    $id = $this->getConnectionID();
4130
    Database::getConnection($this->target, $this->key);
4131

    
4132
    // Verify that there is a new connection.
4133
    $this->assertConnection($id);
4134

    
4135
    // Close the connection.
4136
    Database::closeConnection($this->target, $this->key);
4137
    // Wait 20ms to give the database engine sufficient time to react.
4138
    usleep(20000);
4139

    
4140
    // Verify that we are back to the original connection count.
4141
    $this->assertNoConnection($id);
4142
  }
4143

    
4144
  /**
4145
   * Tests Database::closeConnection() with a query.
4146
   */
4147
  function testOpenQueryClose() {
4148
    if ($this->skipTest) {
4149
      return;
4150
    }
4151
    // Add and open a new connection.
4152
    $this->addConnection();
4153
    $id = $this->getConnectionID();
4154
    Database::getConnection($this->target, $this->key);
4155

    
4156
    // Verify that there is a new connection.
4157
    $this->assertConnection($id);
4158

    
4159
    // Execute a query.
4160
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES');
4161

    
4162
    // Close the connection.
4163
    Database::closeConnection($this->target, $this->key);
4164
    // Wait 20ms to give the database engine sufficient time to react.
4165
    usleep(20000);
4166

    
4167
    // Verify that we are back to the original connection count.
4168
    $this->assertNoConnection($id);
4169
  }
4170

    
4171
  /**
4172
   * Tests Database::closeConnection() with a query and custom prefetch method.
4173
   */
4174
  function testOpenQueryPrefetchClose() {
4175
    if ($this->skipTest) {
4176
      return;
4177
    }
4178
    // Add and open a new connection.
4179
    $this->addConnection();
4180
    $id = $this->getConnectionID();
4181
    Database::getConnection($this->target, $this->key);
4182

    
4183
    // Verify that there is a new connection.
4184
    $this->assertConnection($id);
4185

    
4186
    // Execute a query.
4187
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol();
4188

    
4189
    // Close the connection.
4190
    Database::closeConnection($this->target, $this->key);
4191
    // Wait 20ms to give the database engine sufficient time to react.
4192
    usleep(20000);
4193

    
4194
    // Verify that we are back to the original connection count.
4195
    $this->assertNoConnection($id);
4196
  }
4197

    
4198
  /**
4199
   * Tests Database::closeConnection() with a select query.
4200
   */
4201
  function testOpenSelectQueryClose() {
4202
    if ($this->skipTest) {
4203
      return;
4204
    }
4205
    // Add and open a new connection.
4206
    $this->addConnection();
4207
    $id = $this->getConnectionID();
4208
    Database::getConnection($this->target, $this->key);
4209

    
4210
    // Verify that there is a new connection.
4211
    $this->assertConnection($id);
4212

    
4213
    // Create a table.
4214
    $name = 'foo';
4215
    Database::getConnection($this->target, $this->key)->schema()->createTable($name, array(
4216
      'fields' => array(
4217
        'name' => array(
4218
          'type' => 'varchar',
4219
          'length' => 255,
4220
        ),
4221
      ),
4222
    ));
4223

    
4224
    // Execute a query.
4225
    Database::getConnection($this->target, $this->key)->select('foo', 'f')
4226
      ->fields('f', array('name'))
4227
      ->execute()
4228
      ->fetchAll();
4229

    
4230
    // Drop the table.
4231
    Database::getConnection($this->target, $this->key)->schema()->dropTable($name);
4232

    
4233
    // Close the connection.
4234
    Database::closeConnection($this->target, $this->key);
4235
    // Wait 20ms to give the database engine sufficient time to react.
4236
    usleep(20000);
4237

    
4238
    // Verify that we are back to the original connection count.
4239
    $this->assertNoConnection($id);
4240
  }
4241

    
4242
}