Projet

Général

Profil

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1951
  /**
1952
   * Tests that the sort direction is sanitized properly.
1953
   */
1954
  function testOrderByEscaping() {
1955
    $query = db_select('test')->orderBy('name', 'invalid direction');
1956
    $order_bys = $query->getOrderBy();
1957
    $this->assertEqual($order_bys['name'], 'ASC', 'Invalid order by direction is converted to ASC.');
1958
  }
1959
}
1960

    
1961
/**
1962
 * Test more complex select statements.
1963
 */
1964
class DatabaseSelectComplexTestCase extends DatabaseTestCase {
1965

    
1966
  public static function getInfo() {
1967
    return array(
1968
      'name' => 'Select tests, complex',
1969
      'description' => 'Test the Select query builder with more complex queries.',
1970
      'group' => 'Database',
1971
    );
1972
  }
1973

    
1974
  /**
1975
   * Test simple JOIN statements.
1976
   */
1977
  function testDefaultJoin() {
1978
    $query = db_select('test_task', 't');
1979
    $people_alias = $query->join('test', 'p', 't.pid = p.id');
1980
    $name_field = $query->addField($people_alias, 'name', 'name');
1981
    $task_field = $query->addField('t', 'task', 'task');
1982
    $priority_field = $query->addField('t', 'priority', 'priority');
1983

    
1984
    $query->orderBy($priority_field);
1985
    $result = $query->execute();
1986

    
1987
    $num_records = 0;
1988
    $last_priority = 0;
1989
    foreach ($result as $record) {
1990
      $num_records++;
1991
      $this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.');
1992
      $this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.');
1993
      $last_priority = $record->$priority_field;
1994
    }
1995

    
1996
    $this->assertEqual($num_records, 7, 'Returned the correct number of rows.');
1997
  }
1998

    
1999
  /**
2000
   * Test LEFT OUTER joins.
2001
   */
2002
  function testLeftOuterJoin() {
2003
    $query = db_select('test', 'p');
2004
    $people_alias = $query->leftJoin('test_task', 't', 't.pid = p.id');
2005
    $name_field = $query->addField('p', 'name', 'name');
2006
    $task_field = $query->addField($people_alias, 'task', 'task');
2007
    $priority_field = $query->addField($people_alias, 'priority', 'priority');
2008

    
2009
    $query->orderBy($name_field);
2010
    $result = $query->execute();
2011

    
2012
    $num_records = 0;
2013
    $last_name = 0;
2014

    
2015
    foreach ($result as $record) {
2016
      $num_records++;
2017
      $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.');
2018
      $last_priority = $record->$name_field;
2019
    }
2020

    
2021
    $this->assertEqual($num_records, 8, 'Returned the correct number of rows.');
2022
  }
2023

    
2024
  /**
2025
   * Test GROUP BY clauses.
2026
   */
2027
  function testGroupBy() {
2028
    $query = db_select('test_task', 't');
2029
    $count_field = $query->addExpression('COUNT(task)', 'num');
2030
    $task_field = $query->addField('t', 'task');
2031
    $query->orderBy($count_field);
2032
    $query->groupBy($task_field);
2033
    $result = $query->execute();
2034

    
2035
    $num_records = 0;
2036
    $last_count = 0;
2037
    $records = array();
2038
    foreach ($result as $record) {
2039
      $num_records++;
2040
      $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
2041
      $last_count = $record->$count_field;
2042
      $records[$record->$task_field] = $record->$count_field;
2043
    }
2044

    
2045
    $correct_results = array(
2046
      'eat' => 1,
2047
      'sleep' => 2,
2048
      'code' => 1,
2049
      'found new band' => 1,
2050
      'perform at superbowl' => 1,
2051
    );
2052

    
2053
    foreach ($correct_results as $task => $count) {
2054
      $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
2055
    }
2056

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

    
2060
  /**
2061
   * Test GROUP BY and HAVING clauses together.
2062
   */
2063
  function testGroupByAndHaving() {
2064
    $query = db_select('test_task', 't');
2065
    $count_field = $query->addExpression('COUNT(task)', 'num');
2066
    $task_field = $query->addField('t', 'task');
2067
    $query->orderBy($count_field);
2068
    $query->groupBy($task_field);
2069
    $query->having('COUNT(task) >= 2');
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 >= 2, 'Record has the minimum count.');
2078
      $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
2079
      $last_count = $record->$count_field;
2080
      $records[$record->$task_field] = $record->$count_field;
2081
    }
2082

    
2083
    $correct_results = array(
2084
      'sleep' => 2,
2085
    );
2086

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

    
2091
    $this->assertEqual($num_records, 1, 'Returned the correct number of total rows.');
2092
  }
2093

    
2094
  /**
2095
   * Test range queries. The SQL clause varies with the database.
2096
   */
2097
  function testRange() {
2098
    $query = db_select('test');
2099
    $name_field = $query->addField('test', 'name');
2100
    $age_field = $query->addField('test', 'age', 'age');
2101
    $query->range(0, 2);
2102
    $result = $query->execute();
2103

    
2104
    $num_records = 0;
2105
    foreach ($result as $record) {
2106
      $num_records++;
2107
    }
2108

    
2109
    $this->assertEqual($num_records, 2, 'Returned the correct number of rows.');
2110
  }
2111

    
2112
  /**
2113
   * Test distinct queries.
2114
   */
2115
  function testDistinct() {
2116
    $query = db_select('test_task');
2117
    $task_field = $query->addField('test_task', 'task');
2118
    $query->distinct();
2119
    $result = $query->execute();
2120

    
2121
    $num_records = 0;
2122
    foreach ($result as $record) {
2123
      $num_records++;
2124
    }
2125

    
2126
    $this->assertEqual($num_records, 6, 'Returned the correct number of rows.');
2127
  }
2128

    
2129
  /**
2130
   * Test that we can generate a count query from a built query.
2131
   */
2132
  function testCountQuery() {
2133
    $query = db_select('test');
2134
    $name_field = $query->addField('test', 'name');
2135
    $age_field = $query->addField('test', 'age', 'age');
2136
    $query->orderBy('name');
2137

    
2138
    $count = $query->countQuery()->execute()->fetchField();
2139

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

    
2142
    // Now make sure we didn't break the original query!  We should still have
2143
    // all of the fields we asked for.
2144
    $record = $query->execute()->fetch();
2145
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2146
    $this->assertEqual($record->$age_field, 27, 'Correct data retrieved.');
2147
  }
2148

    
2149
  function testHavingCountQuery() {
2150
    $query = db_select('test')
2151
      ->extend('PagerDefault')
2152
      ->groupBy('age')
2153
      ->having('age + 1 > 0');
2154
    $query->addField('test', 'age');
2155
    $query->addExpression('age + 1');
2156
    $count = count($query->execute()->fetchCol());
2157
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2158
  }
2159

    
2160
  /**
2161
   * Test that countQuery properly removes 'all_fields' statements and
2162
   * ordering clauses.
2163
   */
2164
  function testCountQueryRemovals() {
2165
    $query = db_select('test');
2166
    $query->fields('test');
2167
    $query->orderBy('name');
2168
    $count = $query->countQuery();
2169

    
2170
    // Check that the 'all_fields' statement is handled properly.
2171
    $tables = $query->getTables();
2172
    $this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.');
2173
    $tables = $count->getTables();
2174
    $this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.');
2175

    
2176
    // Check that the ordering clause is handled properly.
2177
    $orderby = $query->getOrderBy();
2178
    $this->assertEqual($orderby['name'], 'ASC', 'Query correctly sets ordering clause.');
2179
    $orderby = $count->getOrderBy();
2180
    $this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering caluse.');
2181

    
2182
    // Make sure that the count query works.
2183
    $count = $count->execute()->fetchField();
2184

    
2185
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2186
  }
2187

    
2188

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

    
2202
    $query = db_select('test');
2203
    $query->addExpression('fail');
2204
    $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions');
2205
  }
2206

    
2207
  /**
2208
   * Test that we can generate a count query from a query with distinct.
2209
   */
2210
  function testCountQueryDistinct() {
2211
    $query = db_select('test_task');
2212
    $task_field = $query->addField('test_task', 'task');
2213
    $query->distinct();
2214

    
2215
    $count = $query->countQuery()->execute()->fetchField();
2216

    
2217
    $this->assertEqual($count, 6, 'Counted the correct number of records.');
2218
  }
2219

    
2220
  /**
2221
   * Test that we can generate a count query from a query with GROUP BY.
2222
   */
