Projet

Général

Profil

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

root / drupal7 / modules / simpletest / tests / database_test.test @ 01dfd3b5

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
    db_insert('virtual')
168
      ->fields(array(
169
        'function' => 'Function value 1',
170
      ))
171
      ->execute();
172
  }
173
}
174

    
175
/**
176
 * Test connection management.
177
 */
178
class DatabaseConnectionTestCase extends DatabaseTestCase {
179

    
180
  public static function getInfo() {
181
    return array(
182
      'name' => 'Connection tests',
183
      'description' => 'Tests of the core database system.',
184
      'group' => 'Database',
185
    );
186
  }
187

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

    
198
    $db1 = Database::getConnection('default', 'default');
199
    $db2 = Database::getConnection('slave', 'default');
200

    
201
    $this->assertNotNull($db1, 'default connection is a real connection object.');
202
    $this->assertNotNull($db2, 'slave connection is a real connection object.');
203
    $this->assertNotIdentical($db1, $db2, 'Each target refers to a different connection.');
204

    
205
    // Try to open those targets another time, that should return the same objects.
206
    $db1b = Database::getConnection('default', 'default');
207
    $db2b = Database::getConnection('slave', 'default');
208
    $this->assertIdentical($db1, $db1b, 'A second call to getConnection() returns the same object.');
209
    $this->assertIdentical($db2, $db2b, 'A second call to getConnection() returns the same object.');
210

    
211
    // Try to open an unknown target.
212
    $unknown_target = $this->randomName();
213
    $db3 = Database::getConnection($unknown_target, 'default');
214
    $this->assertNotNull($db3, 'Opening an unknown target returns a real connection object.');
215
    $this->assertIdentical($db1, $db3, 'An unknown target opens the default connection.');
216

    
217
    // Try to open that unknown target another time, that should return the same object.
218
    $db3b = Database::getConnection($unknown_target, 'default');
219
    $this->assertIdentical($db3, $db3b, 'A second call to getConnection() returns the same object.');
220
  }
221

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

    
232
    Database::ignoreTarget('default', 'slave');
233

    
234
    $db1 = Database::getConnection('default', 'default');
235
    $db2 = Database::getConnection('slave', 'default');
236

    
237
    $this->assertIdentical($db1, $db2, 'Both targets refer to the same connection.');
238
  }
239

    
240
  /**
241
   * Tests the closing of a database connection.
242
   */
243
  function testConnectionClosing() {
244
    // Open the default target so we have an object to compare.
245
    $db1 = Database::getConnection('default', 'default');
246

    
247
    // Try to close the default connection, then open a new one.
248
    Database::closeConnection('default', 'default');
249
    $db2 = Database::getConnection('default', 'default');
250

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

    
255
  /**
256
   * Tests the connection options of the active database.
257
   */
258
  function testConnectionOptions() {
259
    $connection_info = Database::getConnectionInfo('default');
260

    
261
    // Be sure we're connected to the default database.
262
    $db = Database::getConnection('default', 'default');
263
    $connectionOptions = $db->getConnectionOptions();
264

    
265
    // In the MySQL driver, the port can be different, so check individual
266
    // options.
267
    $this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], 'The default connection info driver matches the current connection options driver.');
268
    $this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], 'The default connection info database matches the current connection options database.');
269

    
270
    // Set up identical slave and confirm connection options are identical.
271
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
272
    $db2 = Database::getConnection('slave', 'default');
273
    $connectionOptions2 = $db2->getConnectionOptions();
274

    
275
    // Get a fresh copy of the default connection options.
276
    $connectionOptions = $db->getConnectionOptions();
277
    $this->assertIdentical($connectionOptions, $connectionOptions2, 'The default and slave connection options are identical.');
278

    
279
    // Set up a new connection with different connection info.
280
    $test = $connection_info['default'];
281
    $test['database'] .= 'test';
282
    Database::addConnectionInfo('test', 'default', $test);
283
    $connection_info = Database::getConnectionInfo('test');
284

    
285
    // Get a fresh copy of the default connection options.
286
    $connectionOptions = $db->getConnectionOptions();
287
    $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.');
288
  }
289
}
290

    
291
/**
292
 * Test cloning Select queries.
293
 */
294
class DatabaseSelectCloneTest extends DatabaseTestCase {
295

    
296
  public static function getInfo() {
297
    return array(
298
      'name' => 'Select tests, cloning',
299
      'description' => 'Test cloning Select queries.',
300
      'group' => 'Database',
301
    );
302
  }
303

    
304
  /**
305
   * Test that subqueries as value within conditions are cloned properly.
306
   */
307
  function testSelectConditionSubQueryCloning() {
308
    $subquery = db_select('test', 't');
309
    $subquery->addField('t', 'id', 'id');
310
    $subquery->condition('age', 28, '<');
311

    
312
    $query = db_select('test', 't');
313
    $query->addField('t', 'name', 'name');
314
    $query->condition('id', $subquery, 'IN');
315

    
316
    $clone = clone $query;
317
    // Cloned query should not be altered by the following modification
318
    // happening on original query.
319
    $subquery->condition('age', 25, '>');
320

    
321
    $clone_result = $clone->countQuery()->execute()->fetchField();
322
    $query_result = $query->countQuery()->execute()->fetchField();
323

    
324
    // Make sure the cloned query has not been modified
325
    $this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows');
326
    $this->assertEqual(2, $query_result, 'The query returns the expected number of rows');
327
  }
328
}
329

    
330
/**
331
 * Test fetch actions, part 1.
332
 *
333
 * We get timeout errors if we try to run too many tests at once.
334
 */
335
class DatabaseFetchTestCase extends DatabaseTestCase {
336

    
337
  public static function getInfo() {
338
    return array(
339
      'name' => 'Fetch tests',
340
      'description' => 'Test the Database system\'s various fetch capabilities.',
341
      'group' => 'Database',
342
    );
343
  }
344

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

    
358
    $this->assertIdentical(count($records), 1, 'There is only one record.');
359
  }
360

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

    
373
    $this->assertIdentical(count($records), 1, 'There is only one record.');
374
  }
375

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

    
389
    $this->assertIdentical(count($records), 1, 'There is only one record.');
390
  }
391

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

    
407
    $this->assertIdentical(count($records), 1, 'There is only one record.');
408
  }
409
}
410

    
411
/**
412
 * Test fetch actions, part 2.
413
 *
414
 * We get timeout errors if we try to run too many tests at once.
415
 */
416
class DatabaseFetch2TestCase extends DatabaseTestCase {
417

    
418
  public static function getInfo() {
419
    return array(
420
      'name' => 'Fetch tests, part 2',
421
      'description' => 'Test the Database system\'s various fetch capabilities.',
422
      'group' => 'Database',
423
    );
424
  }
425

    
426
  function setUp() {
427
    parent::setUp();
428
  }
429

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

    
441
    $this->assertIdentical(count($records), 1, 'There is only one record');
442
  }
443

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

    
458
    $this->assertIdentical(count($records), 1, 'There is only one record.');
459
  }
460

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

    
470
    $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25));
471
    $i = 0;
472
    foreach ($result as $record) {
473
      $this->assertIdentical($record->name, $column[$i++], 'Column matches direct accesss.');
474
    }
475
  }
476
}
477

    
478
/**
479
 * Test the insert builder.
480
 */
