drupal/modules/simpletest/tests/database_test.test

2570 lines
88 KiB
Plaintext

<?php
// $Id$
/**
* Dummy class for fetching into a class.
*
* PDO supports using a new instance of an arbitrary class for records
* rather than just a stdClass or array. This class is for testing that
* functionality. (See testQueryFetchClass() below)
*/
class FakeRecord { }
/**
* Base test class for databases.
*
* Because all database tests share the same test data, we can centralize that
* here.
*/
class DatabaseTestCase extends DrupalWebTestCase {
function setUp() {
parent::setUp('database_test');
$schema['test'] = drupal_get_schema('test');
$schema['test_people'] = drupal_get_schema('test_people');
$schema['test_one_blob'] = drupal_get_schema('test_one_blob');
$schema['test_two_blobs'] = drupal_get_schema('test_two_blobs');
$schema['test_task'] = drupal_get_schema('test_task');
$this->installTables($schema);
$this->addSampleData();
}
/**
* Set up several tables needed by a certain test.
*
* @param $schema
* An array of table definitions to install.
*/
function installTables($schema) {
// This ends up being a test for table drop and create, too, which is nice.
$ret = array();
foreach ($schema as $name => $data) {
if (db_table_exists($name)) {
db_drop_table($ret, $name);
}
db_create_table($ret, $name, $data);
}
foreach ($schema as $name => $data) {
$this->assertTrue(db_table_exists($name), t('Table @name created successfully.', array('@name' => $name)));
}
}
/**
* Set up tables for NULL handling.
*/
function ensureSampleDataNull() {
$schema['test_null'] = drupal_get_schema('test_null');
$this->installTables($schema);
db_insert('test_null')
->fields(array('name', 'age'))
->values(array(
'name' => 'Kermit',
'age' => 25,
))
->values(array(
'name' => 'Fozzie',
'age' => NULL,
))
->values(array(
'name' => 'Gonzo',
'age' => 27,
))
->execute();
}
/**
* Setup our sample data.
*
* These are added using db_query(), since we're not trying to test the
* INSERT operations here, just populate.
*/
function addSampleData() {
// We need the IDs, so we can't use a multi-insert here.
$john = db_insert('test')
->fields(array(
'name' => 'John',
'age' => 25,
'job' => 'Singer',
))
->execute();
$george = db_insert('test')
->fields(array(
'name' => 'George',
'age' => 27,
'job' => 'Singer',
))
->execute();
$ringo = db_insert('test')
->fields(array(
'name' => 'Ringo',
'age' => 28,
'job' => 'Drummer',
))
->execute();
$paul = db_insert('test')
->fields(array(
'name' => 'Paul',
'age' => 26,
'job' => 'Songwriter',
))
->execute();
db_insert('test_people')
->fields(array(
'name' => 'Meredith',
'age' => 30,
'job' => 'Speaker',
))
->execute();
db_insert('test_task')
->fields(array('pid', 'task', 'priority'))
->values(array(
'pid' => $john,
'task' => 'eat',
'priority' => 3,
))
->values(array(
'pid' => $john,
'task' => 'sleep',
'priority' => 4,
))
->values(array(
'pid' => $john,
'task' => 'code',
'priority' => 1,
))
->values(array(
'pid' => $george,
'task' => 'sing',
'priority' => 2,
))
->values(array(
'pid' => $george,
'task' => 'sleep',
'priority' => 2,
))
->values(array(
'pid' => $paul,
'task' => 'found new band',
'priority' => 1,
))
->values(array(
'pid' => $paul,
'task' => 'perform at superbowl',
'priority' => 3,
))
->execute();
}
}
/**
* Test connection management.
*/
class DatabaseConnectionTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Connection tests'),
'description' => t('Tests of the core database system.'),
'group' => t('Database'),
);
}
/**
* Test that connections return appropriate connection objects.
*/
function testConnectionRouting() {
// Clone the master credentials to a slave connection.
// Note this will result in two independent connection objects that happen
// to point to the same place.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('default', 'slave', $connection_info['default']);
$db1 = Database::getConnection('default', 'default');
$db2 = Database::getConnection('slave', 'default');
$this->assertNotNull($db1, t('default connection is a real connection object.'));
$this->assertNotNull($db2, t('slave connection is a real connection object.'));
$this->assertNotIdentical($db1, $db2, t('Each target refers to a different connection.'));
// Try to open those targets another time, that should return the same objects.
$db1b = Database::getConnection('default', 'default');
$db2b = Database::getConnection('slave', 'default');
$this->assertIdentical($db1, $db1b, t('A second call to getConnection() returns the same object.'));
$this->assertIdentical($db2, $db2b, t('A second call to getConnection() returns the same object.'));
// Try to open an unknown target.
$unknown_target = $this->randomName();
$db3 = Database::getConnection($unknown_target, 'default');
$this->assertNotNull($db3, t('Opening an unknown target returns a real connection object.'));
$this->assertIdentical($db1, $db3, t('An unknown target opens the default connection.'));
// Try to open that unknown target another time, that should return the same object.
$db3b = Database::getConnection($unknown_target, 'default');
$this->assertIdentical($db3, $db3b, t('A second call to getConnection() returns the same object.'));
}
/**
* Test that connections return appropriate connection objects.
*/
function testConnectionRoutingOverride() {
// Clone the master credentials to a slave connection.
// Note this will result in two independent connection objects that happen
// to point to the same place.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('default', 'slave', $connection_info['default']);
Database::ignoreTarget('default', 'slave');
$db1 = Database::getConnection('default', 'default');
$db2 = Database::getConnection('slave', 'default');
$this->assertIdentical($db1, $db2, t('Both targets refer to the same connection.'));
}
}
/**
* Test fetch actions, part 1.
*
* We get timeout errors if we try to run too many tests at once.
*/
class DatabaseFetchTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Fetch tests'),
'description' => t('Test the Database system\'s various fetch capabilities.'),
'group' => t('Database'),
);
}
/**
* Confirm that we can fetch a record properly in default object mode.
*/
function testQueryFetchDefault() {
$records = array();
$result = db_query("SELECT name FROM {test} WHERE age = :age", array(':age' => 25));
$this->assertTrue($result instanceof DatabaseStatementInterface, t('Result set is a Drupal statement object.'));
foreach ($result as $record) {
$records[] = $record;
$this->assertTrue(is_object($record), t('Record is an object.'));
$this->assertIdentical($record->name, 'John', t('25 year old is John.'));
}
$this->assertIdentical(count($records), 1, t('There is only one record.'));
}
/**
* Confirm that we can fetch a record to an object explicitly.
*/
function testQueryFetchObject() {
$records = array();
$result = db_query("SELECT name FROM {test} WHERE age = :age", array(':age' => 25), array('fetch' => PDO::FETCH_OBJ));
foreach ($result as $record) {
$records[] = $record;
$this->assertTrue(is_object($record), t('Record is an object.'));
$this->assertIdentical($record->name, 'John', t('25 year old is John.'));
}
$this->assertIdentical(count($records), 1, t('There is only one record.'));
}
/**
* Confirm that we can fetch a record to an array associative explicitly.
*/
function testQueryFetchArray() {
$records = array();
$result = db_query("SELECT name FROM {test} WHERE age = :age", array(':age' => 25), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $record) {
$records[] = $record;
if ($this->assertTrue(is_array($record), t('Record is an array.'))) {
$this->assertIdentical($record['name'], 'John', t('Record can be accessed associatively.'));
}
}
$this->assertIdentical(count($records), 1, t('There is only one record.'));
}
/**
* Confirm that we can fetch a record into a new instance of a custom class.
*
* @see FakeRecord
*/
function testQueryFetchClass() {
$records = array();
$result = db_query("SELECT name FROM {test} WHERE age = :age", array(':age' => 25), array('fetch' => 'FakeRecord'));
foreach ($result as $record) {
$records[] = $record;
if ($this->assertTrue($record instanceof FakeRecord, t('Record is an object of class FakeRecord.'))) {
$this->assertIdentical($record->name, 'John', t('25 year old is John.'));
}
}
$this->assertIdentical(count($records), 1, t('There is only one record.'));
}
}
/**
* Test fetch actions, part 2.
*
* We get timeout errors if we try to run too many tests at once.
*/
class DatabaseFetch2TestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Fetch tests, part 2'),
'description' => t('Test the Database system\'s various fetch capabilities.'),
'group' => t('Database'),
);
}
function setUp() {
parent::setUp();
}
// Confirm that we can fetch a record into an indexed array explicitly.
function testQueryFetchNum() {
$records = array();
$result = db_query("SELECT name FROM {test} WHERE age = :age", array(':age' => 25), array('fetch' => PDO::FETCH_NUM));
foreach ($result as $record) {
$records[] = $record;
if ($this->assertTrue(is_array($record), t('Record is an array.'))) {
$this->assertIdentical($record[0], 'John', t('Record can be accessed numerically.'));
}
}
$this->assertIdentical(count($records), 1, 'There is only one record');
}
/**
* Confirm that we can fetch a record into a doubly-keyed array explicitly.
*/
function testQueryFetchBoth() {
$records = array();
$result = db_query("SELECT name FROM {test} WHERE age = :age", array(':age' => 25), array('fetch' => PDO::FETCH_BOTH));
foreach ($result as $record) {
$records[] = $record;
if ($this->assertTrue(is_array($record), t('Record is an array.'))) {
$this->assertIdentical($record[0], 'John', t('Record can be accessed numerically.'));
$this->assertIdentical($record['name'], 'John', t('Record can be accessed associatively.'));
}
}
$this->assertIdentical(count($records), 1, t('There is only one record.'));
}
/**
* Confirm that we can fetch an entire column of a result set at once.
*/
function testQueryFetchCol() {
$records = array();
$result = db_query("SELECT name FROM {test} WHERE age > :age", array(':age' => 25));
$column = $result->fetchCol();
$this->assertIdentical(count($column), 3, t('fetchCol() returns the right number of records.'));
$result = db_query("SELECT name FROM {test} WHERE age > :age", array(':age' => 25));
$i = 0;
foreach ($result as $record) {
$this->assertIdentical($record->name, $column[$i++], t('Column matches direct accesss.'));
}
}
}
/**
* Test the insert builder.
*/
class DatabaseInsertTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Insert tests'),
'description' => t('Test the Insert query builder.'),
'group' => t('Database'),
);
}
/**
* Test the very basic insert functionality.
*/
function testSimpleInsert() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$query = db_insert('test');
$query->fields(array(
'name' => 'Yoko',
'age' => '29',
));
$query->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$this->assertIdentical($num_records_before + 1, (int)$num_records_after, t('Record inserts correctly.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Yoko'))->fetchField();
$this->assertIdentical($saved_age, '29', t('Can retrieve after inserting.'));
}
/**
* Test that we can insert multiple records in one query object.
*/
function testMultiInsert() {
$num_records_before = (int) db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$query = db_insert('test');
$query->fields(array(
'name' => 'Larry',
'age' => '30',
));
// We should be able to specify values in any order if named.
$query->values(array(
'age' => '31',
'name' => 'Curly',
));
// We should be able to say "use the field order".
// This is not the recommended mechanism for most cases, but it should work.
$query->values(array('Moe', '32'));
$query->execute();
$num_records_after = (int) db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$this->assertIdentical($num_records_before + 3, $num_records_after, t('Record inserts correctly.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Larry'))->fetchField();
$this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Curly'))->fetchField();
$this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Moe'))->fetchField();
$this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.'));
}
/**
* Test that an insert object can be reused with new data after it executes.
*/
function testRepeatedInsert() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$query = db_insert('test');
$query->fields(array(
'name' => 'Larry',
'age' => '30',
));
$query->execute(); // This should run the insert, but leave the fields intact.
// We should be able to specify values in any order if named.
$query->values(array(
'age' => '31',
'name' => 'Curly',
));
$query->execute();
// We should be able to say "use the field order".
$query->values(array('Moe', '32'));
$query->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, t('Record inserts correctly.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Larry'))->fetchField();
$this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Curly'))->fetchField();
$this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Moe'))->fetchField();
$this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.'));
}
/**
* Test that we can specify fields without values and specify values later.
*/
function testInsertFieldOnlyDefinintion() {
// This is useful for importers, when we want to create a query and define
// its fields once, then loop over a multi-insert execution.
db_insert('test')
->fields(array('name', 'age'))
->values(array('Larry', '30'))
->values(array('Curly', '31'))
->values(array('Moe', '32'))
->execute();
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Larry'))->fetchField();
$this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Curly'))->fetchField();
$this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Moe'))->fetchField();
$this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.'));
}
/**
* Test that inserts return the proper auto-increment ID.
*/
function testInsertLastInsertID() {
$id = db_insert('test')->fields(array(
'name' => 'Larry',
'age' => '30',
))
->execute();
$this->assertIdentical($id, '5', t('Auto-increment ID returned successfully.'));
}
}
/**
* Insert tests using LOB fields, which are weird on some databases.
*/
class DatabaseInsertLOBTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Insert tests, LOB fields'),
'description' => t('Test the Insert query builder with LOB fields.'),
'group' => t('Database'),
);
}
/**
* Test that we can insert a single blob field successfully.
*/
function testInsertOneBlob() {
$data = "This is\000a test.";
$this->assertTrue(strlen($data) === 15, t('Test data contains a NULL.'));
$id = db_insert('test_one_blob')->fields(array('blob1' => $data))->execute();
$res = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id));
$r = db_fetch_array($res);
$this->assertTrue($r['blob1'] === $data, t('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r))));
}
/**
* Test that we can insert multiple blob fields in the same query.
*/
function testInsertMultipleBlob() {
$id = db_insert('test_two_blobs')->fields(array(
'blob1' => 'This is',
'blob2' => 'a test',
))
->execute();
$res = db_query('SELECT * FROM {test_two_blobs} WHERE id = %d', $id);
$r = db_fetch_array($res);
$this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', t('Can insert multiple blobs per row.'));
}
}
/**
* Insert tests for "database default" values.
*/
class DatabaseInsertDefaultsTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Insert tests, default fields'),
'description' => t('Test the Insert query builder with default values.'),
'group' => t('Database'),
);
}
/**
* Test that we can run a query that is "default values for everything".
*/
function testDefaultInsert() {
$query = db_insert('test')->useDefaults(array('job'));
$id = $query->execute();
$schema = drupal_get_schema('test');
$job = db_query("SELECT job FROM {test} WHERE id = :id", array(':id' => $id))->fetchField();
$this->assertEqual($job, $schema['fields']['job']['default'], t('Default field value is set.'));
}
/**
* Test that no action will be preformed if no fields are specified.
*/
function testDefaultEmptyInsert() {
$num_records_before = (int) db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$result = db_insert('test')->execute();
$this->assertNull($result, t('Return NULL as no fields are specified.'));
$num_records_after = (int) db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$this->assertIdentical($num_records_before, $num_records_after, t('Do nothing as no fields are specified.'));
}
/**
* Test that we can insert fields with values and defaults in the same query.
*/
function testDefaultInsertWithFields() {
$query = db_insert('test')->fields(array('name' => 'Bob'))->useDefaults(array('job'));
$id = $query->execute();
$schema = drupal_get_schema('test');
$job = db_query("SELECT job FROM {test} WHERE id = :id", array(':id' => $id))->fetchField();
$this->assertEqual($job, $schema['fields']['job']['default'], t('Default field value is set.'));
}
}
/**
* Update builder tests.
*/
class DatabaseUpdateTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Update tests'),
'description' => t('Test the Update query builder.'),
'group' => t('Database'),
);
}
/**
* Confirm that we can update a single record successfully.
*/
function testSimpleUpdate() {
$num_updated = db_update('test')->fields(array('name' => 'Tiffany'))->condition('id', 1)->execute();
$this->assertIdentical($num_updated, 1, t('Updated 1 record.'));
$saved_name = db_query("SELECT name FROM {test} WHERE id = :id", array(':id' => 1))->fetchField();
$this->assertIdentical($saved_name, 'Tiffany', t('Updated name successfully.'));
}
/**
* Confirm that we can update a multiple records successfully.
*/
function testMultiUpdate() {
$num_updated = db_update('test')->fields(array('job' => 'Musician'))->condition('job', 'Singer')->execute();
$this->assertIdentical($num_updated, 2, t('Updated 2 records.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', t('Updated fields successfully.'));
}
/**
* Confirm that we can update a multiple records with a non-equality condition.
*/
function testMultiGTUpdate() {
$num_updated = db_update('test')->fields(array('job' => 'Musician'))->condition('age', 26, '>')->execute();
$this->assertIdentical($num_updated, 2, t('Updated 2 records.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', t('Updated fields successfully.'));
}
/**
* Confirm that we can update a multiple records with a where call.
*/
function testWhereUpdate() {
$num_updated = db_update('test')->fields(array('job' => 'Musician'))->where('age > :age', array(':age' => 26))->execute();
$this->assertIdentical($num_updated, 2, t('Updated 2 records.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', t('Updated fields successfully.'));
}
/**
* Confirm that we can stack condition and where calls.
*/
function testWhereAndConditionUpdate() {
$update = db_update('test')->fields(array('job' => 'Musician'))->where('age > :age', array(':age' => 26))->condition('name', 'Ringo');
$num_updated = $update->execute();
$this->assertIdentical($num_updated, 1, t('Updated 1 record.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '1', t('Updated fields successfully.'));
}
}
/**
* Tests for more complex update statements.
*/
class DatabaseUpdateComplexTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Update tests, Complex'),
'description' => t('Test the Update query builder, complex queries.'),
'group' => t('Database'),
);
}
/**
* Test updates with OR conditionals.
*/
function testOrConditionUpdate() {
$update = db_update('test')
->fields(array('job' => 'Musician'))
->condition(db_or()
->condition('name', 'John')
->condition('name', 'Paul')
);
$num_updated = $update->execute();
$this->assertIdentical($num_updated, 2, t('Updated 2 records.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', t('Updated fields successfully.'));
}
/**
* Test WHERE IN clauses.
*/
function testInConditionUpdate() {
$num_updated = db_update('test')->fields(array('job' => 'Musician'))
->condition('name', array('John', 'Paul'), 'IN')
->execute();
$this->assertIdentical($num_updated, 2, t('Updated 2 records.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', t('Updated fields successfully.'));
}
/**
* Test WHERE NOT IN clauses.
*/
function testNotInConditionUpdate() {
$num_updated = db_update('test')->fields(array('job' => 'Musician'))
->condition('name', array('John', 'Paul', 'George'), 'NOT IN')
->execute();
$this->assertIdentical($num_updated, 1, t('Updated 1 record.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '1', t('Updated fields successfully.'));
}
/**
* Test BETWEEN conditional clauses.
*/
function testBetweenConditionUpdate() {
$num_updated = db_update('test')->fields(array('job' => 'Musician'))
->condition('age', array(25, 26), 'BETWEEN')
->execute();
$this->assertIdentical($num_updated, 2, t('Updated 2 records.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '2', t('Updated fields successfully.'));
}
/**
* Test LIKE conditionals.
*/
function testLikeConditionUpdate() {
$num_updated = db_update('test')->fields(array('job' => 'Musician'))
->condition('name', '%ge%', 'LIKE')
->execute();
$this->assertIdentical($num_updated, 1, t('Updated 1 record.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '1', t('Updated fields successfully.'));
}
/**
* Test update with expression values.
*/
function testUpdateExpression() {
$before_age = db_query("SELECT age FROM {test} WHERE name = 'Ringo'")->fetchField();
$GLOBALS['larry_test'] = 1;
$num_updated = db_update('test')
->condition('name', 'Ringo')
->fields(array('job' => 'Musician'))
->expression('age', 'age + :age',array(':age' => 4))
->execute();
$this->assertIdentical($num_updated, 1, t('Updated 1 record.'));
$num_matches = db_query("SELECT COUNT(*) FROM {test} WHERE job = :job", array(':job' => 'Musician'))->fetchField();
$this->assertIdentical($num_matches, '1', t('Updated fields successfully.'));
$person = db_query("SELECT * FROM {test} WHERE name = :name", array(':name' => 'Ringo'))->fetch();
$this->assertEqual($person->name, 'Ringo', t('Name set correctly.'));
$this->assertEqual($person->age, $before_age + 4, t('Age set correctly.'));
$this->assertEqual($person->job, 'Musician', t('Job set correctly.'));
$GLOBALS['larry_test'] = 0;
}
/**
* Test update with only expression values.
*/
function testUpdateOnlyExpression() {
$before_age = db_query("SELECT age FROM {test} WHERE name = 'Ringo'")->fetchField();
$num_updated = db_update('test')
->condition('name', 'Ringo')
->expression('age', 'age + :age',array(':age' => 4))
->execute();
$this->assertIdentical($num_updated, 1, t('Updated 1 record.'));
$after_age = db_query("SELECT age FROM {test} WHERE name = 'Ringo'")->fetchField();
$this->assertEqual($before_age + 4, $after_age, t('Age updated correctly'));
}
}
/**
* Test update queries involving LOB values.
*/
class DatabaseUpdateLOBTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Update tests, LOB'),
'description' => t('Test the Update query builder with LOB fields.'),
'group' => t('Database'),
);
}
/**
* Confirm that we can update a blob column.
*/
function testUpdateOneBlob() {
$data = "This is\000a test.";
$this->assertTrue(strlen($data) === 15, t('Test data contains a NULL.'));
$id = db_insert('test_one_blob')->fields(array('blob1' => $data))->execute();
$data .= $data;
db_update('test_one_blob')->condition('id', $id)->fields(array('blob1' => $data))->execute();
$res = db_query('SELECT * FROM {test_one_blob} WHERE id = %d', $id);
$r = db_fetch_array($res);
$this->assertTrue($r['blob1'] === $data, t('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r))));
}
/**
* Confirm that we can update two blob columns in the same table.
*/
function testUpdateMultipleBlob() {
$id = db_insert('test_two_blobs')->fields(array(
'blob1' => 'This is',
'blob2' => 'a test')
)
->execute();
db_update('test_two_blobs')->condition('id', $id)->fields(array('blob1' => 'and so', 'blob2' => 'is this'))->execute();
$res = db_query('SELECT * FROM {test_two_blobs} WHERE id = %d', $id);
$r = db_fetch_array($res);
$this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', t('Can update multiple blobs per row.'));
}
}
/**
* Delete tests.
*
* The DELETE tests are not as extensive, as all of the interesting code for
* DELETE queries is in the conditional which is identical to the UPDATE and
* SELECT conditional handling.
*
*/
class DatabaseDeleteTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Delete tests'),
'description' => t('Test the Delete query builder.'),
'group' => t('Database'),
);
}
/**
* Confirm that we can delete a single record successfully.
*/
function testSimpleDelete() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$num_deleted = db_delete('test')->condition('id', 1)->execute();
$this->assertIdentical($num_deleted, 1, t('Deleted 1 record.'));
$num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$this->assertEqual($num_records_before, $num_records_after + $num_deleted, t('Deletion adds up.'));
}
}
/**
* Test the MERGE query builder.
*/
class DatabaseMergeTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Merge tests'),
'description' => t('Test the Merge query builder.'),
'group' => t('Database'),
);
}
/**
* Confirm that we can merge-insert a record successfully.
*/
function testMergeInsert() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
db_merge('test_people')
->key(array('job' => 'Presenter'))
->fields(array(
'age' => 31,
'name' => 'Tiffany',
))
->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$this->assertEqual($num_records_before + 1, $num_records_after, t('Merge inserted properly.'));
$person = db_query("SELECT * FROM {test_people} WHERE job = :job", array(':job' => 'Presenter'))->fetch();
$this->assertEqual($person->name, 'Tiffany', t('Name set correctly.'));
$this->assertEqual($person->age, 31, t('Age set correctly.'));
$this->assertEqual($person->job, 'Presenter', t('Job set correctly.'));
}
/**
* Confirm that we can merge-update a record successfully.
*/
function testMergeUpdate() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
db_merge('test_people')->key(array('job' => 'Speaker'))->fields(array('age' => 31, 'name' => 'Tiffany'))->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.'));
$person = db_query("SELECT * FROM {test_people} WHERE job = :job", array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Tiffany', t('Name set correctly.'));
$this->assertEqual($person->age, 31, t('Age set correctly.'));
$this->assertEqual($person->job, 'Speaker', t('Job set correctly.'));
}
/**
* Confirm that we can merge-update a record successfully, with exclusion.
*/
function testMergeUpdateExcept() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
db_merge('test_people')->key(array('job' => 'Speaker'))->fields(array('age' => 31, 'name' => 'Tiffany'))->updateExcept('age')->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.'));
$person = db_query("SELECT * FROM {test_people} WHERE job = :job", array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Tiffany', t('Name set correctly.'));
$this->assertEqual($person->age, 30, t('Age skipped correctly.'));
$this->assertEqual($person->job, 'Speaker', t('Job set correctly.'));
}
/**
* Confirm that we can merge-update a record successfully, with alternate replacement.
*/
function testMergeUpdateExplicit() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
db_merge('test_people')->key(array('job' => 'Speaker'))->fields(array('age' => 31, 'name' => 'Tiffany'))->update(array('name' => 'Joe'))->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.'));
$person = db_query("SELECT * FROM {test_people} WHERE job = :job", array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Joe', t('Name set correctly.'));
$this->assertEqual($person->age, 30, t('Age skipped correctly.'));
$this->assertEqual($person->job, 'Speaker', t('Job set correctly.'));
}
/**
* Confirm that we can merge-update a record successfully, with expressions.
*/
function testMergeUpdateExpression() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$age_before = db_query("SELECT age FROM {test_people} WHERE job = 'Speaker'")->fetchField();
// This is a very contrived example, as I have no idea why you'd want to
// change age this way, but that's beside the point.
// Note that we are also double-setting age here, once as a literal and
// once as an expression. This test will only pass if the expression wins,
// which is what is supposed to happen.
db_merge('test_people')
->key(array('job' => 'Speaker'))
->fields(array('age' => 31, 'name' => 'Tiffany'))
->expression('age', 'age + :age', array(':age' => 4))
->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.'));
$person = db_query("SELECT * FROM {test_people} WHERE job = :job", array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Tiffany', t('Name set correctly.'));
$this->assertEqual($person->age, $age_before + 4, t('Age updated correctly.'));
$this->assertEqual($person->job, 'Speaker', t('Job set correctly.'));
}
/**
* Test that we can merge-insert without any update fields.
*/
function testMergeInsertWithoutUpdate() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
db_merge('test_people')
->key(array('job' => 'Presenter'))
->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$this->assertEqual($num_records_before + 1, $num_records_after, t('Merge inserted properly.'));
$person = db_query("SELECT * FROM {test_people} WHERE job = :job", array(':job' => 'Presenter'))->fetch();
$this->assertEqual($person->name, '', t('Name set correctly.'));
$this->assertEqual($person->age, 0, t('Age set correctly.'));
$this->assertEqual($person->job, 'Presenter', t('Job set correctly.'));
}
/**
* Confirm that we can merge-update without any update fields.
*/
function testMergeUpdateWithoutUpdate() {
$num_records_before = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
db_merge('test_people')
->key(array('job' => 'Speaker'))
->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$this->assertEqual($num_records_before, $num_records_after, t('Merge skipped properly.'));
$person = db_query("SELECT * FROM {test_people} WHERE job = :job", array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Meredith', t('Name skipped correctly.'));
$this->assertEqual($person->age, 30, t('Age skipped correctly.'));
$this->assertEqual($person->job, 'Speaker', t('Job skipped correctly.'));
db_merge('test_people')
->key(array('job' => 'Speaker'))
->fields(array('age' => 31))
->updateExcept(array('age'))
->execute();
$num_records_after = db_query("SELECT COUNT(*) FROM {test_people}")->fetchField();
$this->assertEqual($num_records_before, $num_records_after, t('Merge skipped properly.'));
$person = db_query("SELECT * FROM {test_people} WHERE job = :job", array(':job' => 'Speaker'))->fetch();
$this->assertEqual($person->name, 'Meredith', t('Name skipped correctly.'));
$this->assertEqual($person->age, 30, t('Age skipped correctly.'));
$this->assertEqual($person->job, 'Speaker', t('Job skipped correctly.'));
}
/**
* Test that an invalid merge query throws an exception like it is supposed to.
*/
function testInvalidMerge() {
try {
// This query should die because there is no key field specified.
db_merge('test_people')
->fields(array(
'age' => 31,
'name' => 'Tiffany',
))
->execute();
}
catch (InvalidMergeQueryException $e) {
$this->pass(t('InvalidMergeQueryException thrown for invalid query.'));
return;
}
$this->fail(t('No InvalidMergeQueryException thrown'));
}
}
/**
* Test the SELECT builder.
*/
class DatabaseSelectTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Select tests'),
'description' => t('Test the Select query builder.'),
'group' => t('Database'),
);
}
/**
* Test rudimentary SELECT statements.
*/
function testSimpleSelect() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$result = $query->execute();
$num_records = 0;
foreach ($result as $record) {
$num_records++;
}
$this->assertEqual($num_records, 4, t('Returned the correct number of rows.'));
}
/**
* Test basic conditionals on SELECT statements.
*/
function testSimpleSelectConditional() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->condition('age', 27);
$result = $query->execute();
// Check that the aliases are being created the way we want.
$this->assertEqual($name_field, 'name', t('Name field alias is correct.'));
$this->assertEqual($age_field, 'age', t('Age field alias is correct.'));
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.'));
$this->assertEqual($record->$age_field, 27, t('Fetched age is correct.'));
}
/**
* Test SELECT statements with expressions.
*/
function testSimpleSelectExpression() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addExpression("age*2", 'double_age');
$query->condition('age', 27);
$result = $query->execute();
// Check that the aliases are being created the way we want.
$this->assertEqual($name_field, 'name', t('Name field alias is correct.'));
$this->assertEqual($age_field, 'double_age', t('Age field alias is correct.'));
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.'));
$this->assertEqual($record->$age_field, 27*2, t('Fetched age expression is correct.'));
}
/**
* Test SELECT statements with multiple expressions.
*/
function testSimpleSelectExpressionMultiple() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_double_field = $query->addExpression("age*2");
$age_triple_field = $query->addExpression("age*3");
$query->condition('age', 27);
$result = $query->execute();
// Check that the aliases are being created the way we want.
$this->assertEqual($age_double_field, 'expression', t('Double age field alias is correct.'));
$this->assertEqual($age_triple_field, 'expression_2', t('Triple age field alias is correct.'));
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.'));
$this->assertEqual($record->$age_double_field, 27*2, t('Fetched double age expression is correct.'));
$this->assertEqual($record->$age_triple_field, 27*3, t('Fetched triple age expression is correct.'));
}
/**
* Test adding multiple fields to a select statement at the same time.
*/
function testSimpleSelectMultipleFields() {
$record = db_select('test')
->fields('test', array('id', 'name', 'age', 'job'))
->condition('age', 27)
->execute()->fetchObject();
// Check that all fields we asked for are present.
$this->assertNotNull($record->id, t('ID field is present.'));
$this->assertNotNull($record->name, t('Name field is present.'));
$this->assertNotNull($record->age, t('Age field is present.'));
$this->assertNotNull($record->job, t('Job field is present.'));
// Ensure that we got the right record.
// Check that all fields we asked for are present.
$this->assertEqual($record->id, 2, t('ID field has the correct value.'));
$this->assertEqual($record->name, 'George', t('Name field has the correct value.'));
$this->assertEqual($record->age, 27, t('Age field has the correct value.'));
$this->assertEqual($record->job, 'Singer', t('Job field has the correct value.'));
}
/**
* Test adding all fields from a given table to a select statement.
*/
function testSimpleSelectAllFields() {
$record = db_select('test')
->fields('test')
->condition('age', 27)
->execute()->fetchObject();
// Check that all fields we asked for are present.
$this->assertNotNull($record->id, t('ID field is present.'));
$this->assertNotNull($record->name, t('Name field is present.'));
$this->assertNotNull($record->age, t('Age field is present.'));
$this->assertNotNull($record->job, t('Job field is present.'));
// Ensure that we got the right record.
// Check that all fields we asked for are present.
$this->assertEqual($record->id, 2, t('ID field has the correct value.'));
$this->assertEqual($record->name, 'George', t('Name field has the correct value.'));
$this->assertEqual($record->age, 27, t('Age field has the correct value.'));
$this->assertEqual($record->job, 'Singer', t('Job field has the correct value.'));
}
/**
* Test that we can find a record with a NULL value.
*/
function testNullCondition() {
$this->ensureSampleDataNull();
$names = db_select('test_null', 'tn')
->fields('tn', array('name'))
->isNull('age')
->execute()->fetchCol();
$this->assertEqual(count($names), 1, t('Correct number of records found with NULL age.'));
$this->assertEqual($names[0], 'Fozzie', t('Correct record returned for NULL age.'));
}
/**
* Test that we can find a record without a NULL value.
*/
function testNotNullCondition() {
$this->ensureSampleDataNull();
$names = db_select('test_null', 'tn')
->fields('tn', array('name'))
->isNotNull('tn.age')
->orderBy('name')
->execute()->fetchCol();
$this->assertEqual(count($names), 2, t('Correct number of records found withNOT NULL age.'));
$this->assertEqual($names[0], 'Gonzo', t('Correct record returned for NOT NULL age.'));
$this->assertEqual($names[1], 'Kermit', t('Correct record returned for NOT NULL age.'));
}
}
/**
* Test case for subselects in a dynamic SELECT query.
*/
class DatabaseSelectSubqueryTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Select tests, subqueries'),
'description' => t('Test the Select query builder.'),
'group' => t('Database'),
);
}
/**
* Test that we can use a subquery in a FROM clause.
*/
function testFromSubquerySelect() {
// Create a subquery, which is just a normal query object.
$subquery = db_select('test_task', 'tt');
$subquery->addField('tt', 'pid', 'pid');
$subquery->addField('tt', 'task', 'task');
$subquery->condition('priority', 1);
// Create another query that joins against the virtual table resulting
// from the subquery.
$select = db_select($subquery, 'tt2');
$select->join('test', 't', 't.id=tt2.pid');
$select->addField('t', 'name');
$select->condition('task', 'code');
// The resulting query should be equivalent to:
// SELECT t.name
// FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt
// INNER JOIN test t ON t.id=tt.pid
// WHERE tt.task = 'code'
$people = $select->execute()->fetchCol();
$this->assertEqual(count($people), 1, t('Returned the correct number of rows.'));
}
/**
* Test that we can use a subquery in a JOIN clause.
*/
function testJoinSubquerySelect() {
// Create a subquery, which is just a normal query object.
$subquery = db_select('test_task', 'tt');
$subquery->addField('tt', 'pid', 'pid');
$subquery->condition('priority', 1);
// Create another query that joins against the virtual table resulting
// from the subquery.
$select = db_select('test', 't');
$select->join($subquery, 'tt', 't.id=tt.pid');
$select->addField('t', 'name');
// The resulting query should be equivalent to:
// SELECT t.name
// FROM test t
// INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid
$people = $select->execute()->fetchCol();
$this->assertEqual(count($people), 2, t('Returned the correct number of rows.'));
}
}
/**
* Test select with order by clauses.
*/
class DatabaseSelectOrderedTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Select tests, ordered'),
'description' => t('Test the Select query builder.'),
'group' => t('Database'),
);
}
/**
* Test basic order by.
*/
function testSimpleSelectOrdered() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->orderBy($age_field);
$result = $query->execute();
$num_records = 0;
$last_age = 0;
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->age >= $last_age, t('Results returned in correct order.'));
$last_age = $record->age;
}
$this->assertEqual($num_records, 4, t('Returned the correct number of rows.'));
}
/**
* Test multiple order by.
*/
function testSimpleSelectMultiOrdered() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$job_field = $query->addField('test', 'job');
$query->orderBy($job_field);
$query->orderBy($age_field);
$result = $query->execute();
$num_records = 0;
$expected = array(
array('Ringo', 28, 'Drummer'),
array('John', 25, 'Singer'),
array('George', 27, 'Singer'),
array('Paul', 26, 'Songwriter'),
);
$results = $result->fetchAll(PDO::FETCH_NUM);
foreach ($expected as $k => $record) {
$num_records++;
foreach ($record as $kk => $col) {
if ($expected[$k][$kk] != $results[$k][$kk]) {
$this->assertTrue(FALSE, t('Results returned in correct order.'));
}
}
}
$this->assertEqual($num_records, 4, t('Returned the correct number of rows.'));
}
/**
* Test order by descending.
*/
function testSimpleSelectOrderedDesc() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->orderBy($age_field, 'DESC');
$result = $query->execute();
$num_records = 0;
$last_age = 100000000;
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->age <= $last_age, t('Results returned in correct order.'));
$last_age = $record->age;
}
$this->assertEqual($num_records, 4, t('Returned the correct number of rows.'));
}
}
/**
* Test more complex select statements.
*/
class DatabaseSelectComplexTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Select tests, complex'),
'description' => t('Test the Select query builder with more complex queries.'),
'group' => t('Database'),
);
}
/**
* Test simple JOIN statements.
*/
function testDefaultJoin() {
$query = db_select('test_task', 't');
$people_alias = $query->join('test', 'p', 't.pid = p.id');
$name_field = $query->addField($people_alias, 'name', 'name');
$task_field = $query->addField('t', 'task', 'task');
$priority_field = $query->addField('t', 'priority', 'priority');
$query->orderBy($priority_field);
$result = $query->execute();
$num_records = 0;
$last_priority = 0;
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->$priority_field >= $last_priority, t('Results returned in correct order.'));
$this->assertNotEqual($record->$name_field, 'Ringo', t('Taskless person not selected.'));
$last_priority = $record->$priority_field;
}
$this->assertEqual($num_records, 7, t('Returned the correct number of rows.'));
}
/**
* Test LEFT OUTER joins.
*/
function testLeftOuterJoin() {
$query = db_select('test', 'p');
$people_alias = $query->leftJoin('test_task', 't', 't.pid = p.id');
$name_field = $query->addField('p', 'name', 'name');
$task_field = $query->addField($people_alias, 'task', 'task');
$priority_field = $query->addField($people_alias, 'priority', 'priority');
$query->orderBy($name_field);
$result = $query->execute();
$num_records = 0;
$last_name = 0;
foreach ($result as $record) {
$num_records++;
$this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, t('Results returned in correct order.'));
$last_priority = $record->$name_field;
}
$this->assertEqual($num_records, 8, t('Returned the correct number of rows.'));
}
/**
* Test GROUP BY clauses.
*/
function testGroupBy() {
$query = db_select('test_task', 't');
$count_field = $query->addExpression('COUNT(task)', 'num');
$task_field = $query->addField('t', 'task');
$query->orderBy($count_field);
$query->groupBy($task_field);
$result = $query->execute();
$num_records = 0;
$last_count = 0;
$records = array();
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->$count_field >= $last_count, t('Results returned in correct order.'));
$last_count = $record->$count_field;
$records[$record->$task_field] = $record->$count_field;
}
$correct_results = array(
'eat' => 1,
'sleep' => 2,
'code' => 1,
'found new band' => 1,
'perform at superbowl' => 1,
);
foreach ($correct_results as $task => $count) {
$this->assertEqual($records[$task], $count, t("Correct number of '@task' records found.", array('@task' => $task)));
}
$this->assertEqual($num_records, 6, t('Returned the correct number of total rows.'));
}
/**
* Test GROUP BY and HAVING clauses together.
*/
function testGroupByAndHaving() {
$query = db_select('test_task', 't');
$count_field = $query->addExpression('COUNT(task)', 'num');
$task_field = $query->addField('t', 'task');
$query->orderBy($count_field);
$query->groupBy($task_field);
$query->havingCondition('COUNT(task)', 2, '>=');
$result = $query->execute();
$num_records = 0;
$last_count = 0;
$records = array();
foreach ($result as $record) {
$num_records++;
$this->assertTrue($record->$count_field >= 2, t('Record has the minimum count.'));
$this->assertTrue($record->$count_field >= $last_count, t('Results returned in correct order.'));
$last_count = $record->$count_field;
$records[$record->$task_field] = $record->$count_field;
}
$correct_results = array(
'sleep' => 2,
);
foreach ($correct_results as $task => $count) {
$this->assertEqual($records[$task], $count, t("Correct number of '@task' records found.", array('@task' => $task)));
}
$this->assertEqual($num_records, 1, t('Returned the correct number of total rows.'));
}
/**
* Test range queries. The SQL clause varies with the database.
*/
function testRange() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->range(0, 2);
$result = $query->execute();
$num_records = 0;
foreach ($result as $record) {
$num_records++;
}
$this->assertEqual($num_records, 2, t('Returned the correct number of rows.'));
}
/**
* Test distinct queries.
*/
function testDistinct() {
$query = db_select('test_task');
$task_field = $query->addField('test_task', 'task');
$query->distinct();
$result = $query->execute();
$num_records = 0;
foreach ($result as $record) {
$num_records++;
}
$this->assertEqual($num_records, 6, t('Returned the correct number of rows.'));
}
/**
* Test that we can generate a count query from a built query.
*/
function testCountQuery() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->orderBy('name');
$count = $query->countQuery()->execute()->fetchField();
$this->assertEqual($count, 4, t('Counted the correct number of records.'));
// Now make sure we didn't break the original query! We should still have
// all of the fields we asked for.
$record = $query->execute()->fetch();
$this->assertEqual($record->$name_field, 'George', t('Correct data retrieved.'));
$this->assertEqual($record->$age_field, 27, t('Correct data retrieved.'));
}
/**
* Confirm that we can properly nest conditional clauses.
*/
function testNestedConditions() {
// This query should translate to:
// "SELECT job FROM {test} WHERE name = 'Paul' AND (age = 26 OR age = 27)"
// That should find only one record. Yes it's a non-optimal way of writing
// that query but that's not the point!
$query = db_select('test');
$query->addField('test', 'job');
$query->condition('name', 'Paul');
$query->condition(db_or()->condition('age', 26)->condition('age', 27));
$job = $query->execute()->fetchField();
$this->assertEqual($job, 'Songwriter', t('Correct data retrieved.'));
}
}
class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Pager query tests'),
'description' => t('Test the pager query extender.'),
'group' => t('Database'),
);
}
/**
* Confirm that a pager query returns the correct results.
*
* Note that we have to make an HTTP request to a test page handler
* because the pager depends on GET parameters.
*/
function testEvenPagerQuery() {
// To keep the test from being too brittle, we determine up front
// what the page count should be dynamically, and pass the control
// information forward to the actual query on the other side of the
// HTTP request.
$limit = 2;
$count = db_query("SELECT COUNT(*) FROM {test}")->fetchField();
$correct_number = $limit;
$num_pages = floor($count / $limit);
// If there is no remainder from rounding, subtract 1 since we index from 0.
if (!($num_pages * $limit < $count)) {
$num_pages--;
}
for ($page = 0; $page <= $num_pages; ++$page) {
$this->drupalGet('database_test/pager_query_even/' . $limit, array('query' => array('page' => $page)));
$data = json_decode($this->drupalGetContent());
if ($page == $num_pages) {
$correct_number = $count - ($limit * $page);
}
$this->assertEqual(count($data->names), $correct_number, t('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
}
}
/**
* Confirm that a pager query returns the correct results.
*
* Note that we have to make an HTTP request to a test page handler
* because the pager depends on GET parameters.
*/
function testOddPagerQuery() {
// To keep the test from being too brittle, we determine up front
// what the page count should be dynamically, and pass the control
// information forward to the actual query on the other side of the
// HTTP request.
$limit = 2;
$count = db_query("SELECT COUNT(*) FROM {test_task}")->fetchField();
$correct_number = $limit;
$num_pages = floor($count / $limit);
// If there is no remainder from rounding, subtract 1 since we index from 0.
if (!($num_pages * $limit < $count)) {
$num_pages--;
}
for ($page = 0; $page <= $num_pages; ++$page) {
$this->drupalGet('database_test/pager_query_odd/' . $limit, array('query' => array('page' => $page)));
$data = json_decode($this->drupalGetContent());
if ($page == $num_pages) {
$correct_number = $count - ($limit * $page);
}
$this->assertEqual(count($data->names), $correct_number, t('Correct number of records returned by pager: @number', array('@number' => $correct_number)));
}
}
}
class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Tablesort query tests'),
'description' => t('Test the tablesort query extender.'),
'group' => t('Database'),
);
}
/**
* Confirm that a tablesort query returns the correct results.
*
* Note that we have to make an HTTP request to a test page handler
* because the pager depends on GET parameters.
*/
function testTableSortQuery() {
$sorts = array(
array('field' => t('Task ID'), 'sort' => 'desc', 'first' => 'perform at superbowl', 'last' => 'eat'),
array('field' => t('Task ID'), 'sort' => 'asc', 'first' => 'eat', 'last' => 'perform at superbowl'),
array('field' => t('Task'), 'sort' => 'asc', 'first' => 'code', 'last' => 'sleep'),
array('field' => t('Task'), 'sort' => 'desc', 'first' => 'sleep', 'last' => 'code'),
// more elements here
);
foreach ($sorts as $sort) {
$this->drupalGet('database_test/tablesort/', array('query' => array('order' => $sort['field'], 'sort' => $sort['sort'])));
$data = json_decode($this->drupalGetContent());
$first = array_shift($data->tasks);
$last = array_pop($data->tasks);
$this->assertEqual($first->task, $sort['first'], t('Items appear in the correct order.'));
$this->assertEqual($last->task, $sort['last'], t('Items appear in the correct order.'));
}
}
}
/**
* Select tagging tests.
*
* Tags are a way to flag queries for alter hooks so they know
* what type of query it is, such as "node_access".
*/
class DatabaseTaggingTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Query tagging tests'),
'description' => t('Test the tagging capabilities of the Select builder.'),
'group' => t('Database'),
);
}
/**
* Confirm that a query has a "tag" added to it.
*/
function testHasTag() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$this->assertTrue($query->hasTag('test'), t('hasTag() returned true.'));
$this->assertFalse($query->hasTag('other'), t('hasTag() returned false.'));
}
/**
* Test query tagging "has all of these tags" functionality.
*/
function testHasAllTags() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$query->addTag('other');
$this->assertTrue($query->hasAllTags('test', 'other'), t('hasAllTags() returned true.'));
$this->assertFalse($query->hasAllTags('test', 'stuff'), t('hasAllTags() returned false.'));
}
/**
* Test query tagging "has at least one of these tags" functionality.
*/
function testHasAnyTag() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('test');
$this->assertTrue($query->hasAnyTag('test', 'other'), t('hasAnyTag() returned true.'));
$this->assertFalse($query->hasAnyTag('other', 'stuff'), t('hasAnyTag() returned false.'));
}
/**
* Test that we can attach meta data to a query object.
*
* This is how we pass additional context to alter hooks.
*/
function testMetaData() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$data = array(
'a' => 'A',
'b' => 'B',
);
$query->addMetaData('test', $data);
$return = $query->getMetaData('test');
$this->assertEqual($data, $return, t('Corect metadata returned.'));
$return = $query->getMetaData('nothere');
$this->assertNull($return, t('Non-existent key returned NULL.'));
}
}
/**
* Select alter tests.
*
* @see database_test_query_alter().
*/
class DatabaseAlterTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Query altering tests'),
'description' => t('Test the hook_query_alter capabilities of the Select builder.'),
'group' => t('Database'),
);
}
/**
* Test that we can do basic alters.
*/
function testSimpleAlter() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->addTag('database_test_alter_add_range');
$result = $query->execute();
$num_records = 0;
foreach ($result as $record) {
$num_records++;
}
$this->assertEqual($num_records, 2, t('Returned the correct number of rows.'));
}
/**
* Test that we can alter the joins on a query.
*/
function testAlterWithJoin() {
$query = db_select('test_task');
$tid_field = $query->addField('test_task', 'tid');
$task_field = $query->addField('test_task', 'task');
$query->orderBy($task_field);
$query->addTag('database_test_alter_add_join');
$result = $query->execute();
$records = $result->fetchAll();
$this->assertEqual(count($records), 2, t('Returned the correct number of rows.'));
$this->assertEqual($records[0]->name, 'George', t('Correct data retrieved.'));
$this->assertEqual($records[0]->$tid_field, 4, t('Correct data retrieved.'));
$this->assertEqual($records[0]->$task_field, 'sing', t('Correct data retrieved.'));
$this->assertEqual($records[1]->name, 'George', t('Correct data retrieved.'));
$this->assertEqual($records[1]->$tid_field, 5, t('Correct data retrieved.'));
$this->assertEqual($records[1]->$task_field, 'sleep', t('Correct data retrieved.'));
}
/**
* Test that we can alter a query's conditionals.
*/
function testAlterChangeConditional() {
$query = db_select('test_task');
$tid_field = $query->addField('test_task', 'tid');
$pid_field = $query->addField('test_task', 'pid');
$task_field = $query->addField('test_task', 'task');
$people_alias = $query->join('test', 'people', "test_task.pid = people.id");
$name_field = $query->addField($people_alias, 'name', 'name');
$query->condition('test_task.tid', '1');
$query->orderBy($tid_field);
$query->addTag('database_test_alter_change_conditional');
$result = $query->execute();
$records = $result->fetchAll();
$this->assertEqual(count($records), 1, t('Returned the correct number of rows.'));
$this->assertEqual($records[0]->$name_field, 'John', t('Correct data retrieved.'));
$this->assertEqual($records[0]->$tid_field, 2, t('Correct data retrieved.'));
$this->assertEqual($records[0]->$pid_field, 1, t('Correct data retrieved.'));
$this->assertEqual($records[0]->$task_field, 'sleep', t('Correct data retrieved.'));
}
}
/**
* Select alter tests, part 2.
*
* @see database_test_query_alter().
*/
class DatabaseAlter2TestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Query altering tests, part 2'),
'description' => t('Test the hook_query_alter capabilities of the Select builder.'),
'group' => t('Database'),
);
}
/**
* Test that we can alter the fields of a query.
*/
function testAlterChangeFields() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addField('test', 'age', 'age');
$query->orderBy('name');
$query->addTag('database_test_alter_change_fields');
$record = $query->execute()->fetch();
$this->assertEqual($record->$name_field, 'George', t('Correct data retrieved.'));
$this->assertFalse(isset($record->$age_field), t('Age field not found, as intended.'));
}
/**
* Test that we can alter expressions in the query.
*/
function testAlterExpression() {
$query = db_select('test');
$name_field = $query->addField('test', 'name');
$age_field = $query->addExpression("age*2", 'double_age');
$query->condition('age', 27);
$query->addTag('database_test_alter_change_expressions');
$result = $query->execute();
// Ensure that we got the right record.
$record = $result->fetch();
$this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.'));
$this->assertEqual($record->$age_field, 27*3, t('Fetched age expression is correct.'));
}
/**
* Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter().
*/
function testAlterRemoveRange() {
$query = db_select('test');
$query->addField('test', 'name');
$query->addField('test', 'age', 'age');
$query->range(0, 2);
$query->addTag('database_test_alter_remove_range');
$num_records = count($query->execute()->fetchAll());
$this->assertEqual($num_records, 4, t('Returned the correct number of rows.'));
}
}
/**
* Regression tests.
*/
class DatabaseRegressionTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Regression tests'),
'description' => t('Regression tests cases for the database layer.'),
'group' => t('Database'),
);
}
/**
* Regression test for #310447.
*
* Tries to insert non-ascii UTF-8 data in a database column and checks
* if its stored properly.
*/
function testRegression_310447() {
// That's a 255 character UTF-8 string.
$name = str_repeat("é", 255);
db_insert('test')
->fields(array(
'name' => $name,
'age' => 20,
'job' => 'Dancer',
))->execute();
$from_database = db_query("SELECT name FROM {test} WHERE name = :name", array(':name' => $name))->fetchField();
$this->assertIdentical($name, $from_database, t("The database handles UTF-8 characters cleanly."));
}
/**
* Test the db_column_exists() function.
*/
function testDBColumnExists() {
$this->assertTrue(db_column_exists('node', 'nid'), t('Returns true for existent column.'));
$this->assertFalse(db_column_exists('node', 'nosuchcolumn'), t('Returns false for nonexistent column.'));
}
/**
* Test the db_table_exists() function.
*/
function testDBTableExists() {
$this->assertTrue(db_table_exists('node'), t('Returns true for existent table.'));
$this->assertFalse(db_table_exists('nosuchtable'), t('Returns false for nonexistent table.'));
}
}
/**
* Query logging tests.
*/
class DatabaseLoggingTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Query logging'),
'description' => t('Test the query logging facility.'),
'group' => t('Database'),
);
}
/**
* Test that we can log the existence of a query.
*/
function testEnableLogging() {
Database::startLog('testing');
db_query("SELECT name FROM {test} WHERE age > :age", array(':age' => 25))->fetchCol();
db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Ringo'))->fetchCol();
$queries = Database::getLog('testing', 'default');
$this->assertEqual(count($queries), 2, t('Correct number of queries recorded.'));
foreach ($queries as $query) {
$this->assertEqual($query['caller']['function'], __FUNCTION__, t('Correct function in query log.'));
}
}
/**
* Test that we can run two logs in parallel.
*/
function testEnableMultiLogging() {
Database::startLog('testing1');
db_query("SELECT name FROM {test} WHERE age > :age", array(':age' => 25))->fetchCol();
Database::startLog('testing2');
db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Ringo'))->fetchCol();
$queries1 = Database::getLog('testing1');
$queries2 = Database::getLog('testing2');
$this->assertEqual(count($queries1), 2, t('Correct number of queries recorded for log 1.'));
$this->assertEqual(count($queries2), 1, t('Correct number of queries recorded for log 2.'));
}
/**
* Test that we can log queries against multiple targets on the same connection.
*/
function testEnableTargetLogging() {
// Clone the master credentials to a slave connection and to another fake
// connection.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('default', 'slave', $connection_info['default']);
Database::startLog('testing1');
db_query("SELECT name FROM {test} WHERE age > :age", array(':age' => 25))->fetchCol();
db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Ringo'), array('target' => 'slave'));//->fetchCol();
$queries1 = Database::getLog('testing1');
$this->assertEqual(count($queries1), 2, t('Recorded queries from all targets.'));
$this->assertEqual($queries1[0]['target'], 'default', t('First query used default target.'));
$this->assertEqual($queries1[1]['target'], 'slave', t('Second query used slave target.'));
}
/**
* Test that logs to separate targets collapse to the same connection properly.
*
* This test is identical to the one above, except that it doesn't create
* a fake target so the query should fall back to running on the default
* target.
*/
function testEnableTargetLoggingNoTarget() {
Database::startLog('testing1');
db_query("SELECT name FROM {test} WHERE age > :age", array(':age' => 25))->fetchCol();
// We use "fake" here as a target because any non-existent target will do.
// However, because all of the tests in this class share a single page
// request there is likely to be a target of "slave" from one of the other
// unit tests, so we use a target here that we know with absolute certainty
// does not exist.
db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Ringo'), array('target' => 'fake'))->fetchCol();
$queries1 = Database::getLog('testing1');
$this->assertEqual(count($queries1), 2, t('Recorded queries from all targets.'));
$this->assertEqual($queries1[0]['target'], 'default', t('First query used default target.'));
$this->assertEqual($queries1[1]['target'], 'default', t('Second query used default target as fallback.'));
}
/**
* Test that we can log queries separately on different connections.
*/
function testEnableMultiConnectionLogging() {
// Clone the master credentials to a fake connection.
// That both connections point to the same physical database is irrelevant.
$connection_info = Database::getConnectionInfo('default');
Database::addConnectionInfo('test2', 'default', $connection_info['default']);
Database::startLog('testing1');
Database::startLog('testing1', 'test2');
db_query("SELECT name FROM {test} WHERE age > :age", array(':age' => 25))->fetchCol();
$old_key = db_set_active('test2');
db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Ringo'), array('target' => 'slave'))->fetchCol();
db_set_active($old_key);
$queries1 = Database::getLog('testing1');
$queries2 = Database::getLog('testing1', 'test2');
$this->assertEqual(count($queries1), 1, t('Correct number of queries recorded for first connection.'));
$this->assertEqual(count($queries2), 1, t('Correct number of queries recorded for second connection.'));
}
}
/**
* Range query tests.
*/
class DatabaseRangeQueryTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => t('Range query test'),
'description' => t('Test the Range query functionality.'),
'group' => t('Database'),
);
}
function setUp() {
parent::setUp('database_test');
}
/**
* Confirm that range query work and return correct result.
*/
function testRangeQuery() {
// Test if return correct number of rows.
$range_rows = db_query_range("SELECT name FROM {system} ORDER BY name", array(), 2, 3)->fetchAll();
$this->assertEqual(count($range_rows), 3, t('Range query work and return correct number of rows.'));
// Test if return target data.
$raw_rows = db_query("SELECT name FROM {system} ORDER BY name")->fetchAll();
$raw_rows = array_slice($raw_rows, 2, 3);
$this->assertEqual($range_rows, $raw_rows, t('Range query work and return target data.'));
}
}
/**
* Temporary query tests.
*/
class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => t('Temporary query test'),
'description' => t('Test the temporary query functionality.'),
'group' => t('Database'),
);
}
function setUp() {
parent::setUp('database_test');
}
/**
* Return the number of rows of a table.
*/
function countTableRows($table_name) {
return db_select($table_name)->countQuery()->execute()->fetchField();
}
/**
* Confirm that temporary tables work and are limited to one request.
*/
function testTemporaryQuery() {
$this->drupalGet('database_test/db_query_temporary');
$data = json_decode($this->drupalGetContent());
if ($data) {
$this->assertEqual($this->countTableRows("system"), $data->row_count, t('The temporary table contains the correct amount of rows.'));
$this->assertFalse(db_table_exists($data->table_name), t('The temporary table is, indeed, temporary.'));
}
else {
$this->fail(t("The creation of the temporary table failed."));
}
// Now try to run two db_query_temporary() in the same request.
$table_name_system = db_query_temporary('SELECT status FROM {system}', array());
$table_name_users = db_query_temporary('SELECT uid FROM {users}', array());
$this->assertEqual($this->countTableRows($table_name_system), $this->countTableRows("system"), t('A temporary table was created successfully in this request.'));
$this->assertEqual($this->countTableRows($table_name_users), $this->countTableRows("users"), t('A second temporary table was created successfully in this request.'));
}
}
/**
* Test how the current database driver interprets the SQL syntax.
*
* In order to ensure consistent SQL handling throughout Drupal
* across multiple kinds of database systems, we test that the
* database system interprets SQL syntax in an expected fashion.
*/
class DatabaseAnsiSyntaxTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('ANSI SQL syntax tests'),
'description' => t('Test ANSI SQL syntax interpretation.'),
'group' => t('Database'),
);
}
function setUp() {
parent::setUp('database_test');
}
/**
* Test for ANSI string concatenation.
*/
function testBasicConcat() {
$result = db_query("SELECT 'This' || ' ' || 'is' || ' a ' || 'test.'");
$this->assertIdentical($result->fetchField(), 'This is a test.', t('Basic ANSI Concat works.'));
}
/**
* Test for ANSI string concatenation with field values.
*/
function testFieldConcat() {
$result = db_query("SELECT 'The age of ' || name || ' is ' || age || '.' FROM {test} WHERE age = :age", array(':age' => 25));
$this->assertIdentical($result->fetchField(), 'The age of John is 25.', t('Field ANSI Concat works.'));
}
/**
* ANSI standard allows for double quotes to escape field names.
*/
function testQuotes() {
$result = db_query('SELECT "name" FROM {test} WHERE age = :age', array(':age' => 25));
$this->assertIdentical($result->fetchField(), 'John', t('ANSI field quoting works.'));
}
}
/**
* Test invalid data handling.
*/
class DatabaseInvalidDataTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Invalid data'),
'description' => t('Test handling of some invalid data.'),
'group' => t('Database'),
);
}
function setUp() {
parent::setUp('database_test');
}
/**
* Traditional SQL database systems abort inserts when invalid data is encountered.
*/
function testInsertDuplicateData() {
// Try to insert multiple records where at least one has bad data.
try {
db_insert('test')
->fields(array('name', 'age', 'job'))
->values(array(
'name' => 'Elvis',
'age' => 63,
'job' => 'Singer',
))->values(array(
'name' => 'John', // <-- Duplicate value on unique field.
'age' => 17,
'job' => 'Consultant',
))
->values(array(
'name' => 'Frank',
'age' => 75,
'job' => 'Singer',
))
->execute();
$this->fail(t('Insert succeedded when it should not have.'));
}
catch (Exception $e) {
// Check if the first record was inserted.
$name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField();
if ($name == 'Elvis') {
if (!Database::getConnection()->supportsTransactions()) {
// This is an expected fail.
// Database engines that don't support transactions can leave partial
// inserts in place when an error occurs. This is the case for MySQL
// when running on a MyISAM table.
$this->pass(t("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions"));
}
else {
$this->fail(t('The whole transaction is rolled back when a duplicate key insert occurs.'));
}
}
else {
$this->pass(t('The whole transaction is rolled back when a duplicate key insert occurs.'));
}
// Ensure the other values were not inserted.
$record = db_select('test')
->fields('test', array('name', 'age'))
->condition('age', array(17, 75),'IN')
->execute()->fetchObject();
$this->assertFalse($record, t('The rest of the insert aborted as expected.'));
}
}
}
/**
* Drupal-specific SQL syntax tests.
*/
class DatabaseQueryTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Custom query syntax tests'),
'description' => t('Test Drupal\'s extended prepared statement syntax..'),
'group' => t('Database'),
);
}
function setUp() {
parent::setUp('database_test');
}
/**
* Test that we can specify an array of values in the query by simply passing in an array.
*/
function testArraySubstitution() {
$names = db_query("SELECT name FROM {test} WHERE age IN (:ages) ORDER BY age", array(':ages' => array(25, 26, 27)))->fetchAll();
$this->assertEqual(count($names), 3, t('Correct number of names returned'));
}
}
/**
* Test transaction support, particularly nesting.
*
* We test nesting by having two transaction layers, an outer and inner. The
* outer layer encapsulates the inner layer. Our transaction nesting abstraction
* should allow the outer layer function to call any function it wants,
* especially the inner layer that starts its own transaction, and be
* confident that, when the function it calls returns, its own transaction
* is still "alive."
*
* Call structure:
* transactionOuterLayer()
* Start transaction
* transactionInnerLayer()
* Start transaction (does nothing in database)
* [Maybe decide to roll back]
* Do more stuff
* Should still be in transaction A
*
*/
class DatabaseTransactionTestCase extends DatabaseTestCase {
public static function getInfo() {
return array(
'name' => t('Transaction tests'),
'description' => t('Test the transaction abstraction system.'),
'group' => t('Database'),
);
}
/**
* Helper method for transaction unit test. This "outer layer" transaction
* starts and then encapsulates the "inner layer" transaction. This nesting
* is used to evaluate whether the the database transaction API properly
* supports nesting. By "properly supports," we mean the outer transaction
* continues to exist regardless of what functions are called and whether
* those functions start their own transactions.
*
* In contrast, a typical database would commit the outer transaction, start
* a new transaction for the inner layer, commit the inner layer transaction,
* and then be confused when the outer layer transaction tries to commit its
* transaction (which was already committed when the inner transaction
* started).
*
* @param $suffix
* Suffix to add to field values to differentiate tests.
* @param $rollback
* Whether or not to try rolling back the transaction when we're done.
*/
protected function transactionOuterLayer($suffix, $rollback = FALSE) {
$connection = Database::getConnection();
$txn = db_transaction();
// Insert a single row into the testing table.
db_insert('test')
->fields(array(
'name' => 'David' . $suffix,
'age' => '24',
))
->execute();
$this->assertTrue($connection->inTransaction(), t('In transaction before calling nested transaction.'));
// We're already in a transaction, but we call ->transactionInnerLayer
// to nest another transaction inside the current one.
$this->transactionInnerLayer($suffix, $rollback);
$this->assertTrue($connection->inTransaction(), t('In transaction after calling nested transaction.'));
}
/**
* Helper method for transaction unit tests. This "inner layer" transaction
* is either used alone or nested inside of the "outer layer" transaction.
*
* @param $suffix
* Suffix to add to field values to differentiate tests.
* @param $rollback
* Whether or not to try rolling back the transaction when we're done.
*/
protected function transactionInnerLayer($suffix, $rollback = FALSE) {
$connection = Database::getConnection();
// Start a transaction. If we're being called from ->transactionOuterLayer,
// then we're already in a transaction. Normally, that would make starting
// a transaction here dangerous, but the database API handles this problem
// for us by tracking the nesting and avoiding the danger.
$txn = db_transaction();
// Insert a single row into the testing table.
db_insert('test')
->fields(array(
'name' => 'Daniel' . $suffix,
'age' => '19',
))
->execute();
$this->assertTrue($connection->inTransaction(), t('In transaction inside nested transaction.'));
if ($rollback) {
// Roll back the transaction, if requested.
// This rollback should propagate to the the outer transaction, if present.
$connection->rollBack();
$this->assertTrue($connection->willRollBack(), t('Transaction is scheduled to roll back after calling rollBack().'));
}
}
/**
* Test that a database that claims to support transactions will return a transaction object.
*
* If the active connection does not support transactions, this test does nothing.
*/
function testTransactionsSupported() {
try {
$connection = Database::getConnection();
if ($connection->supportsTransactions()) {
// Start a "required" transaction. This should fail if we do
// this on a database that does not actually support transactions.
$txn = db_transaction(TRUE);
}
$this->pass('Transaction started successfully.');
}
catch (TransactionsNotSupportedException $e) {
$this->fail("Exception thrown when it shouldn't have been.");
}
}
/**
* Test that a database that doesn't support transactions fails correctly.
*
* If the active connection supports transactions, this test does nothing.
*/
function testTransactionsNotSupported() {
try {
$connection = Database::getConnection();
if (!$connection->supportsTransactions()) {
// Start a "required" transaction. This should fail if we do this
// on a database that does not actually support transactions, and
// the current database does claim to NOT support transactions.
$txn = db_transaction(TRUE);
$this->fail('No transaction failure registered.');
}
}
catch (TransactionsNotSupportedException $e) {
$this->pass('Exception thrown for unsupported transactions.');
}
}
/**
* Test transaction rollback on a database that supports transactions.
*
* If the active connection does not support transactions, this test does nothing.
*/
function testTransactionRollBackSupported() {
// This test won't work right if transactions are not supported.
if (!Database::getConnection()->supportsTransactions()) {
return;
}
try {
// Create two nested transactions. Roll back from the inner one.
$this->transactionOuterLayer('B', TRUE);
// Neither of the rows we inserted in the two transaction layers
// should be present in the tables post-rollback.
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DavidB'))->fetchField();
$this->assertNotIdentical($saved_age, '24', t('Cannot retrieve DavidB row after commit.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DanielB'))->fetchField();
$this->assertNotIdentical($saved_age, '19', t('Cannot retrieve DanielB row after commit.'));
}
catch(Exception $e) {
$this->fail($e->getMessage());
}
}
/**
* Test transaction rollback on a database that does not support transactions.
*
* If the active driver supports transactions, this test does nothing.
*/
function testTransactionRollBackNotSupported() {
// This test won't work right if transactions are supported.
if (Database::getConnection()->supportsTransactions()) {
return;
}
try {
// Create two nested transactions. Attempt to roll back from the inner one.
$this->transactionOuterLayer('B', TRUE);
// Because our current database claims to not support transactions,
// the inserted rows should be present despite the attempt to roll back.
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DavidB'))->fetchField();
$this->assertIdentical($saved_age, '24', t('DavidB not rolled back, since transactions are not supported.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DanielB'))->fetchField();
$this->assertIdentical($saved_age, '19', t('DanielB not rolled back, since transactions are not supported.'));
}
catch(Exception $e) {
$this->fail($e->getMessage());
}
}
/**
* Test committed transaction.
*
* The behavior of this test should be identical for connections that support
* transactions and those that do not.
*/
function testCommittedTransaction() {
try {
// Create two nested transactions. The changes should be committed.
$this->transactionOuterLayer('A');
// Because we committed, both of the inserted rows should be present.
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DavidA'))->fetchField();
$this->assertIdentical($saved_age, '24', t('Can retrieve DavidA row after commit.'));
$saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'DanielA'))->fetchField();
$this->assertIdentical($saved_age, '19', t('Can retrieve DanielA row after commit.'));
}
catch(Exception $e) {
$this->fail($e->getMessage());
}
}
}