2223
  function testCountQueryGroupBy() {
2224
    $query = db_select('test_task');
2225
    $pid_field = $query->addField('test_task', 'pid');
2226
    $query->groupBy('pid');
2227

    
2228
    $count = $query->countQuery()->execute()->fetchField();
2229

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

    
2232
    // Use a column alias as, without one, the query can succeed for the wrong
2233
    // reason.
2234
    $query = db_select('test_task');
2235
    $pid_field = $query->addField('test_task', 'pid', 'pid_alias');
2236
    $query->addExpression('COUNT(test_task.task)', 'count');
2237
    $query->groupBy('pid_alias');
2238
    $query->orderBy('pid_alias', 'asc');
2239

    
2240
    $count = $query->countQuery()->execute()->fetchField();
2241

    
2242
    $this->assertEqual($count, 3, 'Counted the correct number of records.');
2243
  }
2244

    
2245
  /**
2246
   * Confirm that we can properly nest conditional clauses.
2247
   */
2248
  function testNestedConditions() {
2249
    // This query should translate to:
2250
    // "SELECT job FROM {test} WHERE name = 'Paul' AND (age = 26 OR age = 27)"
2251
    // That should find only one record. Yes it's a non-optimal way of writing
2252
    // that query but that's not the point!
2253
    $query = db_select('test');
2254
    $query->addField('test', 'job');
2255
    $query->condition('name', 'Paul');
2256
    $query->condition(db_or()->condition('age', 26)->condition('age', 27));
2257

    
2258
    $job = $query->execute()->fetchField();
2259
    $this->assertEqual($job, 'Songwriter', 'Correct data retrieved.');
2260
  }
2261

    
2262
  /**
2263
   * Confirm we can join on a single table twice with a dynamic alias.
2264
   */
2265
  function testJoinTwice() {
2266
    $query = db_select('test')->fields('test');
2267
    $alias = $query->join('test', 'test', 'test.job = %alias.job');
2268
    $query->addField($alias, 'name', 'othername');
2269
    $query->addField($alias, 'job', 'otherjob');
2270
    $query->where("$alias.name <> test.name");
2271
    $crowded_job = $query->execute()->fetch();
2272
    $this->assertEqual($crowded_job->job, $crowded_job->otherjob, 'Correctly joined same table twice.');
2273
    $this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.');
2274
  }
2275

    
2276
}
2277

    
2278
/**
2279
 * Test more complex select statements, part 2.
2280
 */
2281
class DatabaseSelectComplexTestCase2 extends DatabaseTestCase {
2282

    
2283
  public static function getInfo() {
2284
    return array(
2285
      'name' => 'Select tests, complex 2',
2286
      'description' => 'Test the Select query builder with even more complex queries.',
2287
      'group' => 'Database',
2288
    );
2289
  }
2290

    
2291
  function setUp() {
2292
    DrupalWebTestCase::setUp('database_test', 'node_access_test');
2293

    
2294
    $schema['test'] = drupal_get_schema('test');
2295
    $schema['test_people'] = drupal_get_schema('test_people');
2296
    $schema['test_one_blob'] = drupal_get_schema('test_one_blob');
2297
    $schema['test_two_blobs'] = drupal_get_schema('test_two_blobs');
2298
    $schema['test_task'] = drupal_get_schema('test_task');
2299

    
2300
    $this->installTables($schema);
2301

    
2302
    $this->addSampleData();
2303
  }
2304

    
2305
  /**
2306
   * Test that we can join on a query.
2307
   */
2308
  function testJoinSubquery() {
2309
    $acct = $this->drupalCreateUser(array('access content'));
2310
    $this->drupalLogin($acct);
2311

    
2312
    $query = db_select('test_task', 'tt', array('target' => 'slave'));
2313
    $query->addExpression('tt.pid + 1', 'abc');
2314
    $query->condition('priority', 1, '>');
2315
    $query->condition('priority', 100, '<');
2316

    
2317
    $subquery = db_select('test', 'tp');
2318
    $subquery->join('test_one_blob', 'tpb', 'tp.id = tpb.id');
2319
    $subquery->join('node', 'n', 'tp.id = n.nid');
2320
    $subquery->addTag('node_access');
2321
    $subquery->addMetaData('account', $acct);
2322
    $subquery->addField('tp', 'id');
2323
    $subquery->condition('age', 5, '>');
2324
    $subquery->condition('age', 500, '<');
2325

    
2326
    $query->leftJoin($subquery, 'sq', 'tt.pid = sq.id');
2327
    $query->join('test_one_blob', 'tb3', 'tt.pid = tb3.id');
2328

    
2329
    // Construct the query string.
2330
    // This is the same sequence that SelectQuery::execute() goes through.
2331
    $query->preExecute();
2332
    $query->getArguments();
2333
    $str = (string) $query;
2334

    
2335
    // Verify that the string only has one copy of condition placeholder 0.
2336
    $pos = strpos($str, 'db_condition_placeholder_0', 0);
2337
    $pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1);
2338
    $this->assertFalse($pos2, 'Condition placeholder is not repeated.');
2339
  }
2340
}
2341

    
2342
class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase {
2343

    
2344
  public static function getInfo() {
2345
    return array(
2346
      'name' => 'Pager query tests',
2347
      'description' => 'Test the pager query extender.',
2348
      'group' => 'Database',
2349
    );
2350
  }
2351

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

    
2366
    $correct_number = $limit;
2367
    $num_pages = floor($count / $limit);
2368

    
2369
    // If there is no remainder from rounding, subtract 1 since we index from 0.
2370
    if (!($num_pages * $limit < $count)) {
2371
      $num_pages--;
2372
    }
2373

    
2374
    for ($page = 0; $page <= $num_pages; ++$page) {
2375
      $this->drupalGet('database_test/pager_query_even/' . $limit, array('query' => array('page' => $page)));
2376
      $data = json_decode($this->drupalGetContent());
2377

    
2378
      if ($page == $num_pages) {
2379
        $correct_number = $count - ($limit * $page);
2380
      }
2381

    
2382
      $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
2383
    }
2384
  }
2385

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

    
2400
    $correct_number = $limit;
2401
    $num_pages = floor($count / $limit);