481
class DatabaseInsertTestCase extends DatabaseTestCase {
482

    
483
  public static function getInfo() {
484
    return array(
485
      'name' => 'Insert tests',
486
      'description' => 'Test the Insert query builder.',
487
      'group' => 'Database',
488
    );
489
  }
490

    
491
  /**
492
   * Test the very basic insert functionality.
493
   */
494
  function testSimpleInsert() {
495
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
496

    
497
    $query = db_insert('test');
498
    $query->fields(array(
499
      'name' => 'Yoko',
500
      'age' => '29',
501
    ));
502
    $query->execute();
503

    
504
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
505
    $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
506
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Yoko'))->fetchField();
507
    $this->assertIdentical($saved_age, '29', 'Can retrieve after inserting.');
508
  }
509

    
510
  /**
511
   * Test that we can insert multiple records in one query object.
512
   */
513
  function testMultiInsert() {
514
    $num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
515

    
516
    $query = db_insert('test');
517
    $query->fields(array(
518
      'name' => 'Larry',
519
      'age' => '30',
520
    ));
521

    
522
    // We should be able to specify values in any order if named.
523
    $query->values(array(
524
      'age' => '31',
525
      'name' => 'Curly',
526
    ));
527

    
528
    // We should be able to say "use the field order".
529
    // This is not the recommended mechanism for most cases, but it should work.
530
    $query->values(array('Moe', '32'));
531
    $query->execute();
532

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

    
543
  /**
544
   * Test that an insert object can be reused with new data after it executes.
545
   */
546
  function testRepeatedInsert() {
547
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
548

    
549
    $query = db_insert('test');
550

    
551
    $query->fields(array(
552
      'name' => 'Larry',
553
      'age' => '30',
554
    ));
555
    $query->execute();  // This should run the insert, but leave the fields intact.
556

    
557
    // We should be able to specify values in any order if named.
558
    $query->values(array(
559
      'age' => '31',
560
      'name' => 'Curly',
561
    ));
562
    $query->execute();
563

    
564
    // We should be able to say "use the field order".
565
    $query->values(array('Moe', '32'));
566
    $query->execute();
567

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

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

    
598
  /**
599
   * Test that inserts return the proper auto-increment ID.
600
   */
601
  function testInsertLastInsertID() {
602
    $id = db_insert('test')
603
      ->fields(array(
604
        'name' => 'Larry',
605
        'age' => '30',
606
      ))
607
      ->execute();
608

    
609
    $this->assertIdentical($id, '5', 'Auto-increment ID returned successfully.');
610
  }
611

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

    
625
    // The resulting query should be equivalent to:
626
    // INSERT INTO test (age, name, job)
627
    // SELECT tp.age AS age, tp.name AS name, tp.job AS job
628
    // FROM test_people tp
629
    // WHERE tp.name = 'Meredith'
630
    db_insert('test')
631
      ->from($query)
632
      ->execute();
633

    
634
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Meredith'))->fetchField();
635
    $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
636
  }
637

    
638
  /**
639
   * Tests that the INSERT INTO ... SELECT * ... syntax works.
640
   */
641
  function testInsertSelectAll() {
642
    $query = db_select('test_people', 'tp')
643
      ->fields('tp')
644
      ->condition('tp.name', 'Meredith');
645

    
646
    // The resulting query should be equivalent to:
647
    // INSERT INTO test_people_copy
648
    // SELECT *
649
    // FROM test_people tp
650
    // WHERE tp.name = 'Meredith'
651
    db_insert('test_people_copy')
652
      ->from($query)
653
      ->execute();
654

    
655
    $saved_age = db_query('SELECT age FROM {test_people_copy} WHERE name = :name', array(':name' => 'Meredith'))->fetchField();
656
    $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.');
657
  }
658
}
659

    
660
/**
661
 * Insert tests using LOB fields, which are weird on some databases.
662
 */
663
class DatabaseInsertLOBTestCase extends DatabaseTestCase {
664

    
665
  public static function getInfo() {
666
    return array(
667
      'name' => 'Insert tests, LOB fields',
668
      'description' => 'Test the Insert query builder with LOB fields.',
669
      'group' => 'Database',
670
    );
671
  }
672

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

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

    
701
/**
702
 * Insert tests for "database default" values.
703
 */
704
class DatabaseInsertDefaultsTestCase extends DatabaseTestCase {
705

    
706
  public static function getInfo() {
707
    return array(
708
      'name' => 'Insert tests, default fields',
709
      'description' => 'Test the Insert query builder with default values.',
710
      'group' => 'Database',
711
    );
712
  }
713

    
714
  /**
715
   * Test that we can run a query that is "default values for everything".
716
   */
717
  function testDefaultInsert() {
718
    $query = db_insert('test')->useDefaults(array('job'));
719
    $id = $query->execute();
720

    
721
    $schema = drupal_get_schema('test');
722

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

    
727
  /**
728
   * Test that no action will be preformed if no fields are specified.
729
   */
730
  function testDefaultEmptyInsert() {
731
    $num_records_before = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
732

    
733
    try {
734
      $result = db_insert('test')->execute();
735
      // This is only executed if no exception has been thrown.
736
      $this->fail('Expected exception NoFieldsException has not been thrown.');
737
    } catch (NoFieldsException $e) {
738
      $this->pass('Expected exception NoFieldsException has been thrown.');
739
    }
740

    
741
    $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField();
742
    $this->assertIdentical($num_records_before, $num_records_after, 'Do nothing as no fields are specified.');
743
  }
744

    
745
  /**
746
   * Test that we can insert fields with values and defaults in the same query.
747
   */
748
  function testDefaultInsertWithFields() {
749
    $query = db_insert('test')
750
      ->fields(array('name' => 'Bob'))
751
      ->useDefaults(array('job'));
752
    $id = $query->execute();
753

    
754
    $schema = drupal_get_schema('test');
755

    
756
    $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField();
757
    $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.');
758
  }
759
}
760

    
761
/**
762
 * Update builder tests.
763
 */
764
class DatabaseUpdateTestCase extends DatabaseTestCase {
765

    
766
  public static function getInfo() {
767
    return array(
768
      'name' => 'Update tests',
769
      'description' => 'Test the Update query builder.',
770
      'group' => 'Database',
771
    );
772
  }
773

    
774
  /**
775
   * Confirm that we can update a single record successfully.
776
   */
777
  function testSimpleUpdate() {
778
    $num_updated = db_update('test')
779
      ->fields(array('name' => 'Tiffany'))
780
      ->condition('id', 1)
781
      ->execute();
782
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
783

    
784
    $saved_name = db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 1))->fetchField();
785
    $this->assertIdentical($saved_name, 'Tiffany', 'Updated name successfully.');
786
  }
787

    
788
  /**
789
   * Confirm updating to NULL.
790
   */
791
  function testSimpleNullUpdate() {
792
    $this->ensureSampleDataNull();
793
    $num_updated = db_update('test_null')
794
      ->fields(array('age' => NULL))
795
      ->condition('name', 'Kermit')
796
      ->execute();
797
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
798

    
799
    $saved_age = db_query('SELECT age FROM {test_null} WHERE name = :name', array(':name' => 'Kermit'))->fetchField();
800
    $this->assertNull($saved_age, 'Updated name successfully.');
801
  }
802

    
803
  /**
804
   * Confirm that we can update a multiple records successfully.
805
   */
806
  function testMultiUpdate() {
807
    $num_updated = db_update('test')
808
      ->fields(array('job' => 'Musician'))
809
      ->condition('job', 'Singer')
810
      ->execute();
811
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
812

    
813
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
814
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
815
  }
816

    
817
  /**
818
   * Confirm that we can update a multiple records with a non-equality condition.
819
   */
820
  function testMultiGTUpdate() {
821
    $num_updated = db_update('test')
822
      ->fields(array('job' => 'Musician'))
823
      ->condition('age', 26, '>')
824
      ->execute();
825
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
826

    
827
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
828
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
829
  }
830

    
831
  /**
832
   * Confirm that we can update a multiple records with a where call.
833
   */
834
  function testWhereUpdate() {
835
    $num_updated = db_update('test')
836
      ->fields(array('job' => 'Musician'))
837
      ->where('age > :age', array(':age' => 26))
838
      ->execute();
839
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
840

    
841
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
842
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
843
  }
844

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

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

    
860
  /**
861
   * Test updating with expressions.
862
   */
863
  function testExpressionUpdate() {
864
    // Set age = 1 for a single row for this test to work.
865
    db_update('test')
866
      ->condition('id', 1)
867
      ->fields(array('age' => 1))
868
      ->execute();
869

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

    
880
  /**
881
   * Confirm that we can update the primary key of a record successfully.
882
   */
883
  function testPrimaryKeyUpdate() {
884
    $num_updated = db_update('test')
885
      ->fields(array('id' => 42, 'name' => 'John'))
886
      ->condition('id', 1)
887
      ->execute();
888
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
889

    
890
    $saved_name= db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 42))->fetchField();
891
    $this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.');
892
  }
893
}
894

    
895
/**
896
 * Tests for more complex update statements.
897
 */
898
class DatabaseUpdateComplexTestCase extends DatabaseTestCase {
899

    
900
  public static function getInfo() {
901
    return array(
902
      'name' => 'Update tests, Complex',
903
      'description' => 'Test the Update query builder, complex queries.',
904
      'group' => 'Database',
905
    );
906
  }
907

    
908
  /**
909
   * Test updates with OR conditionals.
910
   */
911
  function testOrConditionUpdate() {
912
    $update = db_update('test')
913
      ->fields(array('job' => 'Musician'))
914
      ->condition(db_or()
915
        ->condition('name', 'John')
916
        ->condition('name', 'Paul')
917
      );
918
    $num_updated = $update->execute();
919
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
920

    
921
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
922
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
923
  }
924

    
925
  /**
926
   * Test WHERE IN clauses.
927
   */
928
  function testInConditionUpdate() {
929
    $num_updated = db_update('test')
930
      ->fields(array('job' => 'Musician'))
931
      ->condition('name', array('John', 'Paul'), 'IN')
932
      ->execute();
933
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
934

    
935
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
936
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
937
  }
938

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

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

    
955
  /**
956
   * Test BETWEEN conditional clauses.
957
   */
958
  function testBetweenConditionUpdate() {
959
    $num_updated = db_update('test')
960
      ->fields(array('job' => 'Musician'))
961
      ->condition('age', array(25, 26), 'BETWEEN')
962
      ->execute();
963
    $this->assertIdentical($num_updated, 2, 'Updated 2 records.');
964

    
965
    $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField();
966
    $this->assertIdentical($num_matches, '2', 'Updated fields successfully.');
967
  }
968

    
969
  /**
970
   * Test LIKE conditionals.
971
   */
972
  function testLikeConditionUpdate() {
973
    $num_updated = db_update('test')
974
      ->fields(array('job' => 'Musician'))
975
      ->condition('name', '%ge%', 'LIKE')
976
      ->execute();
977
    $this->assertIdentical($num_updated, 1, 'Updated 1 record.');
978

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

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

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

    
999
    $person = db_query('SELECT * FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetch();
1000
    $this->assertEqual($person->name, 'Ringo', 'Name set correctly.');
1001
    $this->assertEqual($person->age, $before_age + 4, 'Age set correctly.');
1002
    $this->assertEqual($person->job, 'Musician', 'Job set correctly.');
1003
    $GLOBALS['larry_test'] = 0;
1004
  }
1005

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

    
1017
    $after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField();
1018
    $this->assertEqual($before_age + 4, $after_age, 'Age updated correctly');
1019
  }
1020
}
1021

    
1022
/**
1023
 * Test update queries involving LOB values.
1024
 */
1025
class DatabaseUpdateLOBTestCase extends DatabaseTestCase {
1026

    
1027
  public static function getInfo() {
1028
    return array(
1029
      'name' => 'Update tests, LOB',
1030
      'description' => 'Test the Update query builder with LOB fields.',
1031
      'group' => 'Database',
1032
    );
1033
  }
1034

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

    
1045
    $data .= $data;
1046
    db_update('test_one_blob')
1047
      ->condition('id', $id)
1048
      ->fields(array('blob1' => $data))
1049
      ->execute();
1050

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

    
1055
  /**
1056
   * Confirm that we can update two blob columns in the same table.
1057
   */
1058
  function testUpdateMultipleBlob() {
1059
    $id = db_insert('test_two_blobs')
1060
      ->fields(array(
1061
        'blob1' => 'This is',
1062
        'blob2' => 'a test',
1063
      ))
1064
      ->execute();
1065

    
1066
    db_update('test_two_blobs')
1067
      ->condition('id', $id)
1068
      ->fields(array('blob1' => 'and so', 'blob2' => 'is this'))
1069
      ->execute();
1070

    
1071
    $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc();
1072
    $this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', 'Can update multiple blobs per row.');
1073
  }
1074
}
1075

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

    
1089
  public static function getInfo() {
1090
    return array(
1091
      'name' => 'Delete/Truncate tests',
1092
      'description' => 'Test the Delete and Truncate query builders.',
1093
      'group' => 'Database',
1094
    );
1095
  }
1096

    
1097
  /**
1098
   * Confirm that we can use a subselect in a delete successfully.
1099
   */
1100
  function testSubselectDelete() {
1101
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
1102
    $pid_to_delete = db_query("SELECT * FROM {test_task} WHERE task = 'sleep'")->fetchField();
1103

    
1104
    $subquery = db_select('test', 't')
1105
      ->fields('t', array('id'))
1106
      ->condition('t.id', array($pid_to_delete), 'IN');
1107
    $delete = db_delete('test_task')
1108
      ->condition('task', 'sleep')
1109
      ->condition('pid', $subquery, 'IN');
1110

    
1111
    $num_deleted = $delete->execute();
1112
    $this->assertEqual($num_deleted, 1, "Deleted 1 record.");
1113

    
1114
    $num_records_after = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField();
1115
    $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
1116
  }
1117

    
1118
  /**
1119
   * Confirm that we can delete a single record successfully.
1120
   */
1121
  function testSimpleDelete() {
1122
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
1123

    
1124
    $num_deleted = db_delete('test')
1125
      ->condition('id', 1)
1126
      ->execute();
1127
    $this->assertIdentical($num_deleted, 1, 'Deleted 1 record.');
1128

    
1129
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
1130
    $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.');
1131
  }
1132

    
1133
  /**
1134
   * Confirm that we can truncate a whole table successfully.
1135
   */
1136
  function testTruncate() {
1137
    $num_records_before = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
1138

    
1139
    db_truncate('test')->execute();
1140

    
1141
    $num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
1142
    $this->assertEqual(0, $num_records_after, 'Truncate really deletes everything.');
1143
  }
1144
}
1145

    
1146
/**
1147
 * Test the MERGE query builder.
1148
 */
