Index: modules/field/field.crud.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.crud.inc,v retrieving revision 1.71 diff -u -p -r1.71 field.crud.inc --- modules/field/field.crud.inc 29 Sep 2010 01:37:02 -0000 1.71 +++ modules/field/field.crud.inc 10 Oct 2010 12:06:14 -0000 @@ -465,12 +465,25 @@ function field_update_field($field) { // The serialized 'data' column contains everything from $field that does not // have its own column and is not automatically populated when the field is // read. - $data = $field; - unset($data['columns'], $data['field_name'], $data['type'], $data['locked'], $data['module'], $data['cardinality'], $data['active'], $data['deleted']); - // Additionally, do not save the 'bundles' property populated by - // field_info_field(). - unset($data['bundles']); - + $keys = array( + // Ignore {field_config} keys. + 'id', 'field_name', 'type', 'module', 'active', 'locked', 'cardinality', + 'translatable', 'deleted', + // Ignore all possible storage engine keys. + 'storage', 'storage_type', 'storage_module', 'storage_active', + // Ignore the data key itself. + 'data', + // Ignore field schema keys. + // @todo Right now, this is the only way to make this function work like it + // is supposed to work. I.e., without copying field schema information + // into the serialized 'data' column, subsequent invocations of + // field_update_field() are failing, since field_read_fields() retrieves + // the current hook_field_schema(), so there is *nothing* to update. + //'columns', 'primary key', 'unique keys', 'indexes', 'foreign keys', + // Ignore field_info_field() properties. + 'bundles', + ); + $data = array_diff_key($field, array_flip($keys)); $field['data'] = $data; // Store the field and create the id. Index: modules/field/field.info.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.info.inc,v retrieving revision 1.53 diff -u -p -r1.53 field.info.inc --- modules/field/field.info.inc 11 Sep 2010 00:03:42 -0000 1.53 +++ modules/field/field.info.inc 10 Oct 2010 12:06:22 -0000 @@ -255,7 +255,9 @@ function _field_info_collate_fields($res */ function _field_info_prepare_field($field) { // Make sure all expected field settings are present. + $field += array('settings' => array()); $field['settings'] += field_info_field_settings($field['type']); + $field['storage'] += array('settings' => array()); $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); // Add storage details. Index: modules/field/field.install =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.install,v retrieving revision 1.22 diff -u -p -r1.22 field.install --- modules/field/field.install 29 Sep 2010 19:46:40 -0000 1.22 +++ modules/field/field.install 10 Oct 2010 12:07:00 -0000 @@ -309,11 +309,21 @@ function _update_7000_field_delete_insta /** * Utility function: fetch all the field definitions from the database. + * + * @param $conditions + * An array of conditions to limit the select query to. */ -function _update_7000_field_read_fields() { +function _update_7000_field_read_fields(array $conditions = array()) { $fields = array(); - $results = db_query('SELECT * FROM {field_config} WHERE deleted = 0', array(), array('fetch' => PDO::FETCH_ASSOC)); - foreach ($results as $record) { + $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC)) + ->fields('fc') + ->condition('deleted', 0); + if (!empty($conditions)) { + foreach ($conditions as $column => $value) { + $query->condition($column, $value); + } + } + foreach ($query->execute() as $record) { $field = unserialize($record['data']); $field['id'] = $record['id']; $field['field_name'] = $record['field_name']; @@ -334,6 +344,82 @@ function _update_7000_field_read_fields( } /** + * Utility function: Update a field. + * + * @param $prior_field + * A field structure containing the previous field schema definition. + * @param $field + * A field structure containing the new field schema definition. + * + * @return + * Throws a FieldException if the update cannot be performed. + * + * @see field_update_field() + */ +function _update_7000_field_update_field($prior_field, $field) { + // Some updates are always disallowed. + if ($field['storage']['type'] != $prior_field['storage']['type']) { + throw new FieldException("Cannot change an existing field's storage type."); + } + + $has_data = field_has_data($field); + + // See if any module forbids the update by throwing an exception. + foreach (module_implements('field_update_forbid') as $module) { + $function = $module . '_field_update_forbid'; + $function($field, $prior_field, $has_data); + } + + // Tell the storage engine to update the field. Do this before + // saving the new definition since it still might fail. + $storage_type = field_info_storage_types($field['storage']['type']); + module_invoke($storage_type['module'], 'field_storage_update_field', $field, $prior_field, $has_data); + + // Save the new field definition. + // The serialized 'data' column contains everything from $field that does not + // have its own column and is not automatically populated when the field is + // read. + $keys = array( + // Ignore {field_config} keys. + 'id', 'field_name', 'type', 'module', 'active', 'locked', 'cardinality', + 'translatable', 'deleted', + // Ignore all possible storage engine keys. + 'storage', 'storage_type', 'storage_module', 'storage_active', + // Ignore the data key itself. + 'data', + // Ignore field schema keys. + // @todo Right now, this is the only way to make this function work like it + // is supposed to work. I.e., without copying field schema information + // into the serialized 'data' column, subsequent invocations of + // field_update_field() are failing, since field_read_fields() retrieves + // the current hook_field_schema(), so there is *nothing* to update. + //'columns', 'primary key', 'unique keys', 'indexes', 'foreign keys', + // Ignore field_info_field() properties. + 'bundles', + ); + $data = array_diff_key($field, array_flip($keys)); + $field['data'] = $data; + + // Store the field and create the id. + db_update('field_config') + ->fields(array( + 'field_name' => $field['field_name'], + 'type' => $field['type'], + 'module' => $field['module'], + 'active' => $field['active'], + 'storage_type' => $field['storage']['type'], + 'storage_module' => $field['storage']['module'], + 'storage_active' => $field['storage']['active'], + 'locked' => $field['locked'], + 'cardinality' => $field['cardinality'], + 'translatable' => $field['translatable'], + 'data' => serialize($field['data']), + )) + ->condition('id', $field['id']) + ->execute(); +} + +/** * Utility function: write a field instance directly to the database. * * This function can be used for databases whose schema is at field module Index: modules/field/modules/field_sql_storage/field_sql_storage.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module,v retrieving revision 1.56 diff -u -p -r1.56 field_sql_storage.module --- modules/field/modules/field_sql_storage/field_sql_storage.module 6 Oct 2010 13:57:47 -0000 1.56 +++ modules/field/modules/field_sql_storage/field_sql_storage.module 10 Oct 2010 12:05:30 -0000 @@ -240,18 +240,6 @@ function field_sql_storage_field_storage } /** - * Implements hook_field_update_forbid(). - * - * Forbid any field update that changes column definitions if there is - * any data. - */ -function field_sql_storage_field_update_forbid($field, $prior_field, $has_data) { - if ($has_data && $field['columns'] != $prior_field['columns']) { - throw new FieldUpdateForbiddenException("field_sql_storage cannot change the schema for an existing field with data."); - } -} - -/** * Implements hook_field_storage_update_field(). */ function field_sql_storage_field_storage_update_field($field, $prior_field, $has_data) { @@ -267,29 +255,61 @@ function field_sql_storage_field_storage } } else { - // There is data, so there are no column changes. Drop all the - // prior indexes and create all the new ones, except for all the - // priors that exist unchanged. - $table = _field_sql_storage_tablename($prior_field); - $revision_table = _field_sql_storage_revision_tablename($prior_field); - foreach ($prior_field['indexes'] as $name => $columns) { - if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) { - $real_name = _field_sql_storage_indexname($field['field_name'], $name); - db_drop_index($table, $real_name); - db_drop_index($revision_table, $real_name); - } - } + // There is data. $table = _field_sql_storage_tablename($field); $revision_table = _field_sql_storage_revision_tablename($field); - foreach ($field['indexes'] as $name => $columns) { - if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) { - $real_name = _field_sql_storage_indexname($field['field_name'], $name); - $real_columns = array(); - foreach ($columns as $column_name) { - $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name); + + // Drop prior indexes, except for all unchanged. + if (isset($prior_field['indexes'])) { + foreach ($prior_field['indexes'] as $name => $columns) { + if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) { + $real_name = _field_sql_storage_indexname($field['field_name'], $name); + db_drop_index($table, $real_name); + db_drop_index($revision_table, $real_name); + } + } + } + + // Perform field schema column updates, if any. + if (isset($prior_field['columns'])) { + foreach ($prior_field['columns'] as $name => $spec) { + // Remove a field column. + if (!isset($field['columns'][$name])) { + $real_name = _field_sql_storage_columnname($field['field_name'], $name); + db_drop_field($table, $real_name); + db_drop_field($revision_table, $real_name); + } + } + } + if (isset($field['columns'])) { + foreach ($field['columns'] as $name => $spec) { + // Add a field column. + if (!isset($prior_field['columns'][$name])) { + $real_name = _field_sql_storage_columnname($field['field_name'], $name); + db_add_field($table, $real_name, $spec); + db_add_field($revision_table, $real_name, $spec); + } + // Change a field column. + if ($prior_field['columns'][$name] != $spec) { + $real_name = _field_sql_storage_columnname($field['field_name'], $name); + db_change_field($table, $real_name, $real_name, $spec); + db_change_field($revision_table, $real_name, $real_name, $spec); + } + } + } + + // Create new indexes, except for all unchanged. + if (isset($field['indexes'])) { + foreach ($field['indexes'] as $name => $columns) { + if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) { + $real_name = _field_sql_storage_indexname($field['field_name'], $name); + $real_columns = array(); + foreach ($columns as $column_name) { + $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name); + } + db_add_index($table, $real_name, $real_columns); + db_add_index($revision_table, $real_name, $real_columns); } - db_add_index($table, $real_name, $real_columns); - db_add_index($revision_table, $real_name, $real_columns); } } } Index: modules/field/modules/field_sql_storage/field_sql_storage.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.test,v retrieving revision 1.22 diff -u -p -r1.22 field_sql_storage.test --- modules/field/modules/field_sql_storage/field_sql_storage.test 29 Sep 2010 01:37:02 -0000 1.22 +++ modules/field/modules/field_sql_storage/field_sql_storage.test 10 Oct 2010 12:05:45 -0000 @@ -13,6 +13,8 @@ * Tests field storage. */ class FieldSqlStorageTestCase extends DrupalWebTestCase { + protected $profile = 'testing'; + public static function getInfo() { return array( 'name' => 'Field SQL storage tests', @@ -299,25 +301,33 @@ class FieldSqlStorageTestCase extends Dr /** * Test trying to update a field with data. */ - function testUpdateFieldSchemaWithData() { + function testFieldSchemaUpdateWithData() { // Create a decimal 5.2 field and add some data. - $field = array('field_name' => 'decimal52', 'type' => 'number_decimal', 'settings' => array('precision' => 5, 'scale' => 2)); + $field_name = 'decimal52'; + $field = array( + 'field_name' => $field_name, + 'type' => 'number_decimal', + 'settings' => array( + 'precision' => 5, + 'scale' => 2, + )); $field = field_create_field($field); $instance = array('field_name' => 'decimal52', 'entity_type' => 'test_entity', 'bundle' => 'test_bundle'); $instance = field_create_instance($instance); $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); - $entity->decimal52[LANGUAGE_NONE][0]['value'] = '1.235'; - field_attach_insert('test_entity', $entity); + $entity->decimal52[LANGUAGE_NONE][0]['value'] = '1.12345'; + field_test_entity_save($entity); - // Attempt to update the field in a way that would work without data. - $field['settings']['scale'] = 3; - try { - field_update_field($field); - $this->fail(t('Cannot update field schema with data.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot update field schema with data.')); - } + $entity = field_test_entity_test_load($entity->ftid); + $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], '1.12'); + + // Update the field schema. + $field['settings']['precision'] = 2; + $field['settings']['scale'] = 1; + field_update_field($field); + + $entity = field_test_entity_test_load($entity->ftid); + $this->assertEqual($entity->{$field_name}[LANGUAGE_NONE][0]['value'], '1.1'); } /**