2402

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

    
2408
    for ($page = 0; $page <= $num_pages; ++$page) {
2409
      $this->drupalGet('database_test/pager_query_odd/' . $limit, array('query' => array('page' => $page)));
2410
      $data = json_decode($this->drupalGetContent());
2411

    
2412
      if ($page == $num_pages) {
2413
        $correct_number = $count - ($limit * $page);
2414
      }
2415

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

    
2420
  /**
2421
   * Confirm that a pager query with inner pager query returns valid results.
2422
   *
2423
   * This is a regression test for #467984.
2424
   */
2425
  function testInnerPagerQuery() {
2426
    $query = db_select('test', 't')->extend('PagerDefault');
2427
    $query
2428
      ->fields('t', array('age'))
2429
      ->orderBy('age')
2430
      ->limit(5);
2431

    
2432
    $outer_query = db_select($query);
2433
    $outer_query->addField('subquery', 'age');
2434

    
2435
    $ages = $outer_query
2436
      ->execute()
2437
      ->fetchCol();
2438
    $this->assertEqual($ages, array(25, 26, 27, 28), 'Inner pager query returned the correct ages.');
2439
  }
2440

    
2441
  /**
2442
   * Confirm that a paging query with a having expression returns valid results.
2443
   *
2444
   * This is a regression test for #467984.
2445
   */
2446
  function testHavingPagerQuery() {
2447
    $query = db_select('test', 't')->extend('PagerDefault');
2448
    $query
2449
      ->fields('t', array('name'))
2450
      ->orderBy('name')
2451
      ->groupBy('name')
2452
      ->having('MAX(age) > :count', array(':count' => 26))
2453
      ->limit(5);
2454

    
2455
    $ages = $query
2456
      ->execute()
2457
      ->fetchCol();
2458
    $this->assertEqual($ages, array('George', 'Ringo'), 'Pager query with having expression returned the correct ages.');
2459
  }
2460

    
2461
  /**
2462
   * Confirm that every pager gets a valid non-overlaping element ID.
2463
   */
2464
  function testElementNumbers() {
2465
    $_GET['page'] = '3, 2, 1, 0';
2466

    
2467
    $name = db_select('test', 't')->extend('PagerDefault')
2468
      ->element(2)
2469
      ->fields('t', array('name'))
2470
      ->orderBy('age')
2471
      ->limit(1)
2472
      ->execute()
2473
      ->fetchField();
2474
    $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.');
2475

    
2476
    // Setting an element smaller than the previous one
2477
    // should not overwrite the pager $maxElement with a smaller value.
2478
    $name = db_select('test', 't')->extend('PagerDefault')
2479
      ->element(1)
2480
      ->fields('t', array('name'))
2481
      ->orderBy('age')
2482
      ->limit(1)
2483
      ->execute()
2484
      ->fetchField();
2485
    $this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.');
2486

    
2487
    $name = db_select('test', 't')->extend('PagerDefault')
2488
      ->fields('t', array('name'))
2489
      ->orderBy('age')
2490
      ->limit(1)
2491
      ->execute()
2492
      ->fetchField();
2493
    $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.');
2494

    
2495
    unset($_GET['page']);
2496
  }
2497
}
2498

    
2499

    
2500
class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase {
2501

    
2502
  public static function getInfo() {
2503
    return array(
2504
      'name' => 'Tablesort query tests',
2505
      'description' => 'Test the tablesort query extender.',
2506
      'group' => 'Database',
2507
    );
2508
  }
2509

    
2510
  /**
2511
   * Confirm that a tablesort query returns the correct results.
2512
   *
2513
   * Note that we have to make an HTTP request to a test page handler
2514
   * because the pager depends on GET parameters.
2515
   */
2516
  function testTableSortQuery() {
2517
    $sorts = array(
2518
      array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
2519
      array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
2520
      array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
2521
      array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
2522
      // more elements here
2523

    
2524
    );
2525

    
2526
    foreach ($sorts as $sort) {
2527
      $this->drupalGet('database_test/tablesort/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
2528
      $data = json_decode($this->drupalGetContent());
2529

    
2530
      $first = array_shift($data->tasks);
2531
      $last = array_pop($data->tasks);
2532

    
2533
      $this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.');
2534
      $this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.');
2535
    }
2536
  }
2537

    
2538
  /**
2539
   * Confirm that if a tablesort's orderByHeader is called before another orderBy, that the header happens first.
2540
   *
2541
   */
2542
  function testTableSortQueryFirst() {
2543
    $sorts = array(
2544
      array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
2545
      array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
2546
      array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
2547
      array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
2548
      // more elements here
2549

    
2550
    );
2551

    
2552
    foreach ($sorts as $sort) {
2553
      $this->drupalGet('database_test/tablesort_first/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
2554
      $data = json_decode($this->drupalGetContent());
2555

    
2556
      $first = array_shift($data->tasks);
2557
      $last = array_pop($data->tasks);
2558

    
2559
      $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'])));
2560
      $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'])));
2561
    }
2562
  }
2563

    
2564
  /**
2565
   * Confirm that if a sort is not set in a tableselect form there is no error thrown when using the default.
2566
   */
2567
  function testTableSortDefaultSort() {
2568
    $this->drupalGet('database_test/tablesort_default_sort');
2569
    // Any PHP errors or notices thrown would trigger a simpletest exception, so
2570
    // no additional assertions are needed.
2571
  }
2572
}
2573

    
2574
/**
2575
 * Select tagging tests.
2576
 *
2577
 * Tags are a way to flag queries for alter hooks so they know
2578
 * what type of query it is, such as "node_access".
2579
 */
2580
class DatabaseTaggingTestCase extends DatabaseTestCase {
2581

    
2582
  public static function getInfo() {
2583
    return array(
2584
      'name' => 'Query tagging tests',
2585
      'description' => 'Test the tagging capabilities of the Select builder.',
2586
      'group' => 'Database',
2587
    );
2588
  }
2589

    
2590
  /**
2591
   * Confirm that a query has a "tag" added to it.
2592
   */
2593
  function testHasTag() {
2594
    $query = db_select('test');
2595
    $query->addField('test', 'name');
2596
    $query->addField('test', 'age', 'age');
2597

    
2598
    $query->addTag('test');
2599

    
2600
    $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
2601
    $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
2602
  }
2603

    
2604
  /**
2605
   * Test query tagging "has all of these tags" functionality.
2606
   */
2607
  function testHasAllTags() {
2608
    $query = db_select('test');
2609
    $query->addField('test', 'name');
2610
    $query->addField('test', 'age', 'age');
2611

    
2612
    $query->addTag('test');
2613
    $query->addTag('other');
2614

    
2615
    $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
2616
    $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
2617
  }
2618

    
2619
  /**
2620
   * Test query tagging "has at least one of these tags" functionality.
2621
   */
2622
  function testHasAnyTag() {
2623
    $query = db_select('test');
2624
    $query->addField('test', 'name');
2625
    $query->addField('test', 'age', 'age');
2626

    
2627
    $query->addTag('test');
2628

    
2629
    $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
2630
    $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
2631
  }
2632

    
2633
  /**
2634
   * Confirm that an extended query has a "tag" added to it.
2635
   */
2636
  function testExtenderHasTag() {
2637
    $query = db_select('test')
2638
      ->extend('SelectQueryExtender');
2639
    $query->addField('test', 'name');
2640
    $query->addField('test', 'age', 'age');
2641

    
2642
    $query->addTag('test');
2643

    
2644
    $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
2645
    $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
2646
  }
2647

    
2648
  /**
2649
   * Test extended query tagging "has all of these tags" functionality.
2650
   */
2651
  function testExtenderHasAllTags() {
2652
    $query = db_select('test')
2653
      ->extend('SelectQueryExtender');
2654
    $query->addField('test', 'name');
2655
    $query->addField('test', 'age', 'age');
2656

    
2657
    $query->addTag('test');
2658
    $query->addTag('other');
2659

    
2660
    $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
2661
    $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
2662
  }
2663

    
2664
  /**
2665
   * Test extended query tagging "has at least one of these tags" functionality.
2666
   */
2667
  function testExtenderHasAnyTag() {
2668
    $query = db_select('test')
2669
      ->extend('SelectQueryExtender');
2670
    $query->addField('test', 'name');
2671
    $query->addField('test', 'age', 'age');
2672

    
2673
    $query->addTag('test');
2674

    
2675
    $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
2676
    $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
2677
  }
2678

    
2679
  /**
2680
   * Test that we can attach meta data to a query object.
2681
   *
2682
   * This is how we pass additional context to alter hooks.
2683
   */
2684
  function testMetaData() {
2685
    $query = db_select('test');
2686
    $query->addField('test', 'name');
2687
    $query->addField('test', 'age', 'age');
2688

    
2689
    $data = array(
2690
      'a' => 'A',
2691
      'b' => 'B',
2692
    );
2693

    
2694
    $query->addMetaData('test', $data);
2695

    
2696
    $return = $query->getMetaData('test');
2697
    $this->assertEqual($data, $return, 'Corect metadata returned.');
2698

    
2699
    $return = $query->getMetaData('nothere');
2700
    $this->assertNull($return, 'Non-existent key returned NULL.');
2701
  }
2702
}
2703

    
2704
/**
2705
 * Select alter tests.
2706
 *
2707
 * @see database_test_query_alter()
2708
 */
2709
class DatabaseAlterTestCase extends DatabaseTestCase {
2710

    
2711
  public static function getInfo() {
2712
    return array(
2713
      'name' => 'Query altering tests',
2714
      'description' => 'Test the hook_query_alter capabilities of the Select builder.',
2715
      'group' => 'Database',
2716
    );
2717
  }
2718

    
2719
  /**
2720
   * Test that we can do basic alters.
2721
   */
2722
  function testSimpleAlter() {
2723
    $query = db_select('test');
2724
    $query->addField('test', 'name');
2725
    $query->addField('test', 'age', 'age');
2726
    $query->addTag('database_test_alter_add_range');
2727

    
2728
    $result = $query->execute();
2729

    
2730
    $num_records = 0;
2731
    foreach ($result as $record) {
2732
      $num_records++;
2733
    }
2734

    
2735
    $this->assertEqual($num_records, 2, 'Returned the correct number of rows.');
2736
  }
2737

    
2738
  /**
2739
   * Test that we can alter the joins on a query.
2740
   */
2741
  function testAlterWithJoin() {
2742
    $query = db_select('test_task');
2743
    $tid_field = $query->addField('test_task', 'tid');
2744
    $task_field = $query->addField('test_task', 'task');
2745
    $query->orderBy($task_field);
2746
    $query->addTag('database_test_alter_add_join');
2747

    
2748
    $result = $query->execute();
2749

    
2750
    $records = $result->fetchAll();
2751

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

    
2754
    $this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.');
2755
    $this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.');
2756
    $this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.');
2757
    $this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.');
2758
    $this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.');
2759
    $this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.');
2760
  }
2761

    
2762
  /**
2763
   * Test that we can alter a query's conditionals.
2764
   */
2765
  function testAlterChangeConditional() {
2766
    $query = db_select('test_task');
2767
    $tid_field = $query->addField('test_task', 'tid');
2768
    $pid_field = $query->addField('test_task', 'pid');
2769
    $task_field = $query->addField('test_task', 'task');
2770
    $people_alias = $query->join('test', 'people', "test_task.pid = people.id");
2771
    $name_field = $query->addField($people_alias, 'name', 'name');
2772
    $query->condition('test_task.tid', '1');
2773
    $query->orderBy($tid_field);
2774
    $query->addTag('database_test_alter_change_conditional');
2775

    
2776
    $result = $query->execute();
2777

    
2778
    $records = $result->fetchAll();
2779

    
2780
    $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
2781
    $this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.');
2782
    $this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.');
2783
    $this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.');
2784
    $this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.');
2785
  }
2786

    
2787
  /**
2788
   * Test that we can alter the fields of a query.
2789
   */
2790
  function testAlterChangeFields() {
2791
    $query = db_select('test');
2792
    $name_field = $query->addField('test', 'name');
2793
    $age_field = $query->addField('test', 'age', 'age');
2794
    $query->orderBy('name');
2795
    $query->addTag('database_test_alter_change_fields');
2796

    
2797
    $record = $query->execute()->fetch();
2798
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2799
    $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.');
2800
  }
2801

    
2802
  /**
2803
   * Test that we can alter expressions in the query.
2804
   */
2805
  function testAlterExpression() {
2806
    $query = db_select('test');
2807
    $name_field = $query->addField('test', 'name');
2808
    $age_field = $query->addExpression("age*2", 'double_age');
2809
    $query->condition('age', 27);
2810
    $query->addTag('database_test_alter_change_expressions');
2811
    $result = $query->execute();
2812

    
2813
    // Ensure that we got the right record.
2814
    $record = $result->fetch();
2815

    
2816
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2817
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2818
  }
2819

    
2820
  /**
2821
   * Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter().
2822
   */
2823
  function testAlterRemoveRange() {
2824
    $query = db_select('test');
2825
    $query->addField('test', 'name');
2826
    $query->addField('test', 'age', 'age');
2827
    $query->range(0, 2);
2828
    $query->addTag('database_test_alter_remove_range');
2829

    
2830
    $num_records = count($query->execute()->fetchAll());
2831

    
2832
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
2833
  }
2834

    
2835
  /**
2836
   * Test that we can do basic alters on subqueries.
2837
   */
2838
  function testSimpleAlterSubquery() {
2839
    // Create a sub-query with an alter tag.
2840
    $subquery = db_select('test', 'p');
2841
    $subquery->addField('p', 'name');
2842
    $subquery->addField('p', 'id');
2843
    // Pick out George.
2844
    $subquery->condition('age', 27);
2845
    $subquery->addExpression("age*2", 'double_age');
2846
    // This query alter should change it to age * 3.
2847
    $subquery->addTag('database_test_alter_change_expressions');
2848

    
2849
    // Create a main query and join to sub-query.
2850
    $query = db_select('test_task', 'tt');
2851
    $query->join($subquery, 'pq', 'pq.id = tt.pid');
2852
    $age_field = $query->addField('pq', 'double_age');
2853
    $name_field = $query->addField('pq', 'name');
2854

    
2855
    $record = $query->execute()->fetch();
2856
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2857
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2858
  }
2859
}
2860

    
2861
/**
2862
 * Regression tests.
2863
 */
2864
class DatabaseRegressionTestCase extends DatabaseTestCase {
2865

    
2866
  public static function getInfo() {
2867
    return array(
2868
      'name' => 'Regression tests',
2869
      'description' => 'Regression tests cases for the database layer.',
2870
      'group' => 'Database',
2871
    );
2872
  }
2873

    
2874
  /**
2875
   * Regression test for #310447.
2876
   *
2877
   * Tries to insert non-ascii UTF-8 data in a database column and checks
2878
   * if its stored properly.
2879
   */
2880
  function testRegression_310447() {
2881
    // That's a 255 character UTF-8 string.
2882
    $name = str_repeat("é", 255);
2883
    db_insert('test')
2884
      ->fields(array(
2885
        'name' => $name,
2886
        'age' => 20,
2887
        'job' => 'Dancer',
2888
      ))->execute();
2889

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

    
2894
  /**
2895
   * Test the db_table_exists() function.
2896
   */
2897
  function testDBTableExists() {
2898
    $this->assertIdentical(TRUE, db_table_exists('node'), 'Returns true for existent table.');
2899
    $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.');
2900
  }
2901

    
2902
  /**
2903
   * Test the db_field_exists() function.
2904
   */
2905
  function testDBFieldExists() {
2906
    $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), 'Returns true for existent column.');
2907
    $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), 'Returns false for nonexistent column.');
2908
  }