1149
class DatabaseMergeTestCase extends DatabaseTestCase {
1150

    
1151
  public static function getInfo() {
1152
    return array(
1153
      'name' => 'Merge tests',
1154
      'description' => 'Test the Merge query builder.',
1155
      'group' => 'Database',
1156
    );
1157
  }
1158

    
1159
  /**
1160
   * Confirm that we can merge-insert a record successfully.
1161
   */
1162
  function testMergeInsert() {
1163
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1164

    
1165
    $result = db_merge('test_people')
1166
      ->key(array('job' => 'Presenter'))
1167
      ->fields(array(
1168
        'age' => 31,
1169
        'name' => 'Tiffany',
1170
      ))
1171
      ->execute();
1172

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

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

    
1178
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
1179
    $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
1180
    $this->assertEqual($person->age, 31, 'Age set correctly.');
1181
    $this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
1182
  }
1183

    
1184
  /**
1185
   * Confirm that we can merge-update a record successfully.
1186
   */
1187
  function testMergeUpdate() {
1188
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1189

    
1190
    $result = db_merge('test_people')
1191
      ->key(array('job' => 'Speaker'))
1192
      ->fields(array(
1193
        'age' => 31,
1194
        'name' => 'Tiffany',
1195
      ))
1196
      ->execute();
1197

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

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

    
1203
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1204
    $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
1205
    $this->assertEqual($person->age, 31, 'Age set correctly.');
1206
    $this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
1207
  }
1208

    
1209
  /**
1210
   * Confirm that we can merge-update a record successfully, with different insert and update.
1211
   */
1212
  function testMergeUpdateExcept() {
1213
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1214

    
1215
    db_merge('test_people')
1216
      ->key(array('job' => 'Speaker'))
1217
      ->insertFields(array('age' => 31))
1218
      ->updateFields(array('name' => 'Tiffany'))
1219
      ->execute();
1220

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

    
1224
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1225
    $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
1226
    $this->assertEqual($person->age, 30, 'Age skipped correctly.');
1227
    $this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
1228
  }
1229

    
1230
  /**
1231
   * Confirm that we can merge-update a record successfully, with alternate replacement.
1232
   */
1233
  function testMergeUpdateExplicit() {
1234
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1235

    
1236
    db_merge('test_people')
1237
      ->key(array('job' => 'Speaker'))
1238
      ->insertFields(array(
1239
        'age' => 31,
1240
        'name' => 'Tiffany',
1241
      ))
1242
      ->updateFields(array(
1243
        'name' => 'Joe',
1244
      ))
1245
      ->execute();
1246

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

    
1250
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1251
    $this->assertEqual($person->name, 'Joe', 'Name set correctly.');
1252
    $this->assertEqual($person->age, 30, 'Age skipped correctly.');
1253
    $this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
1254
  }
1255

    
1256
  /**
1257
   * Confirm that we can merge-update a record successfully, with expressions.
1258
   */
1259
  function testMergeUpdateExpression() {
1260
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1261

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

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

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

    
1279
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1280
    $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.');
1281
    $this->assertEqual($person->age, $age_before + 4, 'Age updated correctly.');
1282
    $this->assertEqual($person->job, 'Speaker', 'Job set correctly.');
1283
  }
1284

    
1285
  /**
1286
   * Test that we can merge-insert without any update fields.
1287
   */
1288
  function testMergeInsertWithoutUpdate() {
1289
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1290

    
1291
    db_merge('test_people')
1292
      ->key(array('job' => 'Presenter'))
1293
      ->execute();
1294

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

    
1298
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch();
1299
    $this->assertEqual($person->name, '', 'Name set correctly.');
1300
    $this->assertEqual($person->age, 0, 'Age set correctly.');
1301
    $this->assertEqual($person->job, 'Presenter', 'Job set correctly.');
1302
  }
1303

    
1304
  /**
1305
   * Confirm that we can merge-update without any update fields.
1306
   */
1307
  function testMergeUpdateWithoutUpdate() {
1308
    $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
1309

    
1310
    db_merge('test_people')
1311
      ->key(array('job' => 'Speaker'))
1312
      ->execute();
1313

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

    
1317
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1318
    $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.');
1319
    $this->assertEqual($person->age, 30, 'Age skipped correctly.');
1320
    $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
1321

    
1322
    db_merge('test_people')
1323
      ->key(array('job' => 'Speaker'))
1324
      ->insertFields(array('age' => 31))
1325
      ->execute();
1326

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

    
1330
    $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch();
1331
    $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.');
1332
    $this->assertEqual($person->age, 30, 'Age skipped correctly.');
1333
    $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
1334
  }
1335

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

    
1357
/**
1358
 * Test the SELECT builder.
1359
 */
1360
class DatabaseSelectTestCase extends DatabaseTestCase {
1361

    
1362
  public static function getInfo() {
1363
    return array(
1364
      'name' => 'Select tests',
1365
      'description' => 'Test the Select query builder.',
1366
      'group' => 'Database',
1367
    );
1368
  }
1369

    
1370
  /**
1371
   * Test rudimentary SELECT statements.
1372
   */
1373
  function testSimpleSelect() {
1374
    $query = db_select('test');
1375
    $name_field = $query->addField('test', 'name');
1376
    $age_field = $query->addField('test', 'age', 'age');
1377
    $result = $query->execute();
1378

    
1379
    $num_records = 0;
1380
    foreach ($result as $record) {
1381
      $num_records++;
1382
    }
1383

    
1384
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1385
  }
1386

    
1387
  /**
1388
   * Test rudimentary SELECT statement with a COMMENT.
1389
   */
1390
  function testSimpleComment() {
1391
    $query = db_select('test')->comment('Testing query comments');
1392
    $name_field = $query->addField('test', 'name');
1393
    $age_field = $query->addField('test', 'age', 'age');
1394
    $result = $query->execute();
1395

    
1396
    $num_records = 0;
1397
    foreach ($result as $record) {
1398
      $num_records++;
1399
    }
1400

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

    
1404
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1405
    $this->assertEqual($query, $expected, 'The flattened query contains the comment string.');
1406
  }
1407

    
1408
  /**
1409
   * Test query COMMENT system against vulnerabilities.
1410
   */
1411
  function testVulnerableComment() {
1412
    $query = db_select('test')->comment('Testing query comments */ SELECT nid FROM {node}; --');
1413
    $name_field = $query->addField('test', 'name');
1414
    $age_field = $query->addField('test', 'age', 'age');
1415
    $result = $query->execute();
1416

    
1417
    $num_records = 0;
1418
    foreach ($result as $record) {
1419
      $num_records++;
1420
    }
1421

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

    
1425
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1426
    $this->assertEqual($query, $expected, 'The flattened query contains the sanitised comment string.');
1427

    
1428
    $connection = Database::getConnection();
1429
    foreach ($this->makeCommentsProvider() as $test_set) {
1430
      list($expected, $comments) = $test_set;
1431
      $this->assertEqual($expected, $connection->makeComment($comments));
1432
    }
1433
  }
1434

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

    
1466
  /**
1467
   * Test basic conditionals on SELECT statements.
1468
   */
1469
  function testSimpleSelectConditional() {
1470
    $query = db_select('test');
1471
    $name_field = $query->addField('test', 'name');
1472
    $age_field = $query->addField('test', 'age', 'age');
1473
    $query->condition('age', 27);
1474
    $result = $query->execute();
1475

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

    
1480
    // Ensure that we got the right record.
1481
    $record = $result->fetch();
1482
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1483
    $this->assertEqual($record->$age_field, 27, 'Fetched age is correct.');
1484
  }
1485

    
1486
  /**
1487
   * Test SELECT statements with expressions.
1488
   */
1489
  function testSimpleSelectExpression() {
1490
    $query = db_select('test');
1491
    $name_field = $query->addField('test', 'name');
1492
    $age_field = $query->addExpression("age*2", 'double_age');
1493
    $query->condition('age', 27);
1494
    $result = $query->execute();
1495

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

    
1500
    // Ensure that we got the right record.
1501
    $record = $result->fetch();
1502
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1503
    $this->assertEqual($record->$age_field, 27*2, 'Fetched age expression is correct.');
1504
  }
1505

    
1506
  /**
1507
   * Test SELECT statements with multiple expressions.
1508
   */
1509
  function testSimpleSelectExpressionMultiple() {
1510
    $query = db_select('test');
1511
    $name_field = $query->addField('test', 'name');
1512
    $age_double_field = $query->addExpression("age*2");
1513
    $age_triple_field = $query->addExpression("age*3");
1514
    $query->condition('age', 27);
1515
    $result = $query->execute();
1516

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

    
1521
    // Ensure that we got the right record.
1522
    $record = $result->fetch();
1523
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
1524
    $this->assertEqual($record->$age_double_field, 27*2, 'Fetched double age expression is correct.');
1525
    $this->assertEqual($record->$age_triple_field, 27*3, 'Fetched triple age expression is correct.');
1526
  }
1527

    
1528
  /**
1529
   * Test adding multiple fields to a select statement at the same time.
1530
   */
1531
  function testSimpleSelectMultipleFields() {
1532
    $record = db_select('test')
1533
      ->fields('test', array('id', 'name', 'age', 'job'))
1534
      ->condition('age', 27)
1535
      ->execute()->fetchObject();
1536

    
1537
    // Check that all fields we asked for are present.
1538
    $this->assertNotNull($record->id, 'ID field is present.');
1539
    $this->assertNotNull($record->name, 'Name field is present.');
1540
    $this->assertNotNull($record->age, 'Age field is present.');
1541
    $this->assertNotNull($record->job, 'Job field is present.');
1542

    
1543
    // Ensure that we got the right record.
1544
    // Check that all fields we asked for are present.
1545
    $this->assertEqual($record->id, 2, 'ID field has the correct value.');
1546
    $this->assertEqual($record->name, 'George', 'Name field has the correct value.');
1547
    $this->assertEqual($record->age, 27, 'Age field has the correct value.');
1548
    $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
1549
  }
1550

    
1551
  /**
1552
   * Test adding all fields from a given table to a select statement.
1553
   */
1554
  function testSimpleSelectAllFields() {
1555
    $record = db_select('test')
1556
      ->fields('test')
1557
      ->condition('age', 27)
1558
      ->execute()->fetchObject();
1559

    
1560
    // Check that all fields we asked for are present.
1561
    $this->assertNotNull($record->id, 'ID field is present.');
1562
    $this->assertNotNull($record->name, 'Name field is present.');
1563
    $this->assertNotNull($record->age, 'Age field is present.');
1564
    $this->assertNotNull($record->job, 'Job field is present.');
1565

    
1566
    // Ensure that we got the right record.
1567
    // Check that all fields we asked for are present.
1568
    $this->assertEqual($record->id, 2, 'ID field has the correct value.');
1569
    $this->assertEqual($record->name, 'George', 'Name field has the correct value.');
1570
    $this->assertEqual($record->age, 27, 'Age field has the correct value.');
1571
    $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.');
1572
  }
1573

    
1574
  /**
1575
   * Test that we can find a record with a NULL value.
1576
   */
1577
  function testNullCondition() {
1578
    $this->ensureSampleDataNull();
1579

    
1580
    $names = db_select('test_null', 'tn')
1581
      ->fields('tn', array('name'))
1582
      ->isNull('age')
1583
      ->execute()->fetchCol();
1584

    
1585
    $this->assertEqual(count($names), 1, 'Correct number of records found with NULL age.');
1586
    $this->assertEqual($names[0], 'Fozzie', 'Correct record returned for NULL age.');
1587
  }
1588

    
1589
  /**
1590
   * Test that we can find a record without a NULL value.
1591
   */
1592
  function testNotNullCondition() {
1593
    $this->ensureSampleDataNull();
1594

    
1595
    $names = db_select('test_null', 'tn')
1596
      ->fields('tn', array('name'))
1597
      ->isNotNull('tn.age')
1598
      ->orderBy('name')
1599
      ->execute()->fetchCol();
1600

    
1601
    $this->assertEqual(count($names), 2, 'Correct number of records found withNOT NULL age.');
1602
    $this->assertEqual($names[0], 'Gonzo', 'Correct record returned for NOT NULL age.');
1603
    $this->assertEqual($names[1], 'Kermit', 'Correct record returned for NOT NULL age.');
1604
  }
1605

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

    
1615
    $query_2 = db_select('test', 't')
1616
      ->fields('t', array('name'))
1617
      ->condition('age', 28);
1618

    
1619
    $query_1->union($query_2);
1620

    
1621
    $names = $query_1->execute()->fetchCol();
1622

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

    
1626
    $this->assertEqual($names[0], 'George', 'First query returned correct name.');
1627
    $this->assertEqual($names[1], 'Ringo', 'Second query returned correct name.');
1628
  }
1629

    
1630
  /**
1631
   * Test that we can UNION ALL multiple Select queries together.
1632
   */
1633
  function testUnionAll() {
1634
    $query_1 = db_select('test', 't')
1635
      ->fields('t', array('name'))
1636
      ->condition('age', array(27, 28), 'IN');
1637

    
1638
    $query_2 = db_select('test', 't')
1639
      ->fields('t', array('name'))
1640
      ->condition('age', 28);
1641

    
1642
    $query_1->union($query_2, 'ALL');
1643

    
1644
    $names = $query_1->execute()->fetchCol();
1645

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

    
1649
    $this->assertEqual($names[0], 'George', 'First query returned correct first name.');
1650
    $this->assertEqual($names[1], 'Ringo', 'Second query returned correct second name.');
1651
    $this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.');
1652
  }
1653

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

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

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

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

    
1716
  /**
1717
   * Test that aliases are renamed when duplicates.
1718
   */
1719
  function testSelectDuplicateAlias() {
1720
    $query = db_select('test', 't');
1721
    $alias1 = $query->addField('t', 'name', 'the_alias');
1722
    $alias2 = $query->addField('t', 'age', 'the_alias');
1723
    $this->assertNotIdentical($alias1, $alias2, 'Duplicate aliases are renamed.');
1724
  }
1725
}
1726

    
1727
/**
1728
 * Test case for subselects in a dynamic SELECT query.
1729
 */
