Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.999
diff -u -9 -p -r1.999 common.inc
--- includes/common.inc	29 Sep 2009 15:31:13 -0000	1.999
+++ includes/common.inc	29 Sep 2009 17:49:27 -0000
@@ -5028,31 +5028,30 @@ function drupal_schema_fields_sql($table
 /**
  * Save a record to the database based upon the schema.
  *
  * Default values are filled in for missing items, and 'serial' (auto increment)
  * types are filled in with IDs.
  *
  * @param $table
  *   The name of the table; this must exist in schema API.
  * @param $object
- *   The object to write. This is a reference, as defaults according to
- *   the schema may be filled in on the object, as well as ID on the serial
- *   type(s). Both array an object types may be passed.
+ *   The object to write. This is a reference, as defaults according to the
+ *   schema may be filled in on the object, as well as ID on the serial type(s).
+ *   Both array an object types may be passed.
  * @param $primary_keys
  *   If this is an update, specify the primary keys' field names. It is the
- *   caller's responsibility to know if a record for this object already
- *   exists in the database. If there is only 1 key, you may pass a simple string.
+ *   caller's responsibility to know if a record for this object already exists
+ *   in the database. If there is only 1 key, you may pass a simple string.
  * @return
  *   Failure to write a record will return FALSE. Otherwise SAVED_NEW or
- *   SAVED_UPDATED is returned depending on the operation performed. The
- *   $object parameter contains values for any serial fields defined by
- *   the $table. For example, $object->nid will be populated after inserting
- *   a new node.
+ *   SAVED_UPDATED is returned depending on the operation performed. The $object
+ *   parameter contains values for any serial fields defined by the $table. For
+ *   example, $object->nid will be populated after inserting a new a new node.
  */
 function drupal_write_record($table, &$object, $primary_keys = array()) {
   // Standardize $primary_keys to an array.
   if (is_string($primary_keys)) {
     $primary_keys = array($primary_keys);
   }
 
   $schema = drupal_get_schema($table);
   if (empty($schema)) {
@@ -5067,67 +5066,65 @@ function drupal_write_record($table, &$o
   else {
     $array = FALSE;
   }
 
   $fields = array();
 
   // Go through our schema, build SQL, and when inserting, fill in defaults for
   // fields that are not set.
   foreach ($schema['fields'] as $field => $info) {
-    // Special case -- skip serial types if we are updating.
-    if ($info['type'] == 'serial' && !empty($primary_keys)) {
-      continue;
+    if ($info['type'] == 'serial') {
+      // Skip serial types if we are updating.
+      if (!empty($primary_keys)) {
+        continue;
+      }
+      // Track serial field so we can helpfully populate them after the query.
+      // NOTE: Each table should come with one serial field only.
+      $serial = $field;
     }
 
-    // For inserts, populate defaults from schema if not already provided.
-    if (!isset($object->$field) && empty($primary_keys) && isset($info['default'])) {
+    if (!property_exists($object, $field)) {
+      // Skip fields that are not provided, unless we are inserting and a
+      // default value is provided by the schema.
+      if (!empty($primary_keys) || !isset($info['default'])) {
+        continue;
+      }
       $object->$field = $info['default'];
     }
 
-    // Track serial field so we can helpfully populate them after the query.
-    // NOTE: Each table should come with one serial field only.
-    if ($info['type'] == 'serial') {
-      $serial = $field;
+    // Build array of fields to update or insert.
+    if (empty($info['serialize'])) {
+      $fields[$field] = $object->$field;
+    }
+    elseif (!empty($object->$field)) {
+      $fields[$field] = serialize($object->$field);
+    }
+    else {
+      $fields[$field] = '';
     }
 
-    // Build arrays for the fields and values in our query.
-    if (isset($object->$field)) {
-      if (empty($info['serialize'])) {
-        $fields[$field] = $object->$field;
+    // Type cast to proper datatype, except when the value is NULL and the
+    // column allows this.
+    //
+    // MySQL PDO silently casts e.g. FALSE and '' to 0 when inserting the value
+    // into an integer column, but PostgreSQL PDO does not. Also type cast NULL
+    // when the column does not allow this.
+    if (!is_null($object->$field) || !empty($info['not null'])) {
+      if ($info['type'] == 'int' || $info['type'] == 'serial') {
+        $fields[$field] = (int) $fields[$field];
       }
-      elseif (!empty($object->$field)) {
-        $fields[$field] = serialize($object->$field);
+      elseif ($info['type'] == 'float') {
+        $fields[$field] = (float) $fields[$field];
       }
       else {
-        $fields[$field] = '';
+        $fields[$field] = (string) $fields[$field];
       }
     }
-
-    // We don't need to care about type casting if value does not exist.
-    if (!isset($fields[$field])) {
-      continue;
-    }
-
-    // Special case -- skip null value if field allows null.
-    if ($fields[$field] == NULL && $info['not null'] == FALSE) {
-      continue;
-    }
-
-    // Type cast if field does not allow null. Required by DB API.
-    if ($info['type'] == 'int' || $info['type'] == 'serial') {
-      $fields[$field] = (int) $fields[$field];
-    }
-    elseif ($info['type'] == 'float') {
-      $fields[$field] = (float) $fields[$field];
-    }
-    else {
-      $fields[$field] = (string) $fields[$field];
-    }
   }
 
   if (empty($fields)) {
     // No changes requested.
     // If we began with an array, convert back so we don't surprise the caller.
     if ($array) {
       $object = (array) $object;
     }
     return;
Index: modules/simpletest/tests/common.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/common.test,v
retrieving revision 1.75
diff -u -9 -p -r1.75 common.test
--- modules/simpletest/tests/common.test	29 Sep 2009 15:31:15 -0000	1.75
+++ modules/simpletest/tests/common.test	29 Sep 2009 17:49:27 -0000
@@ -1237,27 +1237,46 @@ class DrupalDataApiTest extends DrupalWe
   }
 
   /**
    * Test the drupal_write_record() API function.
    */
   function testDrupalWriteRecord() {
     // Insert an object record for a table with a single-field primary key.
     $vocabulary = new stdClass();
     $vocabulary->name = 'test';
+    $vocabulary->description = '';
+    $vocabulary->help = NULL; // This field cannot be set to NULL.
     $insert_result = drupal_write_record('taxonomy_vocabulary', $vocabulary);
     $this->assertTrue($insert_result == SAVED_NEW, t('Correct value returned when a record is inserted with drupal_write_record() for a table with a single-field primary key.'));
     $this->assertTrue(isset($vocabulary->vid), t('Primary key is set on record created with drupal_write_record().'));
 
+    // Check the inserted values are correct.
+    $result = db_query("SELECT * FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $vocabulary->vid))->fetchObject();
+    $this->assertIdentical($result->name, 'test', t('Name field set.'));
+    $this->assertIdentical($result->description, '', t('Description field set.'));
+    $this->assertIdentical($result->relations, '0', t('Relations field set to default value.'));
+    $this->assertIdentical($result->help, '', t('Help field set to default value.'));
+
     // Update the initial record after changing a property.
-    $vocabulary->name = 'testing';
+    $vocabulary->name = FALSE; // This should set the field to a default value.
+    $vocabulary->description = NULL; // This field can be set to NULL.
+    $vocabulary->relations = TRUE; // This field should be cast to an int.
+    $vocabulary->help = 'help';
     $update_result = drupal_write_record('taxonomy_vocabulary', $vocabulary, array('vid'));
     $this->assertTrue($update_result == SAVED_UPDATED, t('Correct value returned when a record updated with drupal_write_record() for table with single-field primary key.'));
 
+    // Check the updated values are correct.
+    $result = db_query("SELECT * FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $vocabulary->vid))->fetchObject();
+    $this->assertIdentical($result->name, '', t('Name field set to an empty string.'));
+    $this->assertIdentical($result->description, NULL, t('Description field set to NULL.'));
+    $this->assertIdentical($result->relations, '1', t('Relations field set and type cast to int.'));
+    $this->assertIdentical($result->help, 'help', t('Help field set.'));
+
     // Insert an object record for a table with a multi-field primary key.
     $vocabulary_node_type = new stdClass();
     $vocabulary_node_type->vid = $vocabulary->vid;
     $vocabulary_node_type->type = 'page';
     $insert_result = drupal_write_record('taxonomy_vocabulary_node_type', $vocabulary_node_type);
     $this->assertTrue($insert_result == SAVED_NEW, t('Correct value returned when a record is inserted with drupal_write_record() for a table with a multi-field primary key.'));
 
     // Update the record.
     $update_result = drupal_write_record('taxonomy_vocabulary_node_type', $vocabulary_node_type, array('vid', 'type'));