2909

    
2910
  /**
2911
   * Test the db_index_exists() function.
2912
   */
2913
  function testDBIndexExists() {
2914
    $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.');
2915
    $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.');
2916
  }
2917
}
2918

    
2919
/**
2920
 * Query logging tests.
2921
 */
2922
class DatabaseLoggingTestCase extends DatabaseTestCase {
2923

    
2924
  public static function getInfo() {
2925
    return array(
2926
      'name' => 'Query logging',
2927
      'description' => 'Test the query logging facility.',
2928
      'group' => 'Database',
2929
    );
2930
  }
2931

    
2932
  /**
2933
   * Test that we can log the existence of a query.
2934
   */
2935
  function testEnableLogging() {
2936
    $log = Database::startLog('testing');
2937

    
2938
    db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25))->fetchCol();
2939
    db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchCol();
2940

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

    
2944
    $queries = Database::getLog('testing', 'default');
2945

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

    
2948
    foreach ($queries as $query) {
2949
      $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.');
2950
    }
2951
  }
2952

    
2953
  /**
2954
   * Test that we can run two logs in parallel.
2955
   */
2956
  function testEnableMultiLogging() {
2957
    Database::startLog('testing1');
2958

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

    
2961
    Database::startLog('testing2');
2962

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

    
2965
    $queries1 = Database::getLog('testing1');
2966
    $queries2 = Database::getLog('testing2');
2967

    
2968
    $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.');
2969
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.');
2970
  }
2971

    
2972
  /**
2973
   * Test that we can log queries against multiple targets on the same connection.
2974
   */
2975
  function testEnableTargetLogging() {
2976
    // Clone the master credentials to a slave connection and to another fake
2977
    // connection.
2978
    $connection_info = Database::getConnectionInfo('default');
2979
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
2980

    
2981
    Database::startLog('testing1');
2982

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

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

    
2987
    $queries1 = Database::getLog('testing1');
2988

    
2989
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
2990
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
2991
    $this->assertEqual($queries1[1]['target'], 'slave', 'Second query used slave target.');
2992
  }
2993

    
2994
  /**
2995
   * Test that logs to separate targets collapse to the same connection properly.
2996
   *
2997
   * This test is identical to the one above, except that it doesn't create
2998
   * a fake target so the query should fall back to running on the default
2999
   * target.
3000
   */
3001
  function testEnableTargetLoggingNoTarget() {
3002
    Database::startLog('testing1');
3003

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

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

    
3013
    $queries1 = Database::getLog('testing1');
3014

    
3015
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
3016
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
3017
    $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.');
3018
  }
3019

    
3020
  /**
3021
   * Test that we can log queries separately on different connections.
3022
   */
3023
  function testEnableMultiConnectionLogging() {
3024
    // Clone the master credentials to a fake connection.
3025
    // That both connections point to the same physical database is irrelevant.
3026
    $connection_info = Database::getConnectionInfo('default');
3027
    Database::addConnectionInfo('test2', 'default', $connection_info['default']);
3028

    
3029
    Database::startLog('testing1');
3030
    Database::startLog('testing1', 'test2');
3031

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

    
3034
    $old_key = db_set_active('test2');
3035

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

    
3038
    db_set_active($old_key);
3039

    
3040
    $queries1 = Database::getLog('testing1');
3041
    $queries2 = Database::getLog('testing1', 'test2');
3042

    
3043
    $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.');
3044
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
3045
  }