1730
class DatabaseSelectSubqueryTestCase extends DatabaseTestCase {
1731

    
1732
  public static function getInfo() {
1733
    return array(
1734
      'name' => 'Select tests, subqueries',
1735
      'description' => 'Test the Select query builder.',
1736
      'group' => 'Database',
1737
    );
1738
  }
1739

    
1740
  /**
1741
   * Test that we can use a subquery in a FROM clause.
1742
   */
1743
  function testFromSubquerySelect() {
1744
    // Create a subquery, which is just a normal query object.
1745
    $subquery = db_select('test_task', 'tt');
1746
    $subquery->addField('tt', 'pid', 'pid');
1747
    $subquery->addField('tt', 'task', 'task');
1748
    $subquery->condition('priority', 1);
1749

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

    
1763
      // The resulting query should be equivalent to:
1764
      // SELECT t.name
1765
      // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt
1766
      //   INNER JOIN test t ON t.id=tt.pid
1767
      // WHERE tt.task = 'code'
1768
      $people = $select->execute()->fetchCol();
1769

    
1770
      $this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
1771
    }
1772
  }
1773

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

    
1785
    // Create another query that joins against the virtual table resulting
1786
    // from the subquery.
1787
    $select = db_select($subquery, 'tt2');
1788
    $select->join('test', 't', 't.id=tt2.pid');
1789
    $select->addField('t', 'name');
1790

    
1791
    // The resulting query should be equivalent to:
1792
    // SELECT t.name
1793
    // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt ORDER BY priority DESC LIMIT 1 OFFSET 0) tt
1794
    //   INNER JOIN test t ON t.id=tt.pid
1795
    $people = $select->execute()->fetchCol();
1796

    
1797
    $this->assertEqual(count($people), 1, 'Returned the correct number of rows.');
1798
  }
1799

    
1800
  /**
1801
   * Test that we can use a subquery in a WHERE clause.
1802
   */
1803
  function testConditionSubquerySelect() {
1804
    // Create a subquery, which is just a normal query object.
1805
    $subquery = db_select('test_task', 'tt');
1806
    $subquery->addField('tt', 'pid', 'pid');
1807
    $subquery->condition('tt.priority', 1);
1808

    
1809
    // Create another query that joins against the virtual table resulting
1810
    // from the subquery.
1811
    $select = db_select('test_task', 'tt2');
1812
    $select->addField('tt2', 'task');
1813
    $select->condition('tt2.pid', $subquery, 'IN');
1814

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

    
1823
  /**
1824
   * Test that we can use a subquery in a JOIN clause.
1825
   */
1826
  function testJoinSubquerySelect() {
1827
    // Create a subquery, which is just a normal query object.
1828
    $subquery = db_select('test_task', 'tt');
1829
    $subquery->addField('tt', 'pid', 'pid');
1830
    $subquery->condition('priority', 1);
1831

    
1832
    // Create another query that joins against the virtual table resulting
1833
    // from the subquery.
1834
    $select = db_select('test', 't');
1835
    $select->join($subquery, 'tt', 't.id=tt.pid');
1836
    $select->addField('t', 'name');
1837

    
1838
    // The resulting query should be equivalent to:
1839
    // SELECT t.name
1840
    // FROM test t
1841
    //   INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid
1842
    $people = $select->execute()->fetchCol();
1843

    
1844
    $this->assertEqual(count($people), 2, 'Returned the correct number of rows.');
1845
  }
1846

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

    
1872
    // Ensure that we got the right record.
1873
    $record = $result->fetch();
1874
    $this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.');
1875
  }
1876

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

    
1893
    // Base query to {test}.
1894
    $query = db_select('test', 't')
1895
      ->fields('t', array('name'));
1896
    // Subquery to {test_people}.
1897
    $subquery = db_select('test_people', 'tp')
1898
      ->fields('tp', array('name'))
1899
      ->where('tp.name = t.name');
1900
    $query->notExists($subquery);
1901

    
1902
    // Ensure that we got the right number of records.
1903
    $people = $query->execute()->fetchCol();
1904
    $this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.');
1905
  }
1906
}
1907

    
1908
/**
1909
 * Test select with order by clauses.
1910
 */
1911
class DatabaseSelectOrderedTestCase extends DatabaseTestCase {
1912

    
1913
  public static function getInfo() {
1914
    return array(
1915
      'name' => 'Select tests, ordered',
1916
      'description' => 'Test the Select query builder.',
1917
      'group' => 'Database',
1918
    );
1919
  }
1920

    
1921
  /**
1922
   * Test basic order by.
1923
   */
1924
  function testSimpleSelectOrdered() {
1925
    $query = db_select('test');
1926
    $name_field = $query->addField('test', 'name');
1927
    $age_field = $query->addField('test', 'age', 'age');
1928
    $query->orderBy($age_field);
1929
    $result = $query->execute();
1930

    
1931
    $num_records = 0;
1932
    $last_age = 0;
1933
    foreach ($result as $record) {
1934
      $num_records++;
1935
      $this->assertTrue($record->age >= $last_age, 'Results returned in correct order.');
1936
      $last_age = $record->age;
1937
    }
1938

    
1939
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1940
  }
1941

    
1942
  /**
1943
   * Test multiple order by.
1944
   */
1945
  function testSimpleSelectMultiOrdered() {
1946
    $query = db_select('test');
1947
    $name_field = $query->addField('test', 'name');
1948
    $age_field = $query->addField('test', 'age', 'age');
1949
    $job_field = $query->addField('test', 'job');
1950
    $query->orderBy($job_field);
1951
    $query->orderBy($age_field);
1952
    $result = $query->execute();
1953

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

    
1973
  /**
1974
   * Test order by descending.
1975
   */
1976
  function testSimpleSelectOrderedDesc() {
1977
    $query = db_select('test');
1978
    $name_field = $query->addField('test', 'name');
1979
    $age_field = $query->addField('test', 'age', 'age');
1980
    $query->orderBy($age_field, 'DESC');
1981
    $result = $query->execute();
1982

    
1983
    $num_records = 0;
1984
    $last_age = 100000000;
1985
    foreach ($result as $record) {
1986
      $num_records++;
1987
      $this->assertTrue($record->age <= $last_age, 'Results returned in correct order.');
1988
      $last_age = $record->age;
1989
    }
1990

    
1991
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
1992
  }
1993

    
1994
  /**
1995
   * Tests that the sort direction is sanitized properly.
1996
   */
1997
  function testOrderByEscaping() {
1998
    $query = db_select('test')->orderBy('name', 'invalid direction');
1999
    $order_bys = $query->getOrderBy();
2000
    $this->assertEqual($order_bys['name'], 'ASC', 'Invalid order by direction is converted to ASC.');
2001
  }
2002
}
2003

    
2004
/**
2005
 * Test more complex select statements.
2006
 */
2007
class DatabaseSelectComplexTestCase extends DatabaseTestCase {
2008

    
2009
  public static function getInfo() {
2010
    return array(
2011
      'name' => 'Select tests, complex',
2012
      'description' => 'Test the Select query builder with more complex queries.',
2013
      'group' => 'Database',
2014
    );
2015
  }
2016

    
2017
  /**
2018
   * Test simple JOIN statements.
2019
   */
2020
  function testDefaultJoin() {
2021
    $query = db_select('test_task', 't');
2022
    $people_alias = $query->join('test', 'p', 't.pid = p.id');
2023
    $name_field = $query->addField($people_alias, 'name', 'name');
2024
    $task_field = $query->addField('t', 'task', 'task');
2025
    $priority_field = $query->addField('t', 'priority', 'priority');
2026

    
2027
    $query->orderBy($priority_field);
2028
    $result = $query->execute();
2029

    
2030
    $num_records = 0;
2031
    $last_priority = 0;
2032
    foreach ($result as $record) {
2033
      $num_records++;
2034
      $this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.');
2035
      $this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.');
2036
      $last_priority = $record->$priority_field;
2037
    }
2038

    
2039
    $this->assertEqual($num_records, 7, 'Returned the correct number of rows.');
2040
  }
2041

    
2042
  /**
2043
   * Test LEFT OUTER joins.
2044
   */
2045
  function testLeftOuterJoin() {
2046
    $query = db_select('test', 'p');
2047
    $people_alias = $query->leftJoin('test_task', 't', 't.pid = p.id');
2048
    $name_field = $query->addField('p', 'name', 'name');
2049
    $task_field = $query->addField($people_alias, 'task', 'task');
2050
    $priority_field = $query->addField($people_alias, 'priority', 'priority');
2051

    
2052
    $query->orderBy($name_field);
2053
    $result = $query->execute();
2054

    
2055
    $num_records = 0;
2056
    $last_name = 0;
2057

    
2058
    foreach ($result as $record) {
2059
      $num_records++;
2060
      $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.');
2061
      $last_priority = $record->$name_field;
2062
    }
2063

    
2064
    $this->assertEqual($num_records, 8, 'Returned the correct number of rows.');
2065
  }
2066

    
2067
  /**
2068
   * Test GROUP BY clauses.
2069
   */
2070
  function testGroupBy() {
2071
    $query = db_select('test_task', 't');
2072
    $count_field = $query->addExpression('COUNT(task)', 'num');
2073
    $task_field = $query->addField('t', 'task');
2074
    $query->orderBy($count_field);
2075
    $query->groupBy($task_field);
2076
    $result = $query->execute();
2077

    
2078
    $num_records = 0;
2079
    $last_count = 0;
2080
    $records = array();
2081
    foreach ($result as $record) {
2082
      $num_records++;
2083
      $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
2084
      $last_count = $record->$count_field;
2085
      $records[$record->$task_field] = $record->$count_field;
2086
    }
2087

    
2088
    $correct_results = array(
2089
      'eat' => 1,
2090
      'sleep' => 2,
2091
      'code' => 1,
2092
      'found new band' => 1,
2093
      'perform at superbowl' => 1,
2094
    );
2095

    
2096
    foreach ($correct_results as $task => $count) {
2097
      $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
2098
    }
2099

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

    
2103
  /**
2104
   * Test GROUP BY and HAVING clauses together.
2105
   */
2106
  function testGroupByAndHaving() {
2107
    $query = db_select('test_task', 't');
2108
    $count_field = $query->addExpression('COUNT(task)', 'num');
2109
    $task_field = $query->addField('t', 'task');
2110
    $query->orderBy($count_field);
2111
    $query->groupBy($task_field);
2112
    $query->having('COUNT(task) >= 2');
2113
    $result = $query->execute();
2114

    
2115
    $num_records = 0;
2116
    $last_count = 0;
2117
    $records = array();
2118
    foreach ($result as $record) {
2119
      $num_records++;
2120
      $this->assertTrue($record->$count_field >= 2, 'Record has the minimum count.');
2121
      $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.');
2122
      $last_count = $record->$count_field;
2123
      $records[$record->$task_field] = $record->$count_field;
2124
    }
2125

    
2126
    $correct_results = array(
2127
      'sleep' => 2,
2128
    );
2129

    
2130
    foreach ($correct_results as $task => $count) {
2131
      $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task)));
2132
    }
2133

    
2134
    $this->assertEqual($num_records, 1, 'Returned the correct number of total rows.');
2135
  }
