diff -u b/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc --- b/core/modules/field/field.crud.inc +++ b/core/modules/field/field.crud.inc @@ -385,7 +385,7 @@ // get it from the state storage. $field = entity_load('field_entity', $instance->field_name); if (empty($field) && $include_deleted && isset($deleted_fields[$instance->field_uuid])) { - $field = new Field($deleted_fields[$instance->field_uuid], 'field_entity'); + $field = new Field($deleted_fields[$instance->field_uuid]); } if (empty($field)) { continue; @@ -582,7 +582,7 @@ // Retrieve all deleted fields. Any that have no instances can be purged. $deleted_fields = state()->get('field.field.deleted') ?: array(); foreach ($deleted_fields as $field) { - $field = new Field($field, 'field_entity'); + $field = new Field($field); $instances = field_read_instances(array('field_id' => $field['uuid']), array('include_deleted' => 1)); if (empty($instances)) { field_purge_field($field); diff -u b/core/modules/field/field.install b/core/modules/field/field.install --- b/core/modules/field/field.install +++ b/core/modules/field/field.install @@ -86,7 +86,7 @@ // Create storage for the field. This requires a field entity, but cannot use // the regular entity_create() function here. - $field_entity = new Field($field, 'field_entity'); + $field_entity = new Field($field); field_sql_storage_field_storage_create_field($field_entity); $field['id'] = $field_id; @@ -243,8 +243,6 @@ function field_update_dependencies() { // Convert Field API to ConfigEntities after: $dependencies['field'][8003] = array( - // - SQL storage has been updated. - 'field_sql_storage' => 8000, // - Custom block bodies have been turned to fields. 'block' => 8008, // - User pictures have been turned to fields. @@ -362,28 +360,30 @@ $deleted_instances = state()->get('field.instance.deleted') ?: array(); $field_uuids = array(); - $fields = db_query("SELECT * FROM {field_config}")->fetchAll(PDO::FETCH_ASSOC); - foreach ($fields as $field) { - $field['data'] = unserialize($field['data']); + + // Migrate field definitions. + $records = db_query("SELECT * FROM {field_config}")->fetchAll(PDO::FETCH_ASSOC); + foreach ($records as $record) { + $record['data'] = unserialize($record['data']); $config = array( - 'id' => $field['field_name'], + 'id' => $record['field_name'], 'uuid' => $uuid->generate(), - 'type' => $field['type'], - 'module' => $field['module'], - 'active' => $field['active'], - 'settings' => $field['data']['settings'], + 'type' => $record['type'], + 'module' => $record['module'], + 'active' => $record['active'], + 'settings' => $record['data']['settings'], 'storage' => array( - 'type' => $field['storage_type'], - 'module' => $field['storage_module'], - 'active' => $field['storage_active'], - 'settings' => $field['data']['storage']['settings'], + 'type' => $record['storage_type'], + 'module' => $record['storage_module'], + 'active' => $record['storage_active'], + 'settings' => $record['data']['storage']['settings'], ), - 'locked' => $field['locked'], - 'cardinality' => $field['cardinality'], - 'translatable' => $field['translatable'], - 'entity_types' => $field['data']['entity_types'], - 'indexes' => $field['data']['indexes'] ?: array(), + 'locked' => $record['locked'], + 'cardinality' => $record['cardinality'], + 'translatable' => $record['translatable'], + 'entity_types' => $record['data']['entity_types'], + 'indexes' => $record['data']['indexes'] ?: array(), 'status' => 1, 'langcode' => 'und', ); @@ -393,7 +393,8 @@ $config['module'] = 'options'; } - if (!$field['deleted']) { + // Save in either config or state. + if (!$record['deleted']) { config('field.field.' . $config['id']) ->setData($config) ->save(); @@ -402,34 +403,51 @@ else { $config['deleted'] = TRUE; $deleted_fields[$config['uuid']] = $config; + // Additionally, rename the data tables for deleted fields. Technically + // this would belong in an update in field_sql_storage.module, but it is + // easier to do it now, when the old numeric ID is available. + if ($config['storage']['type'] == 'field_sql_storage') { + $field = new Field($config); + $tables = array( + "field_deleted_data_{$record['id']}" => _field_sql_storage_tablename($field), + "field_deleted_revision_{$record['id']}" => _field_sql_storage_revision_tablename($field), + ); + foreach ($tables as $table_old => $table_new) { + if (db_table_exists($table_old)) { + db_rename_table($table_old, $table_new); + } + } + } } - // Store with the old field_id so that we can map the instance. - $field_uuids[$field['id']] = $config['uuid']; + // Store the UUID with the old field_id so that we can map the instances. + $field_uuids[$record['id']] = $config['uuid']; } - $instances = db_query("SELECT * FROM {field_config_instance}")->fetchAll(PDO::FETCH_ASSOC); - foreach ($instances as $instance) { - $instance['data'] = unserialize($instance['data']); + // Migrate instance definitions. + $records = db_query("SELECT * FROM {field_config_instance}")->fetchAll(PDO::FETCH_ASSOC); + foreach ($records as $record) { + $record['data'] = unserialize($record['data']); $config = array( - 'id' => $instance['entity_type'] . '.' . $instance['bundle'] . '.' . $instance['field_name'], + 'id' => $record['entity_type'] . '.' . $record['bundle'] . '.' . $record['field_name'], 'uuid' => $uuid->generate(), - 'field_uuid' => $field_uuids[$instance['field_id']], - 'entity_type' => $instance['entity_type'], - 'bundle' => $instance['bundle'], - 'label' => $instance['data']['label'], - 'description' => $instance['data']['description'], - 'required' => $instance['data']['required'], - 'default_value' => isset($instance['data']['default_value']) ? $instance['data']['default_value'] : array(), - 'default_value_function' => isset($instance['data']['default_value_function']) ? $instance['data']['default_value_function'] : '', - 'settings' => $instance['data']['settings'], - 'widget' => $instance['data']['widget'], + 'field_uuid' => $field_uuids[$record['field_id']], + 'entity_type' => $record['entity_type'], + 'bundle' => $record['bundle'], + 'label' => $record['data']['label'], + 'description' => $record['data']['description'], + 'required' => $record['data']['required'], + 'default_value' => isset($record['data']['default_value']) ? $record['data']['default_value'] : array(), + 'default_value_function' => isset($record['data']['default_value_function']) ? $record['data']['default_value_function'] : '', + 'settings' => $record['data']['settings'], + 'widget' => $record['data']['widget'], 'status' => 1, 'langcode' => 'und', ); - if (!$instance['deleted']) { + // Save in either config or state. + if (!$record['deleted']) { config('field.instance.' . $config['id']) ->setData($config) ->save(); @@ -446,7 +464,7 @@ ->fields(array('id' => $config['field_uuid'])) ->condition('type', 'default_image') ->condition('module', 'image') - ->condition('id', $instance['field_id']) + ->condition('id', $record['field_id']) ->condition('fid', $config['settings']['default_image']) ->execute(); } diff -u b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php --- b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php @@ -141,7 +141,7 @@ /** * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::__construct(). */ - public function __construct(array $values, $entity_type) { + public function __construct(array $values, $entity_type = 'field_entity') { // Check required properties. if (empty($values['type'])) { throw new FieldException('Attempt to create a field with no type.'); diff -u b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php --- b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php @@ -139,7 +139,7 @@ /** * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::__construct(). */ - public function __construct(array $values, $entity_type) { + public function __construct(array $values, $entity_type = 'field_instance') { // Check required properties. if (empty($values['entity_type'])) { throw new FieldException(format_string('Attempt to create an instance of field @field_name without an entity type.', array('@field_name' => $values['field_name']))); diff -u b/core/modules/field_sql_storage/field_sql_storage.install b/core/modules/field_sql_storage/field_sql_storage.install --- b/core/modules/field_sql_storage/field_sql_storage.install +++ b/core/modules/field_sql_storage/field_sql_storage.install @@ -22,7 +22,7 @@ foreach ($field_names as $name) { $field = config($name)->get(); if ($field['storage']['type'] == 'field_sql_storage') { - $field = new Field($field, 'field_entity'); + $field = new Field($field); $schema += _field_sql_storage_schema($field); } } @@ -86,67 +86,58 @@ } /** - * Generates a table name for a field data table. - * - * @param $field - * The field structure. - * - * @return - * A string containing the generated name for the database table. + * Implements hook_update_dependencies(). */ -function _update_8000_field_sql_storage_tablename($field) { - if ($field['deleted']) { - return "field_deleted_data_" . $field['id']; - } - else { - return "field_data_{$field['field_name']}"; - } +function field_sql_storage_update_dependencies() { + // Convert storage tables after field definitions have moved to + // ConfigEntities. + $dependencies['field_sql_storage'][8000] = array( + 'field' => 8003, + ); + return $dependencies; } /** - * Generates a table name for a field revision archive table. - * - * @param $name - * The field structure. - * - * @return - * A string containing the generated name for the database table. + * Renames the 'language' column to 'langcode' in field data tables. */ -function _update_8000_field_sql_storage_revision_tablename($field) { - if ($field['deleted']) { - return "field_deleted_revision_" . $field['id']; +function field_sql_storage_update_8000(&$sandbox) { + // Get field definitions from config, and deleted fields from state(). + $config_names = config_get_storage_names_with_prefix('field.field'); + $deleted_fields = state()->get('field.field.deleted') ?: array(); + // Ditch UUID keys, we will iterate through deleted fields using a numeric + // index. + $deleted_fields = array_values($deleted_fields); + + if (empty($config_names) && empty($deleted_fields)) { + return; + } + + if (!isset($sandbox['index'])) { + $sandbox['index'] = 0; + $sandbox['max'] = count($config_names) + count($deleted_fields); + } + + // Retrieve the next field definition. When the index exceeds the number of + // 'configuration' fields, use it to iterate on deleted fields. + if (isset($config_names[$sandbox['index']])) { + $field_config = config($config_names[$sandbox['index']])->get(); } else { - return "field_revision_{$field['field_name']}"; + $field_config = $deleted_fields[$sandbox['index'] - count($config_names)]; } -} -/** - * Changes field language into langcode. - */ -function field_sql_storage_update_8000(&$sandbox) { - if (!isset($sandbox['progress'])) { - $sandbox['progress'] = 0; - $sandbox['last'] = 0; - $sandbox['max'] = db_query("SELECT COUNT(id) FROM {field_config} WHERE storage_type = 'field_sql_storage'")->fetchField(); - } - - // Retrieve field data. - $field = db_query("SELECT id, field_name, deleted FROM {field_config} WHERE id > :id AND storage_type = 'field_sql_storage'", array(':id' => $sandbox['last']))->fetchAssoc(); - if ($field) { - - $sandbox['progress']++; - $sandbox['last'] = $field['id']; + if ($field_config['storage']['type'] == 'field_sql_storage') { + $field = new Field($field_config); // Prepare updated schema data structures. - $primary_key_data = array ( + $primary_key_data = array( 'entity_type', 'entity_id', 'deleted', 'delta', 'langcode', ); - $primary_key_revision = array ( + $primary_key_revision = array( 'entity_type', 'entity_id', 'revision_id', @@ -160,14 +151,14 @@ $field_langcode = array( 'type' => 'varchar', 'length' => 32, - 'not null' => true, + 'not null' => TRUE, 'default' => '', ); - $data_table = _update_8000_field_sql_storage_tablename($field); - $revision_table = _update_8000_field_sql_storage_revision_tablename($field); - $table_info = array($data_table => $primary_key_data, $revision_table => $primary_key_revision); - + $table_info = array( + _field_sql_storage_tablename($field) => $primary_key_data, + _field_sql_storage_revision_tablename($field) => $primary_key_revision, + ); foreach ($table_info as $table => $primary_key) { // Do not update tables which already have the langcode column, // created during the upgrade before this update function. @@ -183,3 +174,4 @@ - $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); + $sandbox['index']++; + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['index'] / $sandbox['max']); } diff -u b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php --- b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/FieldUpgradePathTest.php @@ -165,6 +165,12 @@ $this->assertEqual($instance_manifest["node.$node_type.body"]['name'], "field.instance.node.$node_type.body"); } + // Check that field values in a pre-existing node are read correctly. + $body = node_load(1)->get('body'); + $this->assertEqual($body->value, 'Some value'); + $this->assertEqual($body->summary, 'Some summary'); + $this->assertEqual($body->format, 'filtered_html'); + // Check that the definition of a deleted field is stored in state rather // than config. $this->assertFalse(config('field.field.test_deleted_field')->get()); @@ -189,11 +195,17 @@ // The deleted field uuid and deleted instance field_uuid must match. $this->assertEqual($deleted_field['uuid'], $deleted_instance['field_uuid']); - // Check that field values in a pre-existing node are correct. - $body = node_load(1)->get('body'); - $this->assertEqual($body->value, 'Some value'); - $this->assertEqual($body->summary, 'Some summary'); - $this->assertEqual($body->format, 'filtered_html'); + // Check that pre-existing deleted field values are read correctly. + $entity = _field_create_entity_from_ids((object) array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'entity_id' => 2, + 'revision_id' => 2, + )); + field_attach_load('node', array(2 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $deleted_field['uuid'], 'deleted' => 1)); + $deleted_value = $entity->get('test_deleted_field'); + $this->assertEqual($deleted_value[LANGUAGE_NOT_SPECIFIED][0]['value'], 'Some deleted value'); + $this->assertEqual($deleted_value[LANGUAGE_NOT_SPECIFIED][0]['format'], 'filtered_html'); // Check that creation of a new node works as expected. $value = $this->randomName(); diff -u b/core/modules/system/tests/upgrade/drupal-7.field.database.php b/core/modules/system/tests/upgrade/drupal-7.field.database.php --- b/core/modules/system/tests/upgrade/drupal-7.field.database.php +++ b/core/modules/system/tests/upgrade/drupal-7.field.database.php @@ -83,40 +83,31 @@ 'sticky' => '0', )) ->execute(); + +$field_data_row = array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '1', + 'revision_id' => '1', + 'language' => 'und', + 'delta' => '0', + 'body_value' => 'Some value', + 'body_summary' => 'Some summary', + 'body_format' => 'filtered_html', +); db_insert('field_data_body') - ->fields(array( - 'entity_type' => 'node', - 'bundle' => 'article', - 'deleted' => '0', - 'entity_id' => '1', - 'revision_id' => '1', - 'language' => 'und', - 'delta' => '0', - 'body_value' => 'Some value', - 'body_summary' => 'Some summary', - 'body_format' => 'filtered_html', - )) + ->fields($field_data_row) ->execute(); db_insert('field_revision_body') - ->fields(array( - 'entity_type' => 'node', - 'bundle' => 'article', - 'deleted' => '0', - 'entity_id' => '1', - 'revision_id' => '1', - 'language' => 'und', - 'delta' => '0', - 'body_value' => 'Some value', - 'body_summary' => 'Some summary', - 'body_format' => 'filtered_html', - )) + ->fields($field_data_row) ->execute(); // Add a deleted field and instance. $field_id = db_insert('field_config') ->fields(array( 'field_name' => 'test_deleted_field', - 'type' => 'text_long', + 'type' => 'text', 'module' => 'text', 'active' => 1, 'storage_type' => 'field_sql_storage', @@ -185,0 +177,209 @@ + +// Add data tables for the deleted field. +db_create_table("field_deleted_data_{$field_id}", array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'entity_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + ), + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'test_deleted_field_value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'test_deleted_field_format' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'test_deleted_field_format' => array( + 'test_deleted_field_format', + ), + ), + 'foreign keys' => array( + 'test_deleted_field_format' => array( + 'table' => 'filter_format', + 'columns' => array( + 'test_deleted_field_format' => 'format', + ), + ), + ), + 'module' => 'field_sql_storage', + 'name' => "field_deleted_data_{$field_id}", +)); +db_create_table("field_deleted_revision_{$field_id}", array( + 'fields' => array( + 'entity_type' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), + 'entity_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'test_deleted_field_value' => array( + 'type' => 'text', + 'size' => 'big', + 'not null' => FALSE, + ), + 'test_deleted_field_format' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'primary key' => array( + 'entity_type', + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'language', + ), + 'indexes' => array( + 'entity_type' => array( + 'entity_type', + ), + 'bundle' => array( + 'bundle', + ), + 'deleted' => array( + 'deleted', + ), + 'entity_id' => array( + 'entity_id', + ), + 'revision_id' => array( + 'revision_id', + ), + 'language' => array( + 'language', + ), + 'test_deleted_field_format' => array( + 'test_deleted_field_format', + ), + ), + 'foreign keys' => array( + 'test_deleted_field_format' => array( + 'table' => 'filter_format', + 'columns' => array( + 'test_deleted_field_format' => 'format', + ), + ), + ), + 'module' => 'field_sql_storage', + 'name' => "field_deleted_revision_{$field_id}", +)); + +// Add some deleted field data. +$field_data_row = array( + 'entity_type' => 'node', + 'bundle' => 'article', + 'deleted' => '0', + 'entity_id' => '2', + 'revision_id' => '2', + 'language' => 'und', + 'delta' => '0', + 'test_deleted_field_value' => 'Some deleted value', + 'test_deleted_field_format' => 'filtered_html', +); +db_insert("field_deleted_data_{$field_id}") + ->fields($field_data_row) + ->execute(); +db_insert("field_deleted_revision_{$field_id}") + ->fields($field_data_row) + ->execute(); +