3046
}
3047

    
3048
/**
3049
 * Query serialization tests.
3050
 */
3051
class DatabaseSerializeQueryTestCase extends DatabaseTestCase {
3052
  public static function getInfo() {
3053
    return array(
3054
      'name' => 'Serialize query',
3055
      'description' => 'Test serializing and unserializing a query.',
3056
      'group' => 'Database',
3057
    );
3058
  }
3059

    
3060
  /**
3061
   * Confirm that a query can be serialized and unserialized.
3062
   */
3063
  function testSerializeQuery() {
3064
    $query = db_select('test');
3065
    $query->addField('test', 'age');
3066
    $query->condition('name', 'Ringo');
3067
    // If this doesn't work, it will throw an exception, so no need for an
3068
    // assertion.
3069
    $query = unserialize(serialize($query));
3070
    $results = $query->execute()->fetchCol();
3071
    $this->assertEqual($results[0], 28, 'Query properly executed after unserialization.');
3072
  }
3073
}
3074

    
3075
/**
3076
 * Range query tests.
3077
 */
3078
class DatabaseRangeQueryTestCase extends DrupalWebTestCase {
3079
  public static function getInfo() {
3080
    return array(
3081
      'name' => 'Range query test',
3082
      'description' => 'Test the Range query functionality.',
3083
      'group' => 'Database',
3084
    );
3085
  }
3086

    
3087
  function setUp() {
3088
    parent::setUp('database_test');
3089
  }
3090

    
3091
  /**
3092
   * Confirm that range query work and return correct result.
3093
   */
3094
  function testRangeQuery() {
3095
    // Test if return correct number of rows.
3096
    $range_rows = db_query_range("SELECT name FROM {system} ORDER BY name", 2, 3)->fetchAll();
3097
    $this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.');
3098

    
3099
    // Test if return target data.
3100
    $raw_rows = db_query('SELECT name FROM {system} ORDER BY name')->fetchAll();
3101
    $raw_rows = array_slice($raw_rows, 2, 3);
3102
    $this->assertEqual($range_rows, $raw_rows, 'Range query work and return target data.');
3103
  }
3104
}
3105

    
3106
/**
3107
 * Temporary query tests.
3108
 */
3109
class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase {
3110
  public static function getInfo() {
3111
    return array(
3112
      'name' => 'Temporary query test',
3113
      'description' => 'Test the temporary query functionality.',
3114
      'group' => 'Database',
3115
    );
3116
  }
3117

    
3118
  function setUp() {
3119
    parent::setUp('database_test');
3120
  }
3121

    
3122
  /**
3123
   * Return the number of rows of a table.
3124
   */
3125
  function countTableRows($table_name) {
3126
    return db_select($table_name)->countQuery()->execute()->fetchField();
3127
  }
3128

    
3129
  /**
3130
   * Confirm that temporary tables work and are limited to one request.
3131
   */
3132
  function testTemporaryQuery() {
3133
    $this->drupalGet('database_test/db_query_temporary');
3134
    $data = json_decode($this->drupalGetContent());
3135
    if ($data) {
3136
      $this->assertEqual($this->countTableRows("system"), $data->row_count, 'The temporary table contains the correct amount of rows.');
3137
      $this->assertFalse(db_table_exists($data->table_name), 'The temporary table is, indeed, temporary.');
3138
    }
3139
    else {
3140
      $this->fail("The creation of the temporary table failed.");
3141
    }
3142

    
3143
    // Now try to run two db_query_temporary() in the same request.
3144
    $table_name_system = db_query_temporary('SELECT status FROM {system}', array());
3145
    $table_name_users = db_query_temporary('SELECT uid FROM {users}', array());
3146

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

    
3150
    // Check that leading whitespace and comments do not cause problems
3151
    // in the modified query.
3152
    $sql = "
3153
      -- Let's select some rows into a temporary table
3154
      SELECT name FROM {test}
3155
    ";
3156
    $table_name_test = db_query_temporary($sql, array());
3157
    $this->assertEqual($this->countTableRows($table_name_test), $this->countTableRows('test'), 'Leading white space and comments do not interfere with temporary table creation.');
3158
  }
3159
}
3160

    
3161
/**
3162
 * Test how the current database driver interprets the SQL syntax.
3163
 *
3164
 * In order to ensure consistent SQL handling throughout Drupal
3165
 * across multiple kinds of database systems, we test that the
3166
 * database system interprets SQL syntax in an expected fashion.
3167
 */
3168
class DatabaseBasicSyntaxTestCase extends DatabaseTestCase {
3169
  public static function getInfo() {
3170
    return array(
3171
      'name' => 'Basic SQL syntax tests',
3172
      'description' => 'Test SQL syntax interpretation.',
3173
      'group' => 'Database',
3174
    );
3175
  }
3176

    
3177
  function setUp() {
3178
    parent::setUp('database_test');
3179
  }
3180

    
3181
  /**
3182
   * Test for string concatenation.
3183
   */
3184
  function testBasicConcat() {
3185
    $result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', array(
3186
      ':a1' => 'This',
3187
      ':a2' => ' ',
3188
      ':a3' => 'is',
3189
      ':a4' => ' a ',
3190
      ':a5' => 'test.',
3191
    ));
3192
    $this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.');
3193
  }
3194

    
3195
  /**
3196
   * Test for string concatenation with field values.
3197
   */
3198
  function testFieldConcat() {
3199
    $result = db_query('SELECT CONCAT(:a1, CONCAT(name, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', array(
3200
      ':a1' => 'The age of ',
3201
      ':a2' => ' is ',
3202
      ':a3' => '.',
3203
      ':age' => 25,
3204
    ));
3205
    $this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.');
3206
  }
3207

    
3208
  /**
3209
   * Test escaping of LIKE wildcards.
3210
   */
3211
  function testLikeEscape() {
3212
    db_insert('test')
3213
      ->fields(array(
3214
        'name' => 'Ring_',
3215
      ))
3216
      ->execute();
3217

    
3218
    // Match both "Ringo" and "Ring_".
3219
    $num_matches = db_select('test', 't')
3220
      ->condition('name', 'Ring_', 'LIKE')
3221
      ->countQuery()
3222
      ->execute()
3223
      ->fetchField();
3224
    $this->assertIdentical($num_matches, '2', 'Found 2 records.');
3225
    // Match only "Ring_" using a LIKE expression with no wildcards.
3226
    $num_matches = db_select('test', 't')
3227
      ->condition('name', db_like('Ring_'), 'LIKE')
3228
      ->countQuery()
3229
      ->execute()
3230
      ->fetchField();
3231
    $this->assertIdentical($num_matches, '1', 'Found 1 record.');
3232
  }
3233

    
3234
  /**
3235
   * Test LIKE query containing a backslash.
3236
   */
3237
  function testLikeBackslash() {
3238
    db_insert('test')
3239
      ->fields(array('name'))
3240
      ->values(array(
3241
        'name' => 'abcde\f',
3242
      ))
3243
      ->values(array(
3244
        'name' => 'abc%\_',
3245
      ))
3246
      ->execute();
3247

    
3248
    // Match both rows using a LIKE expression with two wildcards and a verbatim
3249
    // backslash.
3250
    $num_matches = db_select('test', 't')
3251
      ->condition('name', 'abc%\\\\_', 'LIKE')
3252
      ->countQuery()
3253
      ->execute()
3254
      ->fetchField();
3255
    $this->assertIdentical($num_matches, '2', 'Found 2 records.');
3256
    // Match only the former using a LIKE expression with no wildcards.
3257
    $num_matches = db_select('test', 't')
3258
      ->condition('name', db_like('abc%\_'), 'LIKE')
3259
      ->countQuery()
3260
      ->execute()
3261
      ->fetchField();
3262
    $this->assertIdentical($num_matches, '1', 'Found 1 record.');
3263
  }
3264
}
3265

    
3266
/**
3267
 * Test case sensitivity handling.
3268
 */
3269
class DatabaseCaseSensitivityTestCase extends DatabaseTestCase {
3270
  public static function getInfo() {
3271
    return array(
3272
      'name' => 'Case sensitivity',
3273
      'description' => 'Test handling case sensitive collation.',
3274
      'group' => 'Database',
3275
    );
3276
  }
3277

    
3278
  /**
3279
   * Test BINARY collation in MySQL.
3280
   */
3281
  function testCaseSensitiveInsert() {
3282
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3283

    
3284
    $john = db_insert('test')
3285
      ->fields(array(
3286
        'name' => 'john', // <- A record already exists with name 'John'.
3287
        'age' => 2,
3288
        'job' => 'Baby',
3289
      ))
3290
      ->execute();
3291

    
3292
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3293
    $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
3294
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
3295
    $this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.');
3296
  }
3297
}
3298

    
3299
/**
3300
 * Test invalid data handling.
3301
 */