2136

    
2137
  /**
2138
   * Test range queries. The SQL clause varies with the database.
2139
   */
2140
  function testRange() {
2141
    $query = db_select('test');
2142
    $name_field = $query->addField('test', 'name');
2143
    $age_field = $query->addField('test', 'age', 'age');
2144
    $query->range(0, 2);
2145
    $result = $query->execute();
2146

    
2147
    $num_records = 0;
2148
    foreach ($result as $record) {
2149
      $num_records++;
2150
    }
2151

    
2152
    $this->assertEqual($num_records, 2, 'Returned the correct number of rows.');
2153
  }
2154

    
2155
  /**
2156
   * Test distinct queries.
2157
   */
2158
  function testDistinct() {
2159
    $query = db_select('test_task');
2160
    $task_field = $query->addField('test_task', 'task');
2161
    $query->distinct();
2162
    $result = $query->execute();
2163

    
2164
    $num_records = 0;
2165
    foreach ($result as $record) {
2166
      $num_records++;
2167
    }
2168

    
2169
    $this->assertEqual($num_records, 6, 'Returned the correct number of rows.');
2170
  }
2171

    
2172
  /**
2173
   * Test that we can generate a count query from a built query.
2174
   */
2175
  function testCountQuery() {
2176
    $query = db_select('test');
2177
    $name_field = $query->addField('test', 'name');
2178
    $age_field = $query->addField('test', 'age', 'age');
2179
    $query->orderBy('name');
2180

    
2181
    $count = $query->countQuery()->execute()->fetchField();
2182

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

    
2185
    // Now make sure we didn't break the original query!  We should still have
2186
    // all of the fields we asked for.
2187
    $record = $query->execute()->fetch();
2188
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2189
    $this->assertEqual($record->$age_field, 27, 'Correct data retrieved.');
2190
  }
2191

    
2192
  function testHavingCountQuery() {
2193
    $query = db_select('test')
2194
      ->extend('PagerDefault')
2195
      ->groupBy('age')
2196
      ->having('age + 1 > 0');
2197
    $query->addField('test', 'age');
2198
    $query->addExpression('age + 1');
2199
    $count = count($query->execute()->fetchCol());
2200
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2201
  }
2202

    
2203
  /**
2204
   * Test that countQuery properly removes 'all_fields' statements and
2205
   * ordering clauses.
2206
   */
2207
  function testCountQueryRemovals() {
2208
    $query = db_select('test');
2209
    $query->fields('test');
2210
    $query->orderBy('name');
2211
    $count = $query->countQuery();
2212

    
2213
    // Check that the 'all_fields' statement is handled properly.
2214
    $tables = $query->getTables();
2215
    $this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.');
2216
    $tables = $count->getTables();
2217
    $this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.');
2218

    
2219
    // Check that the ordering clause is handled properly.
2220
    $orderby = $query->getOrderBy();
2221
    $this->assertEqual($orderby['name'], 'ASC', 'Query correctly sets ordering clause.');
2222
    $orderby = $count->getOrderBy();
2223
    $this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering caluse.');
2224

    
2225
    // Make sure that the count query works.
2226
    $count = $count->execute()->fetchField();
2227

    
2228
    $this->assertEqual($count, 4, 'Counted the correct number of records.');
2229
  }
2230

    
2231

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

    
2245
    $query = db_select('test');
2246
    $query->addExpression('fail');
2247
    $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions');
2248
  }
2249

    
2250
  /**
2251
   * Test that we can generate a count query from a query with distinct.
2252
   */
2253
  function testCountQueryDistinct() {
2254
    $query = db_select('test_task');
2255
    $task_field = $query->addField('test_task', 'task');
2256
    $query->distinct();
2257

    
2258
    $count = $query->countQuery()->execute()->fetchField();
2259

    
2260
    $this->assertEqual($count, 6, 'Counted the correct number of records.');
2261
  }
2262

    
2263
  /**
2264
   * Test that we can generate a count query from a query with GROUP BY.
2265
   */
2266
  function testCountQueryGroupBy() {
2267
    $query = db_select('test_task');
2268
    $pid_field = $query->addField('test_task', 'pid');
2269
    $query->groupBy('pid');
2270

    
2271
    $count = $query->countQuery()->execute()->fetchField();
2272

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

    
2275
    // Use a column alias as, without one, the query can succeed for the wrong
2276
    // reason.
2277
    $query = db_select('test_task');
2278
    $pid_field = $query->addField('test_task', 'pid', 'pid_alias');
2279
    $query->addExpression('COUNT(test_task.task)', 'count');
2280
    $query->groupBy('pid_alias');
2281
    $query->orderBy('pid_alias', 'asc');
2282

    
2283
    $count = $query->countQuery()->execute()->fetchField();
2284

    
2285
    $this->assertEqual($count, 3, 'Counted the correct number of records.');
2286
  }
2287

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

    
2301
    $job = $query->execute()->fetchField();
2302
    $this->assertEqual($job, 'Songwriter', 'Correct data retrieved.');
2303
  }
2304

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

    
2319
}
2320

    
2321
/**
2322
 * Test more complex select statements, part 2.
2323
 */
2324
class DatabaseSelectComplexTestCase2 extends DatabaseTestCase {
2325

    
2326
  public static function getInfo() {
2327
    return array(
2328
      'name' => 'Select tests, complex 2',
2329
      'description' => 'Test the Select query builder with even more complex queries.',
2330
      'group' => 'Database',
2331
    );
2332
  }
2333

    
2334
  function setUp() {
2335
    DrupalWebTestCase::setUp('database_test', 'node_access_test');
2336

    
2337
    $schema['test'] = drupal_get_schema('test');
2338
    $schema['test_people'] = drupal_get_schema('test_people');
2339
    $schema['test_one_blob'] = drupal_get_schema('test_one_blob');
2340
    $schema['test_two_blobs'] = drupal_get_schema('test_two_blobs');
2341
    $schema['test_task'] = drupal_get_schema('test_task');
2342

    
2343
    $this->installTables($schema);
2344

    
2345
    $this->addSampleData();
2346
  }
2347

    
2348
  /**
2349
   * Test that we can join on a query.
2350
   */
2351
  function testJoinSubquery() {
2352
    $acct = $this->drupalCreateUser(array('access content'));
2353
    $this->drupalLogin($acct);
2354

    
2355
    $query = db_select('test_task', 'tt', array('target' => 'slave'));
2356
    $query->addExpression('tt.pid + 1', 'abc');
2357
    $query->condition('priority', 1, '>');
2358
    $query->condition('priority', 100, '<');
2359

    
2360
    $subquery = db_select('test', 'tp');
2361
    $subquery->join('test_one_blob', 'tpb', 'tp.id = tpb.id');
2362
    $subquery->join('node', 'n', 'tp.id = n.nid');
2363
    $subquery->addTag('node_access');
2364
    $subquery->addMetaData('account', $acct);
2365
    $subquery->addField('tp', 'id');
2366
    $subquery->condition('age', 5, '>');
2367
    $subquery->condition('age', 500, '<');
2368

    
2369
    $query->leftJoin($subquery, 'sq', 'tt.pid = sq.id');
2370
    $query->join('test_one_blob', 'tb3', 'tt.pid = tb3.id');
2371

    
2372
    // Construct the query string.
2373
    // This is the same sequence that SelectQuery::execute() goes through.
2374
    $query->preExecute();
2375
    $query->getArguments();
2376
    $str = (string) $query;
2377

    
2378
    // Verify that the string only has one copy of condition placeholder 0.
2379
    $pos = strpos($str, 'db_condition_placeholder_0', 0);
2380
    $pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1);
2381
    $this->assertFalse($pos2, 'Condition placeholder is not repeated.');
2382
  }
2383
}
2384

    
2385
class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase {
2386

    
2387
  public static function getInfo() {
2388
    return array(
2389
      'name' => 'Pager query tests',
2390
      'description' => 'Test the pager query extender.',
2391
      'group' => 'Database',
2392
    );
2393
  }
2394

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

    
2409
    $correct_number = $limit;
2410
    $num_pages = floor($count / $limit);
2411

    
2412
    // If there is no remainder from rounding, subtract 1 since we index from 0.
2413
    if (!($num_pages * $limit < $count)) {
2414
      $num_pages--;
2415
    }
2416

    
2417
    for ($page = 0; $page <= $num_pages; ++$page) {
2418
      $this->drupalGet('database_test/pager_query_even/' . $limit, array('query' => array('page' => $page)));
2419
      $data = json_decode($this->drupalGetContent());
2420

    
2421
      if ($page == $num_pages) {
2422
        $correct_number = $count - ($limit * $page);
2423
      }
2424

    
2425
      $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
2426
    }
2427
  }
2428

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

    
2443
    $correct_number = $limit;
2444
    $num_pages = floor($count / $limit);
2445

    
2446
    // If there is no remainder from rounding, subtract 1 since we index from 0.
2447
    if (!($num_pages * $limit < $count)) {
2448
      $num_pages--;
2449
    }
2450

    
2451
    for ($page = 0; $page <= $num_pages; ++$page) {
2452
      $this->drupalGet('database_test/pager_query_odd/' . $limit, array('query' => array('page' => $page)));
2453
      $data = json_decode($this->drupalGetContent());
2454

    
2455
      if ($page == $num_pages) {
2456
        $correct_number = $count - ($limit * $page);
2457
      }
2458

    
2459
      $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
2460
    }
2461
  }
2462

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

    
2475
    $outer_query = db_select($query);
2476
    $outer_query->addField('subquery', 'age');
2477

    
2478
    $ages = $outer_query
2479
      ->execute()
2480
      ->fetchCol();
2481
    $this->assertEqual($ages, array(25, 26, 27, 28), 'Inner pager query returned the correct ages.');
2482
  }
2483

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

    
2498
    $ages = $query
2499
      ->execute()
2500
      ->fetchCol();
2501
    $this->assertEqual($ages, array('George', 'Ringo'), 'Pager query with having expression returned the correct ages.');
2502
  }
2503

    
2504
  /**
2505
   * Confirm that every pager gets a valid non-overlaping element ID.
2506
   */
2507
  function testElementNumbers() {
2508
    $_GET['page'] = '3, 2, 1, 0';
2509

    
2510
    $name = db_select('test', 't')->extend('PagerDefault')
2511
      ->element(2)
2512
      ->fields('t', array('name'))
2513
      ->orderBy('age')
2514
      ->limit(1)
2515
      ->execute()
2516
      ->fetchField();
2517
    $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.');
2518

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

    
2530
    $name = db_select('test', 't')->extend('PagerDefault')
2531
      ->fields('t', array('name'))
2532
      ->orderBy('age')
2533
      ->limit(1)
2534
      ->execute()
2535
      ->fetchField();
2536
    $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.');
2537

    
2538
    unset($_GET['page']);
2539
  }
