From e07b9d35a1f4dcb1678c4d3bb6482daaebea6350 Mon Sep 17 00:00:00 2001 From: Angie Byron <webchick@24967.no-reply.drupal.org> Date: Fri, 8 Jan 2010 06:07:03 +0000 Subject: [PATCH] #227677 by c960657, yched, cha0s, Dave Reid, et al: Fixed drupal_write_record() can't update a column to NULL. (with tests) --- includes/common.inc | 89 ++++++++++++++-------------- modules/simpletest/tests/common.test | 80 +++++++++++++++++++++---- 2 files changed, 112 insertions(+), 57 deletions(-) diff --git a/includes/common.inc b/includes/common.inc index 6cf0fc1791e3..080caab1bed6 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -5773,19 +5773,18 @@ function drupal_schema_fields_sql($table, $prefix = NULL) { * @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 and 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 and 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. @@ -5812,55 +5811,53 @@ function drupal_write_record($table, &$object, $primary_keys = 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)) { diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index e1a45dad2948..156bb8fd8b10 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -1460,29 +1460,87 @@ class DrupalDataApiTest extends DrupalWebTestCase { } function setUp() { - parent::setUp('taxonomy'); + parent::setUp('database_test'); } /** * 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'; - $insert_result = drupal_write_record('taxonomy_vocabulary', $vocabulary); + // Insert a record - no columns allow NULL values. + $person = new stdClass(); + $person->name = 'John'; + $person->unknown_column = 123; + $insert_result = drupal_write_record('test', $person); $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().')); - - // Update the initial record after changing a property. - $vocabulary->name = 'testing'; - $update_result = drupal_write_record('taxonomy_vocabulary', $vocabulary, array('vid')); + $this->assertTrue(isset($person->id), t('Primary key is set on record created with drupal_write_record().')); + $this->assertIdentical($person->age, 0, t('Age field set to default value.')); + $this->assertIdentical($person->job, 'Undefined', t('Job field set to default value.')); + + // Verify that the record was inserted. + $result = db_query("SELECT * FROM {test} WHERE id = :id", array(':id' => $person->id))->fetchObject(); + $this->assertIdentical($result->name, 'John', t('Name field set.')); + $this->assertIdentical($result->age, '0', t('Age field set to default value.')); + $this->assertIdentical($result->job, 'Undefined', t('Job field set to default value.')); + $this->assertFalse(isset($result->unknown_column), t('Unknown column was ignored.')); + + // Update the newly created record. + $person->name = 'Peter'; + $person->age = 27; + $person->job = NULL; + $update_result = drupal_write_record('test', $person, array('id')); $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.')); + // Verify that the record was updated. + $result = db_query("SELECT * FROM {test} WHERE id = :id", array(':id' => $person->id))->fetchObject(); + $this->assertIdentical($result->name, 'Peter', t('Name field set.')); + $this->assertIdentical($result->age, '27', t('Age field set.')); + $this->assertIdentical($result->job, '', t('Job field set and cast to string.')); + + // Try to insert NULL in columns that does not allow this. + $person = new stdClass(); + $person->name = 'Ringo'; + $person->age = NULL; + $person->job = NULL; + $insert_result = drupal_write_record('test', $person); + $this->assertTrue(isset($person->id), t('Primary key is set on record created with drupal_write_record().')); + $result = db_query("SELECT * FROM {test} WHERE id = :id", array(':id' => $person->id))->fetchObject(); + $this->assertIdentical($result->name, 'Ringo', t('Name field set.')); + $this->assertIdentical($result->age, '0', t('Age field set.')); + $this->assertIdentical($result->job, '', t('Job field set.')); + + // Insert a record - the "age" column allows NULL. + $person = new stdClass(); + $person->name = 'Paul'; + $person->age = NULL; + $insert_result = drupal_write_record('test_null', $person); + $this->assertTrue(isset($person->id), t('Primary key is set on record created with drupal_write_record().')); + $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(':id' => $person->id))->fetchObject(); + $this->assertIdentical($result->name, 'Paul', t('Name field set.')); + $this->assertIdentical($result->age, NULL, t('Age field set.')); + + // Insert a record - do not specify the value of a column that allows NULL. + $person = new stdClass(); + $person->name = 'Meredith'; + $insert_result = drupal_write_record('test_null', $person); + $this->assertTrue(isset($person->id), t('Primary key is set on record created with drupal_write_record().')); + $this->assertIdentical($person->age, 0, t('Age field set to default value.')); + $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(':id' => $person->id))->fetchObject(); + $this->assertIdentical($result->name, 'Meredith', t('Name field set.')); + $this->assertIdentical($result->age, '0', t('Age field set to default value.')); + + // Update the newly created record. + $person->name = 'Mary'; + $person->age = NULL; + $update_result = drupal_write_record('test_null', $person, array('id')); + $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(':id' => $person->id))->fetchObject(); + $this->assertIdentical($result->name, 'Mary', t('Name field set.')); + $this->assertIdentical($result->age, NULL, t('Age field set.')); + // Run an update query where no field values are changed. The database // layer should return zero for number of affected rows, but // db_write_record() should still return SAVED_UPDATED. - $update_result = drupal_write_record('taxonomy_vocabulary', $vocabulary, array('vid')); + $update_result = drupal_write_record('test_null', $person, array('id')); $this->assertTrue($update_result == SAVED_UPDATED, t('Correct value returned when a valid update is run without changing any values.')); // Insert an object record for a table with a multi-field primary key. -- GitLab