3302
class DatabaseInvalidDataTestCase extends DatabaseTestCase {
3303
  public static function getInfo() {
3304
    return array(
3305
      'name' => 'Invalid data',
3306
      'description' => 'Test handling of some invalid data.',
3307
      'group' => 'Database',
3308
    );
3309
  }
3310

    
3311
  function setUp() {
3312
    parent::setUp('database_test');
3313
  }
3314

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

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

    
3360
      // Ensure the other values were not inserted.
3361
      $record = db_select('test')
3362
        ->fields('test', array('name', 'age'))
3363
        ->condition('age', array(17, 75), 'IN')
3364
        ->execute()->fetchObject();
3365

    
3366
      $this->assertFalse($record, 'The rest of the insert aborted as expected.');
3367
    }
3368
  }
3369

    
3370
}
3371

    
3372
/**
3373
 * Drupal-specific SQL syntax tests.
3374
 */
3375
class DatabaseQueryTestCase extends DatabaseTestCase {
3376
  public static function getInfo() {
3377
    return array(
3378
      'name' => 'Custom query syntax tests',
3379
      'description' => 'Test Drupal\'s extended prepared statement syntax..',
3380
      'group' => 'Database',
3381
    );
3382
  }
3383

    
3384
  function setUp() {
3385
    parent::setUp('database_test');
3386
  }
3387

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

    
3394
    $this->assertEqual(count($names), 3, 'Correct number of names returned');
3395
  }
3396

    
3397
  /**
3398
   * Test SQL injection via database query array arguments.
3399
   */
3400
  public function testArrayArgumentsSQLInjection() {
3401
    // Attempt SQL injection and verify that it does not work.
3402
    $condition = array(
3403
      "1 ;INSERT INTO {test} (name) VALUES ('test12345678'); -- " => '',
3404
      '1' => '',
3405
    );
3406
    try {
3407
      db_query("SELECT * FROM {test} WHERE name = :name", array(':name' => $condition))->fetchObject();
3408
      $this->fail('SQL injection attempt via array arguments should result in a PDOException.');
3409
    }
3410
    catch (PDOException $e) {
3411
      $this->pass('SQL injection attempt via array arguments should result in a PDOException.');
3412
    }
3413

    
3414
    // Test that the insert query that was used in the SQL injection attempt did
3415
    // not result in a row being inserted in the database.
3416
    $result = db_select('test')
3417
      ->condition('name', 'test12345678')
3418
      ->countQuery()
3419
      ->execute()
3420
      ->fetchField();
3421
    $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
3422
  }
3423

    
3424
}
3425

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

    
3448
  public static function getInfo() {
3449
    return array(
3450
      'name' => 'Transaction tests',
3451
      'description' => 'Test the transaction abstraction system.',
3452
      'group' => 'Database',
3453
    );
3454
  }
3455

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

    
3484
    // Insert a single row into the testing table.
3485
    db_insert('test')
3486
      ->fields(array(
3487
        'name' => 'David' . $suffix,
3488
        'age' => '24',
3489
      ))
3490
      ->execute();
3491

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

    
3494
    // We're already in a transaction, but we call ->transactionInnerLayer
3495
    // to nest another transaction inside the current one.
3496
    $this->transactionInnerLayer($suffix, $rollback, $ddl_statement);
3497

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

    
3500
    if ($rollback) {
3501
      // Roll back the transaction, if requested.
3502
      // This rollback should propagate to the last savepoint.
3503
      $txn->rollback();
3504
      $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
3505
    }
3506
  }
3507

    
3508
  /**
3509
   * Helper method for transaction unit tests. This "inner layer" transaction
3510
   * is either used alone or nested inside of the "outer layer" transaction.
3511
   *
3512
   * @param $suffix
3513
   *   Suffix to add to field values to differentiate tests.
3514
   * @param $rollback
3515
   *   Whether or not to try rolling back the transaction when we're done.
3516
   * @param $ddl_statement
3517
   *   Whether to execute a DDL statement during the transaction.
3518
   */
3519
  protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
3520
    $connection = Database::getConnection();
3521

    
3522
    $depth = $connection->transactionDepth();
3523
    // Start a transaction. If we're being called from ->transactionOuterLayer,
3524
    // then we're already in a transaction. Normally, that would make starting
3525
    // a transaction here dangerous, but the database API handles this problem
3526
    // for us by tracking the nesting and avoiding the danger.
3527
    $txn = db_transaction();
3528

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

    
3532
    // Insert a single row into the testing table.
3533
    db_insert('test')
3534
      ->fields(array(
3535
        'name' => 'Daniel' . $suffix,
3536
        'age' => '19',
3537
      ))
3538
      ->execute();
3539

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

    
3542
    if ($ddl_statement) {
3543
      $table = array(
3544
        'fields' => array(
3545
          'id' => array(
3546
            'type' => 'serial',
3547
            'unsigned' => TRUE,
3548
            'not null' => TRUE,
3549
          ),
3550
        ),
3551
        'primary key' => array('id'),
3552
      );
3553
      db_create_table('database_test_1', $table);
3554

    
3555
      $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
3556
    }
3557

    
3558
    if ($rollback) {
3559
      // Roll back the transaction, if requested.
3560
      // This rollback should propagate to the last savepoint.
3561
      $txn->rollback();
3562
      $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().');
3563
    }
3564
  }
3565

    
3566
  /**
3567
   * Test transaction rollback on a database that supports transactions.
3568
   *
3569
   * If the active connection does not support transactions, this test does nothing.
3570
   */
3571
  function testTransactionRollBackSupported() {
3572
    // This test won't work right if transactions are not supported.
3573
    if (!Database::getConnection()->supportsTransactions()) {
3574
      return;
3575
    }
3576
    try {
3577
      // Create two nested transactions. Roll back from the inner one.
3578
      $this->transactionOuterLayer('B', TRUE);
3579

    
3580
      // Neither of the rows we inserted in the two transaction layers
3581
      // should be present in the tables post-rollback.
3582
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
3583
      $this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.');
3584
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
3585
      $this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.');
3586
    }
3587
    catch (Exception $e) {
3588
      $this->fail($e->getMessage());
3589
    }
3590
  }
3591

    
3592
  /**
3593
   * Test transaction rollback on a database that does not support transactions.
3594
   *
3595
   * If the active driver supports transactions, this test does nothing.
3596
   */
3597
  function testTransactionRollBackNotSupported() {
3598
    // This test won't work right if transactions are supported.
3599
    if (Database::getConnection()->supportsTransactions()) {
3600
      return;
3601
    }
3602
    try {
3603
      // Create two nested transactions. Attempt to roll back from the inner one.
3604
      $this->transactionOuterLayer('B', TRUE);
3605

    
3606
      // Because our current database claims to not support transactions,
3607
      // the inserted rows should be present despite the attempt to roll back.
3608
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField();
3609
      $this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.');
3610
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField();
3611
      $this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.');
3612
    }
3613
    catch (Exception $e) {
3614
      $this->fail($e->getMessage());
3615
    }
3616
  }
3617

    
3618
  /**
3619
   * Test committed transaction.
3620
   *
3621
   * The behavior of this test should be identical for connections that support
3622
   * transactions and those that do not.
3623
   */
3624
  function testCommittedTransaction() {
3625
    try {
3626
      // Create two nested transactions. The changes should be committed.
3627
      $this->transactionOuterLayer('A');
3628

    
3629
      // Because we committed, both of the inserted rows should be present.
3630
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField();
3631
      $this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.');
3632
      $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField();
3633
      $this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.');
3634
    }
3635
    catch (Exception $e) {
3636
      $this->fail($e->getMessage());
3637
    }
3638
  }
3639

    
3640
  /**
3641
   * Test the compatibility of transactions with DDL statements.
3642
   */