2540
}
2541

    
2542

    
2543
class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase {
2544

    
2545
  public static function getInfo() {
2546
    return array(
2547
      'name' => 'Tablesort query tests',
2548
      'description' => 'Test the tablesort query extender.',
2549
      'group' => 'Database',
2550
    );
2551
  }
2552

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

    
2567
    );
2568

    
2569
    foreach ($sorts as $sort) {
2570
      $this->drupalGet('database_test/tablesort/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
2571
      $data = json_decode($this->drupalGetContent());
2572

    
2573
      $first = array_shift($data->tasks);
2574
      $last = array_pop($data->tasks);
2575

    
2576
      $this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.');
2577
      $this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.');
2578
    }
2579
  }
2580

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

    
2593
    );
2594

    
2595
    foreach ($sorts as $sort) {
2596
      $this->drupalGet('database_test/tablesort_first/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
2597
      $data = json_decode($this->drupalGetContent());
2598

    
2599
      $first = array_shift($data->tasks);
2600
      $last = array_pop($data->tasks);
2601

    
2602
      $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'])));
2603
      $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'])));
2604
    }
2605
  }
2606

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

    
2617
/**
2618
 * Select tagging tests.
2619
 *
2620
 * Tags are a way to flag queries for alter hooks so they know
2621
 * what type of query it is, such as "node_access".
2622
 */
2623
class DatabaseTaggingTestCase extends DatabaseTestCase {
2624

    
2625
  public static function getInfo() {
2626
    return array(
2627
      'name' => 'Query tagging tests',
2628
      'description' => 'Test the tagging capabilities of the Select builder.',
2629
      'group' => 'Database',
2630
    );
2631
  }
2632

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

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

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

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

    
2655
    $query->addTag('test');
2656
    $query->addTag('other');
2657

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

    
2662
  /**
2663
   * Test query tagging "has at least one of these tags" functionality.
2664
   */
2665
  function testHasAnyTag() {
2666
    $query = db_select('test');
2667
    $query->addField('test', 'name');
2668
    $query->addField('test', 'age', 'age');
2669

    
2670
    $query->addTag('test');
2671

    
2672
    $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
2673
    $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
2674
  }
2675

    
2676
  /**
2677
   * Confirm that an extended query has a "tag" added to it.
2678
   */
2679
  function testExtenderHasTag() {
2680
    $query = db_select('test')
2681
      ->extend('SelectQueryExtender');
2682
    $query->addField('test', 'name');
2683
    $query->addField('test', 'age', 'age');
2684

    
2685
    $query->addTag('test');
2686

    
2687
    $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.');
2688
    $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.');
2689
  }
2690

    
2691
  /**
2692
   * Test extended query tagging "has all of these tags" functionality.
2693
   */
2694
  function testExtenderHasAllTags() {
2695
    $query = db_select('test')
2696
      ->extend('SelectQueryExtender');
2697
    $query->addField('test', 'name');
2698
    $query->addField('test', 'age', 'age');
2699

    
2700
    $query->addTag('test');
2701
    $query->addTag('other');
2702

    
2703
    $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.');
2704
    $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.');
2705
  }
2706

    
2707
  /**
2708
   * Test extended query tagging "has at least one of these tags" functionality.
2709
   */
2710
  function testExtenderHasAnyTag() {
2711
    $query = db_select('test')
2712
      ->extend('SelectQueryExtender');
2713
    $query->addField('test', 'name');
2714
    $query->addField('test', 'age', 'age');
2715

    
2716
    $query->addTag('test');
2717

    
2718
    $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.');
2719
    $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.');
2720
  }
2721

    
2722
  /**
2723
   * Test that we can attach meta data to a query object.
2724
   *
2725
   * This is how we pass additional context to alter hooks.
2726
   */
2727
  function testMetaData() {
2728
    $query = db_select('test');
2729
    $query->addField('test', 'name');
2730
    $query->addField('test', 'age', 'age');
2731

    
2732
    $data = array(
2733
      'a' => 'A',
2734
      'b' => 'B',
2735
    );
2736

    
2737
    $query->addMetaData('test', $data);
2738

    
2739
    $return = $query->getMetaData('test');
2740
    $this->assertEqual($data, $return, 'Corect metadata returned.');
2741

    
2742
    $return = $query->getMetaData('nothere');
2743
    $this->assertNull($return, 'Non-existent key returned NULL.');
2744
  }
2745
}
2746

    
2747
/**
2748
 * Select alter tests.
2749
 *
2750
 * @see database_test_query_alter()
2751
 */
2752
class DatabaseAlterTestCase extends DatabaseTestCase {
2753

    
2754
  public static function getInfo() {
2755
    return array(
2756
      'name' => 'Query altering tests',
2757
      'description' => 'Test the hook_query_alter capabilities of the Select builder.',
2758
      'group' => 'Database',
2759
    );
2760
  }
2761

    
2762
  /**
2763
   * Test that we can do basic alters.
2764
   */
2765
  function testSimpleAlter() {
2766
    $query = db_select('test');
2767
    $query->addField('test', 'name');
2768
    $query->addField('test', 'age', 'age');
2769
    $query->addTag('database_test_alter_add_range');
2770

    
2771
    $result = $query->execute();
2772

    
2773
    $num_records = 0;
2774
    foreach ($result as $record) {
2775
      $num_records++;
2776
    }
2777

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

    
2781
  /**
2782
   * Test that we can alter the joins on a query.
2783
   */
2784
  function testAlterWithJoin() {
2785
    $query = db_select('test_task');
2786
    $tid_field = $query->addField('test_task', 'tid');
2787
    $task_field = $query->addField('test_task', 'task');
2788
    $query->orderBy($task_field);
2789
    $query->addTag('database_test_alter_add_join');
2790

    
2791
    $result = $query->execute();
2792

    
2793
    $records = $result->fetchAll();
2794

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

    
2797
    $this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.');
2798
    $this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.');
2799
    $this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.');
2800
    $this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.');
2801
    $this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.');
2802
    $this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.');
2803
  }
2804

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

    
2819
    $result = $query->execute();
2820

    
2821
    $records = $result->fetchAll();
2822

    
2823
    $this->assertEqual(count($records), 1, 'Returned the correct number of rows.');
2824
    $this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.');
2825
    $this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.');
2826
    $this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.');
2827
    $this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.');
2828
  }
2829

    
2830
  /**
2831
   * Test that we can alter the fields of a query.
2832
   */
2833
  function testAlterChangeFields() {
2834
    $query = db_select('test');
2835
    $name_field = $query->addField('test', 'name');
2836
    $age_field = $query->addField('test', 'age', 'age');
2837
    $query->orderBy('name');
2838
    $query->addTag('database_test_alter_change_fields');
2839

    
2840
    $record = $query->execute()->fetch();
2841
    $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.');
2842
    $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.');
2843
  }
2844

    
2845
  /**
2846
   * Test that we can alter expressions in the query.
2847
   */
2848
  function testAlterExpression() {
2849
    $query = db_select('test');
2850
    $name_field = $query->addField('test', 'name');
2851
    $age_field = $query->addExpression("age*2", 'double_age');
2852
    $query->condition('age', 27);
2853
    $query->addTag('database_test_alter_change_expressions');
2854
    $result = $query->execute();
2855

    
2856
    // Ensure that we got the right record.
2857
    $record = $result->fetch();
2858

    
2859
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2860
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2861
  }
2862

    
2863
  /**
2864
   * Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter().
2865
   */
2866
  function testAlterRemoveRange() {
2867
    $query = db_select('test');
2868
    $query->addField('test', 'name');
2869
    $query->addField('test', 'age', 'age');
2870
    $query->range(0, 2);
2871
    $query->addTag('database_test_alter_remove_range');
2872

    
2873
    $num_records = count($query->execute()->fetchAll());
2874

    
2875
    $this->assertEqual($num_records, 4, 'Returned the correct number of rows.');
2876
  }
2877

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

    
2892
    // Create a main query and join to sub-query.
2893
    $query = db_select('test_task', 'tt');
2894
    $query->join($subquery, 'pq', 'pq.id = tt.pid');
2895
    $age_field = $query->addField('pq', 'double_age');
2896
    $name_field = $query->addField('pq', 'name');
2897

    
2898
    $record = $query->execute()->fetch();
2899
    $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.');
2900
    $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.');
2901
  }
2902
}
2903

    
2904
/**
2905
 * Regression tests.
2906
 */
2907
class DatabaseRegressionTestCase extends DatabaseTestCase {
2908

    
2909
  public static function getInfo() {
2910
    return array(
2911
      'name' => 'Regression tests',
2912
      'description' => 'Regression tests cases for the database layer.',
2913
      'group' => 'Database',
2914
    );
2915
  }
2916

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

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

    
2937
  /**
2938
   * Test the db_table_exists() function.
2939
   */
2940
  function testDBTableExists() {
2941
    $this->assertIdentical(TRUE, db_table_exists('node'), 'Returns true for existent table.');
2942
    $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.');
2943
  }
2944

    
2945
  /**
2946
   * Test the db_field_exists() function.
2947
   */
2948
  function testDBFieldExists() {
2949
    $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), 'Returns true for existent column.');
2950
    $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), 'Returns false for nonexistent column.');
2951
  }
2952

    
2953
  /**
2954
   * Test the db_index_exists() function.
2955
   */
2956
  function testDBIndexExists() {
2957
    $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.');
2958
    $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.');
2959
  }
2960
}
2961

    
2962
/**
2963
 * Query logging tests.
2964
 */
2965
class DatabaseLoggingTestCase extends DatabaseTestCase {
2966

    
2967
  public static function getInfo() {
2968
    return array(
2969
      'name' => 'Query logging',
2970
      'description' => 'Test the query logging facility.',
2971
      'group' => 'Database',
2972
    );
2973
  }
2974

    
2975
  /**
2976
   * Test that we can log the existence of a query.
2977
   */
2978
  function testEnableLogging() {
2979
    $log = Database::startLog('testing');
2980

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

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

    
2987
    $queries = Database::getLog('testing', 'default');
2988

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

    
2991
    foreach ($queries as $query) {
2992
      $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.');
2993
    }
2994
  }
2995

    
2996
  /**
2997
   * Test that we can run two logs in parallel.
2998
   */
2999
  function testEnableMultiLogging() {
3000
    Database::startLog('testing1');
3001

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

    
3004
    Database::startLog('testing2');
3005

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

    
3008
    $queries1 = Database::getLog('testing1');
3009
    $queries2 = Database::getLog('testing2');
3010

    
3011
    $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.');
3012
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.');
3013
  }
3014

    
3015
  /**
3016
   * Test that we can log queries against multiple targets on the same connection.
3017
   */
3018
  function testEnableTargetLogging() {
3019
    // Clone the master credentials to a slave connection and to another fake
3020
    // connection.
3021
    $connection_info = Database::getConnectionInfo('default');
3022
    Database::addConnectionInfo('default', 'slave', $connection_info['default']);
3023

    
3024
    Database::startLog('testing1');
3025

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

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

    
3030
    $queries1 = Database::getLog('testing1');
3031

    
3032
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
3033
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
3034
    $this->assertEqual($queries1[1]['target'], 'slave', 'Second query used slave target.');
3035
  }
3036

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

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

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

    
3056
    $queries1 = Database::getLog('testing1');
3057

    
3058
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
3059
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
3060
    $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.');
3061
  }
3062

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

    
3072
    Database::startLog('testing1');
3073
    Database::startLog('testing1', 'test2');
3074

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

    
3077
    $old_key = db_set_active('test2');
3078

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

    
3081
    db_set_active($old_key);
3082

    
3083
    $queries1 = Database::getLog('testing1');
3084
    $queries2 = Database::getLog('testing1', 'test2');
