Index: modules/simpletest/tests/database_test.info
===================================================================
RCS file: modules/simpletest/tests/database_test.info
diff -N modules/simpletest/tests/database_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/database_test.info	31 Aug 2008 11:34:58 -0000
@@ -0,0 +1,8 @@
+; $Id: $
+name = "Database Test"
+description = "Support module for Database layer tests."
+core = 7.x
+package = Testing
+files[] = database_test.module
+version = VERSION
+hidden = TRUE
Index: modules/simpletest/tests/database_test.install
===================================================================
RCS file: modules/simpletest/tests/database_test.install
diff -N modules/simpletest/tests/database_test.install
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/database_test.install	31 Aug 2008 11:34:58 -0000
@@ -0,0 +1,168 @@
+<?php
+// $Id: $
+
+/**
+ * Implementation of hook_schema().
+ * 
+ * The database tests use the database API which depends on schema
+ * information for certain operations on certain databases.
+ * Therefore, the schema must actually be declared in a normal module
+ * like any other, not directly in the test file.
+ */
+function database_test_schema() {
+  $schema['test'] = array(
+    'description' => 'Basic test table for the database unit tests.',
+    'fields' => array(
+      'id'   => array(
+      	'type' => 'serial', 
+      	'unsigned' => TRUE, 
+      	'not null' => TRUE,
+      ),
+      'name' => array(
+        'description' => "A person's name",
+        'type' => 'varchar', 
+        'length' => 255, 
+        'not null' => TRUE, 
+        'default' => '',
+      ),
+      'age'  => array(
+        'description' => "The person's age",
+        'type' => 'int', 
+      	'unsigned' => TRUE, 
+        'not null' => TRUE, 
+        'default' => 0),
+      'job'  => array(
+        'description' => "The person's job",
+        'type' => 'varchar', 
+        'length' => 255, 
+        'not null' => TRUE, 
+        'default' => 'Undefined',
+      ),
+    ),
+    'primary key' => array('id'),
+    'unique keys' => array(
+      'name'       => array('name')
+    ),
+    'indexes' => array(
+      'ages'  => array('age'),
+    ),
+  );
+  
+  // This is an alternate version of the same table that is structured the same
+  // but has a non-serial Primary Key.
+  $schema['test_people'] = array(
+    'description' => 'A duplicate version of the test table, used for additional tests.',
+    'fields' => array(
+      'name' => array(
+        'description' => "A person's name",
+        'type' => 'varchar', 
+        'length' => 255, 
+        'not null' => TRUE,
+        'default' => '',
+       ),
+      'age'  => array(
+        'description' => "The person's age",
+        'type' => 'int', 
+        'unsigned' => TRUE, 
+        'not null' => TRUE, 
+        'default' => 0,
+      ),
+      'job'  => array(
+        'description' => "The person's job",
+        'type' => 'varchar', 
+        'length' => 255, 
+        'not null' => TRUE, 
+        'default' => '',
+      ),
+    ),
+    'primary key' => array('job'),
+    'indexes' => array(
+      'ages'  => array('age'),
+    ),
+  );
+
+  $schema['test_one_blob'] = array(
+    'description' => 'A simple table including a  BLOB field for testing BLOB behavior.',
+    'fields' => array(
+      'id' => array(
+        'description' => 'Simple unique ID.',
+        'type' => 'serial', 
+        'not null' => TRUE,
+      ),
+      'blob1' => array(
+        'description' => 'A BLOB field.',
+        'type' => 'blob',
+      ),
+    ),
+    'primary key' => array('id'),
+    );
+
+  $schema['test_two_blobs'] = array(
+    'description' => 'A simple test table with two BLOB fields.',
+    'fields' => array(
+      'id' => array(
+        'description' => 'Simple unique ID.',
+        'type' => 'serial', 
+        'not null' => TRUE,
+       ),
+      'blob1' => array(
+        'description' => 'A dummy BLOB field.',
+        'type' => 'blob',
+      ),
+      'blob2' => array(
+        'description' => 'A second BLOB field.',
+        'type' => 'blob'
+      ),
+    ),
+    'primary key' => array('id'),
+    );
+
+  $schema['test_task'] = array(
+    'description' => 'A task list for people in the test table.',
+    'fields' => array(
+      'tid' => array(
+        'description' => 'Task ID, primary key.',
+        'type' => 'serial', 
+        'not null' => TRUE,
+      ),
+      'pid'  => array(
+        'description' => 'The {test_people}.pid, foreign key for the test table.',
+        'type' => 'int', 
+        'unsigned' => TRUE, 
+        'not null' => TRUE, 
+        'default' => 0,
+      ),
+      'task' => array(
+        'description' => 'The task to be completed.',
+        'type' => 'varchar', 
+        'length' => 255, 
+        'not null' => TRUE, 
+        'default' => '',
+      ),
+      'priority'  => array(
+        'description' => 'The priority of the task.',
+        'type' => 'int', 
+        'unsigned' => TRUE, 
+        'not null' => TRUE, 
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('tid'),
+  );
+
+  return $schema;
+}
+
+/**
+ * Implementation of hook_install().
+ */
+function database_test_install() {
+  drupal_install_schema('database_test');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function database_test_uninstall() {
+  drupal_uninstall_schema('database_test');
+}
Index: modules/simpletest/tests/database_test.module
===================================================================
RCS file: modules/simpletest/tests/database_test.module
diff -N modules/simpletest/tests/database_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/database_test.module	31 Aug 2008 11:34:58 -0000
@@ -0,0 +1,38 @@
+<?php
+// $Id: $
+
+/**
+ * Implementation of hook_query_alter().
+ */
+function database_test_query_alter(SelectQuery $query) {
+
+  if ($query->hasTag('database_test_alter_add_range')) {
+    $query->range(0, 2);
+  }
+
+  if ($query->hasTag('database_test_alter_remove_range')) {
+    $query->range();
+  }
+  
+  if ($query->hasTag('database_test_alter_add_join')) {
+    $people_alias = $query->join('test', 'people', "test_task.pid=people.id");
+    $name_field = $query->addField('people', 'name', 'name');
+    $query->condition($people_alias . '.id', 2);
+  }
+  
+  if ($query->hasTag('database_test_alter_change_conditional')) {
+    $conditions =& $query->conditions();
+    $conditions[0]['value'] = 2;
+  }
+  
+  if ($query->hasTag('database_test_alter_change_fields')) {
+    $fields =& $query->getFields();
+    unset($fields['age']);
+  }
+  
+  if ($query->hasTag('database_test_alter_change_expressions')) {
+    $expressions =& $query->getExpressions();
+    $expressions['double_age']['expression'] = 'age*3';
+  } 
+}
+
Index: modules/simpletest/tests/database_test.test
===================================================================
RCS file: modules/simpletest/tests/database_test.test
diff -N modules/simpletest/tests/database_test.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/database_test.test	31 Aug 2008 11:34:58 -0000
@@ -0,0 +1,1535 @@
+<?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');
+
+    try {
+      $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');
+  
+      $ret = array();
+  
+      // This ends up being a test for table drop and create, too, which is 
+      // nice.
+      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)));
+      }
+      
+      $this->addSampleData();
+    }
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * 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() {
+    db_query("INSERT INTO {test} (name, age, job) VALUES ('John', 25, 'Singer')");
+    $john = db_last_insert_id('test', 'id');
+    db_query("INSERT INTO {test} (name, age, job) VALUES ('George', 27, 'Singer')");
+    $george = db_last_insert_id('test', 'id');
+    db_query("INSERT INTO {test} (name, age, job) VALUES ('Ringo', 28, 'Drummer')");
+    $ringo = db_last_insert_id('test', 'id');
+    db_query("INSERT INTO {test} (name, age, job) VALUES ('Paul', 26, 'Songwriter')");
+    $paul = db_last_insert_id('test', 'id');
+
+    db_query("INSERT INTO {test_people} (name, age, job) VALUES ('Meredith', 30, 'Speaker')");
+
+    db_query("INSERT INTO {test_task} (pid, task, priority) VALUES (:pid, :task, :priority)", array(
+      ':pid' => $john, 
+      ':task' => 'eat', 
+      ':priority' => 3,
+    ));
+    db_query("INSERT INTO {test_task} (pid, task, priority) VALUES (:pid, :task, :priority)", array(
+      ':pid' => $john, 
+      ':task' => 'sleep', 
+      ':priority' => 4,
+    ));
+    db_query("INSERT INTO {test_task} (pid, task, priority) VALUES (:pid, :task, :priority)", array(
+      ':pid' => $john, 
+      ':task' => 'code', 
+      ':priority' => 1,
+    ));
+    db_query("INSERT INTO {test_task} (pid, task, priority) VALUES (:pid, :task, :priority)", array(
+      ':pid' => $george, 
+      ':task' => 'sing', 
+      ':priority' => 2,
+    ));
+    db_query("INSERT INTO {test_task} (pid, task, priority) VALUES (:pid, :task, :priority)", array(
+      ':pid' => $george, 
+      ':task' => 'sleep', 
+      ':priority' => 2,
+    ));
+    db_query("INSERT INTO {test_task} (pid, task, priority) VALUES (:pid, :task, :priority)", array(
+      ':pid' => $paul, 
+      ':task' => 'found new band', 
+      ':priority' => 1,
+    ));
+    db_query("INSERT INTO {test_task} (pid, task, priority) VALUES (:pid, :task, :priority)", array(
+      ':pid' => $paul, 
+      ':task' => 'perform at superbowl', 
+      ':priority' => 3,
+    ));
+  }
+
+}
+
+/**
+ * Test fetch actions, part 1.
+ * 
+ * We get timeout errors if we try to run too many tests at once.
+ */
+class DatabaseFetchTestCase extends DatabaseTestCase {
+  
+  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 DatabaseStatement, 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 {
+
+  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 {
+
+  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() {
+    try {
+      $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.'));
+    }
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that we can insert multiple records in one query object.
+   */
+  function testMultiInsert() {
+  	try {
+      $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 by name.'));
+      $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Curly'))->fetchField();
+      $this->assertIdentical($saved_age, '31', t('Can retrieve by name.'));
+      $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Moe'))->fetchField();
+      $this->assertIdentical($saved_age, '32', t('Can retrieve by name.'));
+  	}
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that an insert object can be reused with new data after it executes.
+   */
+  function testRepeatedInsert() {
+    try {
+      $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 by name.'));
+      $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Curly'))->fetchField();
+      $this->assertIdentical($saved_age, '31', t('Can retrieve by name.'));
+      $saved_age = db_query("SELECT age FROM {test} WHERE name = :name", array(':name' => 'Moe'))->fetchField();
+      $this->assertIdentical($saved_age, '32', t('Can retrieve by name.'));
+  	}
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * 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.'));
+  }
+
+  /**
+   * Test that inserts return the proper auto-increment ID.
+   */
+  function testInsertLastInsertID() {
+    try {
+      $id = db_insert('test')->fields(array(
+          'name' => 'Larry',
+          'age' => '30',
+        ))
+        ->execute();
+      
+      $this->assertIdentical($id, '5', t('Auto-increment ID returned successfully.'));
+  	}
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+}
+
+/**
+ * Insert tests using LOB fields, which are weird on some databases.
+ */
+class DatabaseInsertLOBTestCase extends DatabaseTestCase {
+
+  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 {
+
+  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() {
+    try {
+      $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.'));
+    }
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that we can insert fields with values and defaults in the same query.
+   */
+  function testDefaultInsertWithFields() {
+    try {
+      $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.'));
+    }
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+}
+
+/**
+ * Update builder tests.
+ */
+class DatabaseUpdateTestCase extends DatabaseTestCase {
+
+  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 {
+
+  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() {
+    try{
+      $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.'));
+    }
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * 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() {
+    try {
+      $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;
+    }
+    catch (Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+}
+
+/**
+ * Test update queries involving LOB values.
+ */
+class DatabaseUpdateLOBTestCase extends DatabaseTestCase {
+
+  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 {
+
+  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 {
+
+  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() {
+    try{
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * 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 the SELECT builder.
+ *
+ */
+class DatabaseSelectTestCase extends DatabaseTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('Select tests'),
+      'description' => t('Test the Select query builder.'),
+      'group' => t('Database'),
+    );
+  }
+
+  /**
+   * Test rudimentary SELECT statements.
+   */
+  function testSimpleSelect() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * 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, 'test_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, 'test_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 with order by clauses.
+ */
+class DatabaseSelectOrderedTestCase extends DatabaseTestCase {
+
+  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() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test order by descending.
+   */
+  function testSimpleSelectOrderedDesc() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+}
+
+/**
+ * Test more complex select statements.
+ */
+class DatabaseSelectComplexTestCase extends DatabaseTestCase {
+
+  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() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test LEFT OUTER joins.
+   */
+  function testLeftOuterJoin() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test GROUP BY clauses.
+   */
+  function testGroupBy() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test GROUP BY and HAVING clauses together.
+   */
+  function testGroupByAndHaving() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test range queries. The SQL clause varies with the database.
+   */
+  function testRange() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test distinct queries.
+   */
+  function testDistinct() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that we can generate a count query from a built query.
+   */
+  function testCountQuery() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+}
+
+/**
+ * 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 {
+
+  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() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test query tagging "has all of these tags" functionality.
+   */
+  function testHasAllTags() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test query tagging "has at least one of these tags" functionality.
+   */
+  function testHasAnyTag() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that we can attach meta data to a query object.
+   * 
+   * This is how we pass additional context to alter hooks.
+   */
+  function testMetaData() {
+    try {
+      $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-existant key returned NULL.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+}
+
+/**
+ * Select alter tests.
+ * 
+ * @see database_test_query_alter().
+ */
+class DatabaseAlterTestCase extends DatabaseTestCase {
+
+  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() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that we can alter the joins on a query.
+   */
+  function testAlterWithJoin() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that we can alter a query's conditionals.
+   */
+  function testAlterChangeConditional() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+}
+
+/**
+ * Select alter tests, part 2.
+ * 
+ * @see database_test_query_alter().
+ */
+class DatabaseAlter2TestCase extends DatabaseTestCase {
+
+  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() {
+    try {
+      $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->assertNull($record->$age_field, t('Age field not found, as intended.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that we can alter expressions in the query.
+   */
+  function testAlterExpression() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+
+  /**
+   * Test that we can remove a range() value from a query.
+   */
+  function testAlterRemoveRange() {
+    try {
+      $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.'));
+    }
+    catch(Exception $e) {
+      $this->assertTrue(FALSE, $e->getMessage());
+    }
+  }
+}
+