3643
  function testTransactionWithDdlStatement() {
3644
    // First, test that a commit works normally, even with DDL statements.
3645
    $transaction = db_transaction();
3646
    $this->insertRow('row');
3647
    $this->executeDDLStatement();
3648
    unset($transaction);
3649
    $this->assertRowPresent('row');
3650

    
3651
    // Even in different order.
3652
    $this->cleanUp();
3653
    $transaction = db_transaction();
3654
    $this->executeDDLStatement();
3655
    $this->insertRow('row');
3656
    unset($transaction);
3657
    $this->assertRowPresent('row');
3658

    
3659
    // Even with stacking.
3660
    $this->cleanUp();
3661
    $transaction = db_transaction();
3662
    $transaction2 = db_transaction();
3663
    $this->executeDDLStatement();
3664
    unset($transaction2);
3665
    $transaction3 = db_transaction();
3666
    $this->insertRow('row');
3667
    unset($transaction3);
3668
    unset($transaction);
3669
    $this->assertRowPresent('row');
3670

    
3671
    // A transaction after a DDL statement should still work the same.
3672
    $this->cleanUp();
3673
    $transaction = db_transaction();
3674
    $transaction2 = db_transaction();
3675
    $this->executeDDLStatement();
3676
    unset($transaction2);
3677
    $transaction3 = db_transaction();
3678
    $this->insertRow('row');
3679
    $transaction3->rollback();
3680
    unset($transaction3);
3681
    unset($transaction);
3682
    $this->assertRowAbsent('row');
3683

    
3684
    // The behavior of a rollback depends on the type of database server.
3685
    if (Database::getConnection()->supportsTransactionalDDL()) {
3686
      // For database servers that support transactional DDL, a rollback
3687
      // of a transaction including DDL statements should be possible.
3688
      $this->cleanUp();
3689
      $transaction = db_transaction();
3690
      $this->insertRow('row');
3691
      $this->executeDDLStatement();
3692
      $transaction->rollback();
3693
      unset($transaction);
3694
      $this->assertRowAbsent('row');
3695

    
3696
      // Including 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
      $transaction->rollback();
3706
      unset($transaction);
3707
      $this->assertRowAbsent('row');
3708
    }
3709
    else {
3710
      // For database servers that do not support transactional DDL,
3711
      // the DDL statement should commit the transaction stack.
3712
      $this->cleanUp();
3713
      $transaction = db_transaction();
3714
      $this->insertRow('row');
3715
      $this->executeDDLStatement();
3716
      // Rollback the outer transaction.
3717
      try {
3718
        $transaction->rollback();
3719
        unset($transaction);
3720
        // @TODO: an exception should be triggered here, but is not, because
3721
        // "ROLLBACK" fails silently in MySQL if there is no transaction active.
3722
        // $this->fail(t('Rolling back a transaction containing DDL should fail.'));
3723
      }
3724
      catch (DatabaseTransactionNoActiveException $e) {
3725
        $this->pass('Rolling back a transaction containing DDL should fail.');
3726
      }
3727
      $this->assertRowPresent('row');
3728
    }
3729
  }
3730

    
3731
  /**
3732
   * Insert a single row into the testing table.
3733
   */
3734
  protected function insertRow($name) {
3735
    db_insert('test')
3736
      ->fields(array(
3737
        'name' => $name,
3738
      ))
3739
      ->execute();
3740
  }
3741

    
3742
  /**
3743
   * Execute a DDL statement.
3744
   */
3745
  protected function executeDDLStatement() {
3746
    static $count = 0;
3747
    $table = array(
3748
      'fields' => array(
3749
        'id' => array(
3750
          'type' => 'serial',
3751
          'unsigned' => TRUE,
3752
          'not null' => TRUE,
3753
        ),
3754
      ),
3755
      'primary key' => array('id'),
3756
    );
3757
    db_create_table('database_test_' . ++$count, $table);
3758
  }
3759

    
3760
  /**
3761
   * Start over for a new test.
3762
   */
3763
  protected function cleanUp() {
3764
    db_truncate('test')
3765
      ->execute();
3766
  }
3767

    
3768
  /**
3769
   * Assert that a given row is present in the test table.
3770
   *
3771
   * @param $name
3772
   *   The name of the row.
3773
   * @param $message
3774
   *   The message to log for the assertion.
3775
   */
3776
  function assertRowPresent($name, $message = NULL) {
3777
    if (!isset($message)) {
3778
      $message = format_string('Row %name is present.', array('%name' => $name));
3779
    }
3780
    $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
3781
    return $this->assertTrue($present, $message);
3782
  }
3783

    
3784
  /**
3785
   * Assert that a given row is absent from the test table.
3786
   *
3787
   * @param $name
3788
   *   The name of the row.
3789
   * @param $message
3790
   *   The message to log for the assertion.
3791
   */
3792
  function assertRowAbsent($name, $message = NULL) {
3793
    if (!isset($message)) {
3794
      $message = format_string('Row %name is absent.', array('%name' => $name));
3795
    }
3796
    $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField();
3797
    return $this->assertFalse($present, $message);
3798
  }
3799

    
3800
  /**
3801
   * Test transaction stacking and commit / rollback.
3802
   */