3085

    
3086
    $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.');
3087
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
3088
  }
3089
}
3090

    
3091
/**
3092
 * Query serialization tests.
3093
 */
3094
class DatabaseSerializeQueryTestCase extends DatabaseTestCase {
3095
  public static function getInfo() {
3096
    return array(
3097
      'name' => 'Serialize query',
3098
      'description' => 'Test serializing and unserializing a query.',
3099
      'group' => 'Database',
3100
    );
3101
  }
3102

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

    
3118
/**
3119
 * Range query tests.
3120
 */
3121
class DatabaseRangeQueryTestCase extends DrupalWebTestCase {
3122
  public static function getInfo() {
3123
    return array(
3124
      'name' => 'Range query test',
3125
      'description' => 'Test the Range query functionality.',
3126
      'group' => 'Database',
3127
    );
3128
  }
3129

    
3130
  function setUp() {
3131
    parent::setUp('database_test');
3132
  }
3133

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

    
3142
    // Test if return target data.
3143
    $raw_rows = db_query('SELECT name FROM {system} ORDER BY name')->fetchAll();
3144
    $raw_rows = array_slice($raw_rows, 2, 3);
3145
    $this->assertEqual($range_rows, $raw_rows, 'Range query work and return target data.');
3146
  }
3147
}
3148

    
3149
/**
3150
 * Temporary query tests.
3151
 */
3152
class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase {
3153
  public static function getInfo() {
3154
    return array(
3155
      'name' => 'Temporary query test',
3156
      'description' => 'Test the temporary query functionality.',
3157
      'group' => 'Database',
3158
    );
3159
  }
3160

    
3161
  function setUp() {
3162
    parent::setUp('database_test');
3163
  }
3164

    
3165
  /**
3166
   * Return the number of rows of a table.
3167
   */
3168
  function countTableRows($table_name) {
3169
    return db_select($table_name)->countQuery()->execute()->fetchField();
3170
  }
3171

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

    
3186
    // Now try to run two db_query_temporary() in the same request.
3187
    $table_name_system = db_query_temporary('SELECT status FROM {system}', array());
3188
    $table_name_users = db_query_temporary('SELECT uid FROM {users}', array());
3189

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

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

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

    
3220
  function setUp() {
3221
    parent::setUp('database_test');
3222
  }
3223

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

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

    
3251
  /**
3252
   * Test escaping of LIKE wildcards.
3253
   */
3254
  function testLikeEscape() {
3255
    db_insert('test')
3256
      ->fields(array(
3257
        'name' => 'Ring_',
3258
      ))
3259
      ->execute();
3260

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

    
3277
  /**
3278
   * Test LIKE query containing a backslash.
3279
   */
3280
  function testLikeBackslash() {
3281
    db_insert('test')
3282
      ->fields(array('name'))
3283
      ->values(array(
3284
        'name' => 'abcde\f',
3285
      ))
3286
      ->values(array(
3287
        'name' => 'abc%\_',
3288
      ))
3289
      ->execute();
3290

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

    
3309
/**
3310
 * Test case sensitivity handling.
3311
 */
3312
class DatabaseCaseSensitivityTestCase extends DatabaseTestCase {
3313
  public static function getInfo() {
3314
    return array(
3315
      'name' => 'Case sensitivity',
3316
      'description' => 'Test handling case sensitive collation.',
3317
      'group' => 'Database',
3318
    );
3319
  }
3320

    
3321
  /**
3322
   * Test BINARY collation in MySQL.
3323
   */
3324
  function testCaseSensitiveInsert() {
3325
    $num_records_before = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3326

    
3327
    $john = db_insert('test')
3328
      ->fields(array(
3329
        'name' => 'john', // <- A record already exists with name 'John'.
3330
        'age' => 2,
3331
        'job' => 'Baby',
3332
      ))
3333
      ->execute();
3334

    
3335
    $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField();
3336
    $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.');
3337
    $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField();
3338
    $this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.');
3339
  }
3340
}
3341

    
3342
/**
3343
 * Test invalid data handling.
3344
 */
3345
class DatabaseInvalidDataTestCase extends DatabaseTestCase {
3346
  public static function getInfo() {
3347
    return array(
3348
      'name' => 'Invalid data',
3349
      'description' => 'Test handling of some invalid data.',
3350
      'group' => 'Database',
3351
    );
3352
  }
3353

    
3354
  function setUp() {
3355
    parent::setUp('database_test');
3356
  }
3357

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

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

    
3403
      // Ensure the other values were not inserted.
3404
      $record = db_select('test')
3405
        ->fields('test', array('name', 'age'))
3406
        ->condition('age', array(17, 75), 'IN')
3407
        ->execute()->fetchObject();
3408

    
3409
      $this->assertFalse($record, 'The rest of the insert aborted as expected.');
3410
    }
3411
  }
3412

    
3413
}
3414

    
3415
/**
3416
 * Drupal-specific SQL syntax tests.
3417
 */
3418
class DatabaseQueryTestCase extends DatabaseTestCase {
3419
  public static function getInfo() {
3420
    return array(
3421
      'name' => 'Custom query syntax tests',
3422
      'description' => 'Test Drupal\'s extended prepared statement syntax..',
3423
      'group' => 'Database',
3424
    );
3425
  }
3426

    
3427
  function setUp() {
3428
    parent::setUp('database_test');
3429
  }
3430

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

    
3437
    $this->assertEqual(count($names), 3, 'Correct number of names returned');
3438
  }
3439

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

    
3457
    // Test that the insert query that was used in the SQL injection attempt did
3458
    // not result in a row being inserted in the database.
3459
    $result = db_select('test')
3460
      ->condition('name', 'test12345678')
3461
      ->countQuery()
3462
      ->execute()
3463
      ->fetchField();
3464
    $this->assertFalse($result, 'SQL injection attempt did not result in a row being inserted in the database table.');
3465
  }
3466
}
3467

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

    
3490
  public static function getInfo() {
3491
    return array(
3492
      'name' => 'Transaction tests',
3493
      'description' => 'Test the transaction abstraction system.',
3494
      'group' => 'Database',
3495
    );
3496
  }
3497

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

    
3526
    // Insert a single row into the testing table.
3527
    db_insert('test')
3528
      ->fields(array(
3529
        'name' => 'David' . $suffix,
3530
        'age' => '24',
3531
      ))
3532
      ->execute();
3533

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

    
3536
    // We're already in a transaction, but we call ->transactionInnerLayer
3537
    // to nest another transaction inside the current one.
3538
    $this->transactionInnerLayer($suffix, $rollback, $ddl_statement);
3539

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

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

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

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

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

    
3574
    // Insert a single row into the testing table.
3575
    db_insert('test')
3576
      ->fields(array(
3577
        'name' => 'Daniel' . $suffix,
3578
        'age' => '19',
3579
      ))
3580
      ->execute();
3581

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

    
3584
    if ($ddl_statement) {
3585
      $table = array(
3586
        'fields' => array(
3587
          'id' => array(
3588
            'type' => 'serial',
3589
            'unsigned' => TRUE,
3590
            'not null' => TRUE,
3591
          ),
3592
        ),
3593
        'primary key' => array('id'),
3594
      );
3595
      db_create_table('database_test_1', $table);
3596

    
3597
      $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.');
3598
    }
3599

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

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

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

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

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

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

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

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

    
3693
    // Even in different order.
3694
    $this->cleanUp();
3695
    $transaction = db_transaction();
3696
    $this->executeDDLStatement();
3697
    $this->insertRow('row');
3698
    unset($transaction);
3699
    $this->assertRowPresent('row');
3700

    
3701
    // Even with stacking.
3702
    $this->cleanUp();
3703
    $transaction = db_transaction();
3704
    $transaction2 = db_transaction();
3705
    $this->executeDDLStatement();
3706
    unset($transaction2);
3707
    $transaction3 = db_transaction();
3708
    $this->insertRow('row');
3709
    unset($transaction3);
3710
    unset($transaction);
3711
    $this->assertRowPresent('row');
3712

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

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

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

    
3773
  /**
3774
   * Insert a single row into the testing table.
3775
   */
3776
  protected function insertRow($name) {
3777
    db_insert('test')
3778
      ->fields(array(
3779
        'name' => $name,
3780
      ))
3781
      ->execute();
3782
  }
3783

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

    
3802
  /**
3803
   * Start over for a new test.
3804
   */
3805
  protected function cleanUp() {
3806
    db_truncate('test')
3807
      ->execute();
3808
  }
3809

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

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

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

    
3851
    $database = Database::getConnection();
3852

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

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

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

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

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

    
3956

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

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

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

    
3996
  /**
3997
   * Test that the empty result set behaves as empty.
3998
   */
3999
  function testEmpty() {
4000
    $result = new DatabaseStatementEmpty();
4001

    
4002
    $this->assertTrue($result instanceof DatabaseStatementInterface, 'Class implements expected interface');
4003
    $this->assertNull($result->fetchObject(), 'Null result returned.');
4004
  }
4005

    
4006
  /**
4007
   * Test that the empty result set iterates safely.
4008
   */
4009
  function testEmptyIteration() {
4010
    $result = new DatabaseStatementEmpty();
4011

    
4012
    foreach ($result as $record) {
4013
      $this->fail('Iterating empty result set should not iterate.');
4014
      return;
4015
    }
4016

    
4017
    $this->pass('Iterating empty result set skipped iteration.');
4018
  }
4019

    
4020
  /**
4021
   * Test that the empty result set mass-fetches in an expected way.
4022
   */
4023
  function testEmptyFetchAll() {
4024
    $result = new DatabaseStatementEmpty();
4025

    
4026
    $this->assertEqual($result->fetchAll(), array(), 'Empty array returned from empty result set.');
4027
  }
4028
}
4029

    
4030
/**
4031
 * Tests management of database connections.
4032
 */
4033
class ConnectionUnitTest extends DrupalUnitTestCase {
4034

    
4035
  protected $key;
4036
  protected $target;
4037

    
4038
  protected $monitor;
4039
  protected $originalCount;
4040

    
4041
  protected $skipTest;
4042

    
4043
  public static function getInfo() {
4044
    return array(
4045
      'name' => 'Connection unit tests',
4046
      'description' => 'Tests management of database connections.',
4047
      'group' => 'Database',
4048
    );
4049
  }
4050

    
4051
  function setUp() {
4052
    parent::setUp();
4053

    
4054
    $this->key = 'default';
4055
    $this->originalTarget = 'default';
4056
    $this->target = 'DatabaseConnectionUnitTest';
4057

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

    
4070
    // Create an additional connection to monitor the connections being opened
4071
    // and closed in this test.
4072
    // @see TestBase::changeDatabasePrefix()
4073
    $connection_info = Database::getConnectionInfo('default');
4074
    Database::addConnectionInfo('default', 'monitor', $connection_info['default']);
4075
    global $databases;
4076
    $databases['default']['monitor'] = $connection_info['default'];
4077
    $this->monitor = Database::getConnection('monitor');
4078
  }
4079

    
4080
  /**
4081
   * Adds a new database connection info to Database.
4082
   */
4083
  protected function addConnection() {
4084
    // Add a new target to the connection, by cloning the current connection.
4085
    $connection_info = Database::getConnectionInfo($this->key);
4086
    Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]);
4087

    
4088
    // Verify that the new target exists.
4089
    $info = Database::getConnectionInfo($this->key);
4090
    // Note: Custom assertion message to not expose database credentials.
4091
    $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.');
4092
  }
4093

    
4094
  /**
4095
   * Returns the connection ID of the current test connection.
4096
   *
4097
   * @return integer
4098
   */
4099
  protected function getConnectionID() {
4100
    return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField();
4101
  }
