#227677 by c960657, yched, cha0s, Dave Reid, et al: Fixed drupal_write_record() can't update a column to NULL. (with tests)
parent
09e6a714a3
commit
e07b9d35a1
|
@ -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,54 +5811,52 @@ 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;
|
||||
}
|
||||
|
||||
// For inserts, populate defaults from schema if not already provided.
|
||||
if (!isset($object->$field) && empty($primary_keys) && isset($info['default'])) {
|
||||
$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') {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Build arrays for the fields and values in our query.
|
||||
if (isset($object->$field)) {
|
||||
if (empty($info['serialize'])) {
|
||||
$fields[$field] = $object->$field;
|
||||
}
|
||||
elseif (!empty($object->$field)) {
|
||||
$fields[$field] = serialize($object->$field);
|
||||
}
|
||||
else {
|
||||
$fields[$field] = '';
|
||||
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'];
|
||||
}
|
||||
|
||||
// We don't need to care about type casting if value does not exist.
|
||||
if (!isset($fields[$field])) {
|
||||
continue;
|
||||
// Build array of fields to update or insert.
|
||||
if (empty($info['serialize'])) {
|
||||
$fields[$field] = $object->$field;
|
||||
}
|
||||
|
||||
// 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];
|
||||
elseif (!empty($object->$field)) {
|
||||
$fields[$field] = serialize($object->$field);
|
||||
}
|
||||
else {
|
||||
$fields[$field] = (string) $fields[$field];
|
||||
$fields[$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 ($info['type'] == 'float') {
|
||||
$fields[$field] = (float) $fields[$field];
|
||||
}
|
||||
else {
|
||||
$fields[$field] = (string) $fields[$field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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().'));
|
||||
$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.'));
|
||||
|
||||
// Update the initial record after changing a property.
|
||||
$vocabulary->name = 'testing';
|
||||
$update_result = drupal_write_record('taxonomy_vocabulary', $vocabulary, array('vid'));
|
||||
// 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.
|
||||
|
|
Loading…
Reference in New Issue