3803
  function testTransactionStacking() {
3804
    // This test won't work right if transactions are not supported.
3805
    if (!Database::getConnection()->supportsTransactions()) {
3806
      return;
3807
    }
3808

    
3809
    $database = Database::getConnection();
3810

    
3811
    // Standard case: pop the inner transaction before the outer transaction.
3812
    $transaction = db_transaction();
3813
    $this->insertRow('outer');
3814
    $transaction2 = db_transaction();
3815
    $this->insertRow('inner');
3816
    // Pop the inner transaction.
3817
    unset($transaction2);
3818
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction');
3819
    // Pop the outer transaction.
3820
    unset($transaction);
3821
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction');
3822
    $this->assertRowPresent('outer');
3823
    $this->assertRowPresent('inner');
3824

    
3825
    // Pop the transaction in a different order they have been pushed.
3826
    $this->cleanUp();
3827
    $transaction = db_transaction();
3828
    $this->insertRow('outer');
3829
    $transaction2 = db_transaction();
3830
    $this->insertRow('inner');
3831
    // Pop the outer transaction, nothing should happen.
3832
    unset($transaction);
3833
    $this->insertRow('inner-after-outer-commit');
3834
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3835
    // Pop the inner transaction, the whole transaction should commit.
3836
    unset($transaction2);
3837
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3838
    $this->assertRowPresent('outer');
3839
    $this->assertRowPresent('inner');
3840
    $this->assertRowPresent('inner-after-outer-commit');
3841

    
3842
    // Rollback the inner transaction.
3843
    $this->cleanUp();
3844
    $transaction = db_transaction();
3845
    $this->insertRow('outer');
3846
    $transaction2 = db_transaction();
3847
    $this->insertRow('inner');
3848
    // Now rollback the inner transaction.
3849
    $transaction2->rollback();
3850
    unset($transaction2);
3851
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3852
    // Pop the outer transaction, it should commit.
3853
    $this->insertRow('outer-after-inner-rollback');
3854
    unset($transaction);
3855
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3856
    $this->assertRowPresent('outer');
3857
    $this->assertRowAbsent('inner');
3858
    $this->assertRowPresent('outer-after-inner-rollback');
3859

    
3860
    // Rollback the inner transaction after committing the outer one.
3861
    $this->cleanUp();
3862
    $transaction = db_transaction();
3863
    $this->insertRow('outer');
3864
    $transaction2 = db_transaction();
3865
    $this->insertRow('inner');
3866
    // Pop the outer transaction, nothing should happen.
3867
    unset($transaction);
3868
    $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction');
3869
    // Now rollback the inner transaction, it should rollback.
3870
    $transaction2->rollback();
3871
    unset($transaction2);
3872
    $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction');
3873
    $this->assertRowPresent('outer');
3874
    $this->assertRowAbsent('inner');
3875

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

    
3914

    
3915
/**
3916
 * Check the sequences API.
3917
 */
3918
class DatabaseNextIdCase extends DrupalWebTestCase {
3919
  public static function getInfo() {
3920
    return array(
3921
      'name' => 'Sequences API',
3922
      'description' => 'Test the secondary sequences API.',
3923
      'group' => 'Database',
3924
    );
3925
  }
3926

    
3927
  /**
3928
   * Test that the sequences API work.
3929
   */
3930
  function testDbNextId() {
3931
    $first = db_next_id();
3932
    $second = db_next_id();
3933
    // We can test for exact increase in here because we know there is no
3934
    // other process operating on these tables -- normally we could only
3935
    // expect $second > $first.
3936
    $this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.');
3937
    $result = db_next_id(1000);
3938
    $this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.');
3939
  }
3940
}
3941

    
3942
/**
3943
 * Tests the empty pseudo-statement class.
3944
 */
3945
class DatabaseEmptyStatementTestCase extends DrupalWebTestCase {
3946
  public static function getInfo() {
3947
    return array(
3948
      'name' => 'Empty statement',
3949
      'description' => 'Test the empty pseudo-statement class.',
3950
      'group' => 'Database',
3951
    );
3952
  }
3953

    
3954
  /**
3955
   * Test that the empty result set behaves as empty.
3956
   */
3957
  function testEmpty() {
3958
    $result = new DatabaseStatementEmpty();
3959

    
3960
    $this->assertTrue($result instanceof DatabaseStatementInterface, 'Class implements expected interface');
3961
    $this->assertNull($result->fetchObject(), 'Null result returned.');
3962
  }
3963

    
3964
  /**
3965
   * Test that the empty result set iterates safely.
3966
   */
3967
  function testEmptyIteration() {
3968
    $result = new DatabaseStatementEmpty();
3969

    
3970
    foreach ($result as $record) {
3971
      $this->fail('Iterating empty result set should not iterate.');
3972
      return;
3973
    }
3974

    
3975
    $this->pass('Iterating empty result set skipped iteration.');
3976
  }
3977

    
3978
  /**
3979
   * Test that the empty result set mass-fetches in an expected way.
3980
   */
3981
  function testEmptyFetchAll() {
3982
    $result = new DatabaseStatementEmpty();
3983

    
3984
    $this->assertEqual($result->fetchAll(), array(), 'Empty array returned from empty result set.');
3985
  }
3986
}
3987

    
3988
/**
3989
 * Tests management of database connections.
3990
 */
3991
class ConnectionUnitTest extends DrupalUnitTestCase {
3992

    
3993
  protected $key;
3994
  protected $target;
3995

    
3996
  protected $monitor;
3997
  protected $originalCount;
3998

    
3999
  public static function getInfo() {
4000
    return array(
4001
      'name' => 'Connection unit tests',
4002
      'description' => 'Tests management of database connections.',
4003
      'group' => 'Database',
4004
    );
4005
  }
4006

    
4007
  function setUp() {
4008
    parent::setUp();
4009

    
4010
    $this->key = 'default';
4011
    $this->originalTarget = 'default';
4012
    $this->target = 'DatabaseConnectionUnitTest';
4013

    
4014
    // Determine whether the database driver is MySQL. If it is not, the test
4015
    // methods will not be executed.
4016
    // @todo Make this test driver-agnostic, or find a proper way to skip it.
4017
    // @see http://drupal.org/node/1273478
4018
    $connection_info = Database::getConnectionInfo('default');
4019
    $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql';
4020
    if ($this->skipTest) {
4021
      // Insert an assertion to prevent Simpletest from interpreting the test
4022
      // as failure.
4023
      $this->pass('This test is only compatible with MySQL.');
4024
    }
4025

    
4026
    // Create an additional connection to monitor the connections being opened
4027
    // and closed in this test.
4028
    // @see TestBase::changeDatabasePrefix()
4029
    $connection_info = Database::getConnectionInfo('default');
4030
    Database::addConnectionInfo('default', 'monitor', $connection_info['default']);
4031
    global $databases;
4032
    $databases['default']['monitor'] = $connection_info['default'];
4033
    $this->monitor = Database::getConnection('monitor');
4034
  }
4035

    
4036
  /**
4037
   * Adds a new database connection info to Database.
4038
   */
4039
  protected function addConnection() {
4040
    // Add a new target to the connection, by cloning the current connection.
4041
    $connection_info = Database::getConnectionInfo($this->key);
4042
    Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]);
4043

    
4044
    // Verify that the new target exists.
4045
    $info = Database::getConnectionInfo($this->key);
4046
    // Note: Custom assertion message to not expose database credentials.
4047
    $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.');
4048
  }
4049

    
4050
  /**
4051
   * Returns the connection ID of the current test connection.
4052
   *
4053
   * @return integer
4054
   */
4055
  protected function getConnectionID() {
4056
    return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField();
4057
  }
4058

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

    
4070
  /**
4071
   * Asserts that a connection ID does not exist.
4072
   *
4073
   * @param integer $id
4074
   *   The connection ID to verify.
4075
   */
4076
  protected function assertNoConnection($id) {
4077
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
4078
    return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id)));
4079
  }
4080

    
4081
  /**
4082
   * Tests Database::closeConnection() without query.
4083
   *
4084
   * @todo getConnectionID() executes a query.
4085
   */
4086
  function testOpenClose() {
4087
    if ($this->skipTest) {
4088
      return;
4089
    }
4090
    // Add and open a new connection.
4091
    $this->addConnection();
4092
    $id = $this->getConnectionID();
4093
    Database::getConnection($this->target, $this->key);
4094

    
4095
    // Verify that there is a new connection.
4096
    $this->assertConnection($id);
4097

    
4098
    // Close the connection.
4099
    Database::closeConnection($this->target, $this->key);
4100
    // Wait 20ms to give the database engine sufficient time to react.
4101
    usleep(20000);
4102

    
4103
    // Verify that we are back to the original connection count.
4104
    $this->assertNoConnection($id);
4105
  }
4106

    
4107
  /**
4108
   * Tests Database::closeConnection() with a query.
4109
   */
4110
  function testOpenQueryClose() {
4111
    if ($this->skipTest) {
4112
      return;
4113
    }
4114
    // Add and open a new connection.
4115
    $this->addConnection();
4116
    $id = $this->getConnectionID();
4117
    Database::getConnection($this->target, $this->key);
4118

    
4119
    // Verify that there is a new connection.
4120
    $this->assertConnection($id);
4121

    
4122
    // Execute a query.
4123
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES');
4124

    
4125
    // Close the connection.
4126
    Database::closeConnection($this->target, $this->key);
4127
    // Wait 20ms to give the database engine sufficient time to react.
4128
    usleep(20000);
4129

    
4130
    // Verify that we are back to the original connection count.
4131
    $this->assertNoConnection($id);
4132
  }
4133

    
4134
  /**
4135
   * Tests Database::closeConnection() with a query and custom prefetch method.
4136
   */
4137
  function testOpenQueryPrefetchClose() {
4138
    if ($this->skipTest) {
4139
      return;
4140
    }
4141
    // Add and open a new connection.
4142
    $this->addConnection();
4143
    $id = $this->getConnectionID();
4144
    Database::getConnection($this->target, $this->key);
4145

    
4146
    // Verify that there is a new connection.
4147
    $this->assertConnection($id);
4148

    
4149
    // Execute a query.
4150
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol();
4151

    
4152
    // Close the connection.
4153
    Database::closeConnection($this->target, $this->key);
4154
    // Wait 20ms to give the database engine sufficient time to react.
4155
    usleep(20000);
4156

    
4157
    // Verify that we are back to the original connection count.
4158
    $this->assertNoConnection($id);
4159
  }
4160

    
4161
  /**
4162
   * Tests Database::closeConnection() with a select query.
4163
   */
4164
  function testOpenSelectQueryClose() {
4165
    if ($this->skipTest) {
4166
      return;
4167
    }
4168
    // Add and open a new connection.
4169
    $this->addConnection();
4170
    $id = $this->getConnectionID();
4171
    Database::getConnection($this->target, $this->key);
4172

    
4173
    // Verify that there is a new connection.
4174
    $this->assertConnection($id);
4175

    
4176
    // Create a table.
4177
    $name = 'foo';
4178
    Database::getConnection($this->target, $this->key)->schema()->createTable($name, array(
4179
      'fields' => array(
4180
        'name' => array(
4181
          'type' => 'varchar',
4182
          'length' => 255,
4183
        ),
4184
      ),
4185
    ));
4186

    
4187
    // Execute a query.
4188
    Database::getConnection($this->target, $this->key)->select('foo', 'f')
4189
      ->fields('f', array('name'))
4190
      ->execute()
4191
      ->fetchAll();
4192

    
4193
    // Drop the table.
4194
    Database::getConnection($this->target, $this->key)->schema()->dropTable($name);
4195

    
4196
    // Close the connection.
4197
    Database::closeConnection($this->target, $this->key);
4198
    // Wait 20ms to give the database engine sufficient time to react.
4199
    usleep(20000);
4200

    
4201
    // Verify that we are back to the original connection count.
4202
    $this->assertNoConnection($id);
4203
  }
4204

    
4205
}