4102

    
4103
  /**
4104
   * Asserts that a connection ID exists.
4105
   *
4106
   * @param integer $id
4107
   *   The connection ID to verify.
4108
   */
4109
  protected function assertConnection($id) {
4110
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
4111
    return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id)));
4112
  }
4113

    
4114
  /**
4115
   * Asserts that a connection ID does not exist.
4116
   *
4117
   * @param integer $id
4118
   *   The connection ID to verify.
4119
   */
4120
  protected function assertNoConnection($id) {
4121
    $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0);
4122
    return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id)));
4123
  }
4124

    
4125
  /**
4126
   * Tests Database::closeConnection() without query.
4127
   *
4128
   * @todo getConnectionID() executes a query.
4129
   */
4130
  function testOpenClose() {
4131
    if ($this->skipTest) {
4132
      return;
4133
    }
4134
    // Add and open a new connection.
4135
    $this->addConnection();
4136
    $id = $this->getConnectionID();
4137
    Database::getConnection($this->target, $this->key);
4138

    
4139
    // Verify that there is a new connection.
4140
    $this->assertConnection($id);
4141

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

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

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

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

    
4166
    // Execute a query.
4167
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES');
4168

    
4169
    // Close the connection.
4170
    Database::closeConnection($this->target, $this->key);
4171
    // Wait 20ms to give the database engine sufficient time to react.
4172
    usleep(20000);
4173

    
4174
    // Verify that we are back to the original connection count.
4175
    $this->assertNoConnection($id);
4176
  }
4177

    
4178
  /**
4179
   * Tests Database::closeConnection() with a query and custom prefetch method.
4180
   */
4181
  function testOpenQueryPrefetchClose() {
4182
    if ($this->skipTest) {
4183
      return;
4184
    }
4185
    // Add and open a new connection.
4186
    $this->addConnection();
4187
    $id = $this->getConnectionID();
4188
    Database::getConnection($this->target, $this->key);
4189

    
4190
    // Verify that there is a new connection.
4191
    $this->assertConnection($id);
4192

    
4193
    // Execute a query.
4194
    Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol();
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
  /**
4206
   * Tests Database::closeConnection() with a select query.
4207
   */
4208
  function testOpenSelectQueryClose() {
4209
    if ($this->skipTest) {
4210
      return;
4211
    }
4212
    // Add and open a new connection.
4213
    $this->addConnection();
4214
    $id = $this->getConnectionID();
4215
    Database::getConnection($this->target, $this->key);
4216

    
4217
    // Verify that there is a new connection.
4218
    $this->assertConnection($id);
4219

    
4220
    // Create a table.
4221
    $name = 'foo';
4222
    Database::getConnection($this->target, $this->key)->schema()->createTable($name, array(
4223
      'fields' => array(
4224
        'name' => array(
4225
          'type' => 'varchar',
4226
          'length' => 255,
4227
        ),
4228
      ),
4229
    ));
4230

    
4231
    // Execute a query.
4232
    Database::getConnection($this->target, $this->key)->select('foo', 'f')
4233
      ->fields('f', array('name'))
4234
      ->execute()
4235
      ->fetchAll();
4236

    
4237
    // Drop the table.
4238
    Database::getConnection($this->target, $this->key)->schema()->dropTable($name);
4239

    
4240
    // Close the connection.
4241
    Database::closeConnection($this->target, $this->key);
4242
    // Wait 20ms to give the database engine sufficient time to react.
4243
    usleep(20000);
4244

    
4245
    // Verify that we are back to the original connection count.
4246
    $this->assertNoConnection($id);
4247
  }
4248
}
4249

    
4250
/**
4251
 * Test reserved keyword handling (introduced for MySQL 8+)
4252
*/
4253
class DatabaseReservedKeywordTestCase extends DatabaseTestCase {
4254
  public static function getInfo() {
4255
    return array(
4256
      'name' => 'Reserved Keywords',
4257
      'description' => 'Test handling of reserved keywords.',
4258
      'group' => 'Database',
4259
    );
4260
  }
4261

    
4262
  function setUp() {
4263
    parent::setUp('database_test');
4264
  }
4265

    
4266
  public function testTableNameQuoting() {
4267
    // Test db_query with {table} pattern.
4268
    $record = db_query('SELECT * FROM {system} LIMIT 1')->fetchObject();
4269
    $this->assertTrue(isset($record->filename), 'Successfully queried the {system} table.');
4270

    
4271
    $connection = Database::getConnection()->getConnectionOptions();
4272
    if ($connection['driver'] === 'sqlite') {
4273
      // In SQLite simpletest's prefixed db tables exist in their own schema
4274
      // (e.g. simpletest124904.system), so we cannot test the schema.{table}
4275
      // syntax here as the table name will have the schema name prepended to it
4276
      // when prefixes are processed.
4277
      $this->assert(TRUE, 'Skipping schema.{system} test for SQLite.');
4278
    }
4279
    else {
4280
      $database = $connection['database'];
4281
      // Test db_query with schema.{table} pattern
4282
      db_query('SELECT * FROM ' . $database . '.{system} LIMIT 1')->fetchObject();
4283
      $this->assertTrue(isset($record->filename), 'Successfully queried the schema.{system} table.');
4284
    }
4285
  }
4286

    
4287
  public function testSelectReservedWordTableCount() {
4288
    $rows = db_select('virtual')
4289
      ->countQuery()
4290
      ->execute()
4291
      ->fetchField();
4292
    $this->assertEqual($rows, 1, 'Successful count query on a table with a reserved name.');
4293
  }
4294

    
4295
  public function testSelectReservedWordTableSpecificField() {
4296
    $record = db_select('virtual')
4297
      ->fields('virtual', array('function'))
4298
      ->execute()
4299
      ->fetchAssoc();
4300
    $this->assertEqual($record['function'], 'Function value 1', 'Successfully read a field from a table with a name and column which are reserved words.');
4301
  }
4302

    
4303
  public function testSelectReservedWordTableAllFields() {
4304
    $record = db_select('virtual')
4305
      ->fields('virtual')
4306
      ->execute()
4307
      ->fetchAssoc();
4308
    $this->assertEqual($record['function'], 'Function value 1', 'Successful all_fields query from a table with a name and column which are reserved words.');
4309
  }
4310

    
4311
  public function testSelectReservedWordAliasCount() {
4312
    $rows = db_select('test', 'character')
4313
      ->countQuery()
4314
      ->execute()
4315
      ->fetchField();
4316
    $this->assertEqual($rows, 4, 'Successful count query using an alias which is a reserved word.');
4317
  }
4318

    
4319
  public function testSelectReservedWordAliasSpecificFields() {
4320
    $record = db_select('test', 'high_priority')
4321
      ->fields('high_priority', array('name'))
4322
      ->condition('age', 27)
4323
      ->execute()->fetchAssoc();
4324
    $this->assertEqual($record['name'], 'George', 'Successful query using an alias which is a reserved word.');
4325
  }
4326

    
4327
  public function testSelectReservedWordAliasAllFields() {
4328
    $record = db_select('test', 'high_priority')
4329
      ->fields('high_priority')
4330
      ->condition('age', 27)
4331
      ->execute()->fetchAssoc();
4332
    $this->assertEqual($record['name'], 'George', 'Successful all_fields query using an alias which is a reserved word.');
4333
  }
4334

    
4335
  public function testInsertReservedWordTable() {
4336
    $num_records_before = db_query('SELECT COUNT(*) FROM {virtual}')->fetchField();
4337
    db_insert('virtual')
4338
      ->fields(array(
4339
        'function' => 'Inserted function',
4340
      ))
4341
      ->execute();
4342
    $num_records_after = db_query('SELECT COUNT(*) FROM {virtual}')->fetchField();
4343
    $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Successful insert into a table with a name and column which are reserved words.');
4344
  }
4345

    
4346
  public function testDeleteReservedWordTable() {
4347
    $delete = db_delete('virtual')
4348
      ->condition('function', 'Function value 1');
4349
    $num_deleted = $delete->execute();
4350
    $this->assertEqual($num_deleted, 1, "Deleted 1 record from a table with a name and column which are reserved words..");
4351
  }
4352

    
4353
  function testTruncateReservedWordTable() {
4354
    db_truncate('virtual')->execute();
4355
    $num_records_after = db_query("SELECT COUNT(*) FROM {virtual}")->fetchField();
4356
    $this->assertEqual(0, $num_records_after, 'Truncated a table with a reserved name.');
4357
  }
4358

    
4359
  function testUpdateReservedWordTable() {
4360
    $num_updated = db_update('virtual')
4361
      ->fields(array('function' => 'Updated function'))
4362
      ->execute();
4363
    $this->assertIdentical($num_updated, 1, 'Updated 1 record in a table with a name and column which are reserved words.');
4364
  }
4365

    
4366
  function testMergeReservedWordTable() {
4367
    $key = db_query('SELECT id FROM {virtual} LIMIT 1')->fetchField();
4368
    $num_records_before = db_query('SELECT COUNT(*) FROM {virtual}')->fetchField();
4369
    db_merge('virtual')
4370
      ->key(array('id' => $key))
4371
      ->fields(array('function' => 'Merged function'))
4372
      ->execute();
4373
    $num_records_after = db_query('SELECT COUNT(*) FROM {virtual}')->fetchField();
4374
    $this->assertIdentical($num_records_before, $num_records_after, 'Successful merge query on a table with a name and column which are reserved words.');
4375
  }
4376
}
4377

    
4378
/**
4379
 * Test table prefix handling.
4380
*/
4381
class DatabaseTablePrefixTestCase extends DatabaseTestCase {
4382
  public static function getInfo() {
4383
    return array(
4384
      'name' => 'Table prefixes',
4385
      'description' => 'Test handling of table prefixes.',
4386
      'group' => 'Database',
4387
    );
4388
  }
4389

    
4390
  public function testSchemaDotTablePrefixes() {
4391
    // Get a copy of the default connection options.
4392
    $db = Database::getConnection('default', 'default');
4393
    $connection_options = $db->getConnectionOptions();
4394

    
4395
    if ($connection_options['driver'] === 'sqlite') {
4396
      // In SQLite simpletest's prefixed db tables exist in their own schema
4397
      // (e.g. simpletest124904.system), so we cannot test the schema.table
4398
      // prefix syntax here.
4399
      $this->assert(TRUE, 'Skipping schema.table prefixed tables test for SQLite.');
4400
      return;
4401
    }
4402

    
4403
    $db_name = $connection_options['database'];
4404
    // This prefix is usually something like simpletest12345
4405
    $test_prefix = $connection_options['prefix']['default'];
4406

    
4407
    // Set up a new connection with table prefixes in the form "schema.table"
4408
    $prefixed = $connection_options;
4409
    $prefixed['prefix'] = array(
4410
      'default' => $test_prefix,
4411
      'users' => $db_name . '.' . $test_prefix,
4412
      'role' => $db_name . '.' . $test_prefix,
4413
    );
4414
    Database::addConnectionInfo('default', 'prefixed', $prefixed);
4415

    
4416
    // Test that the prefixed database connection can query the prefixed tables.
4417
    $num_users_prefixed = Database::getConnection('prefixed', 'default')->query('SELECT COUNT(1) FROM {users}')->fetchField();
4418
    $this->assertTrue((int) $num_users_prefixed > 0, 'Successfully queried the users table using a schema.table prefix');
4419
    $num_users_default = Database::getConnection('default', 'default')->query('SELECT COUNT(1) FROM {users}')->fetchField();
4420
    $this->assertEqual($num_users_default, $num_users_prefixed, 'Verified results of query using a connection with schema.table prefixed tables');
4421
  }
4422
}