Commit bd1dc1fd authored by David_Rothstein's avatar David_Rothstein

Issue #1040790 by yched, swentel, geerlingguy, justin.randell, Berdir | catch:...

Issue #1040790 by yched, swentel, geerlingguy, justin.randell, Berdir | catch: Fixed _field_info_collate_fields() memory usage.
parent 6a4df4ad
Drupal 7.20, xxxx-xx-xx (development version)
-----------------------
- Refactored the Field module's caching behavior to obtain massive improvements
in memory usage for sites with many fields and instances.
- Fixed entity argument not being passed to implementations of
hook_file_download_access_alter(). The fix adds an additional context
parameter that can be passed when calling drupal_alter() for any hook (API
......
......@@ -1735,11 +1735,14 @@ function hook_field_storage_details_alter(&$details, $field) {
* loaded.
*/
function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) {
$field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
foreach ($fields as $field_id => $ids) {
$field = $field_info[$field_id];
// By the time this hook runs, the relevant field definitions have been
// populated and cached in FieldInfo, so calling field_info_field_by_id()
// on each field individually is more efficient than loading all fields in
// memory upfront with field_info_field_by_ids().
$field = field_info_field_by_id($field_id);
$field_name = $field['field_name'];
$table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
......
......@@ -283,7 +283,6 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
'language' => NULL,
);
$options += $default_options;
$field_info = field_info_field_by_ids();
$fields = array();
$grouped_instances = array();
......@@ -307,7 +306,7 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
foreach ($instances as $instance) {
$field_id = $instance['field_id'];
$field_name = $instance['field_name'];
$field = $field_info[$field_id];
$field = field_info_field_by_id($field_id);
$function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
if (function_exists($function)) {
// Add the field to the list of fields to invoke the hook on.
......@@ -614,7 +613,6 @@ function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcod
* non-deleted fields are operated on.
*/
function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) {
$field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
// Merge default options.
......@@ -692,7 +690,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
}
// Collect the storage backend if the field has not been loaded yet.
if (!isset($skip_fields[$field_id])) {
$field = $field_info[$field_id];
$field = field_info_field_by_id($field_id);
$storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid;
}
}
......
......@@ -319,7 +319,11 @@ function field_read_field($field_name, $include_additional = array()) {
* Reads in fields that match an array of conditions.
*
* @param array $params
* An array of conditions to match against.
* An array of conditions to match against. Keys are columns from the
* 'field_config' table, values are conditions to match. Additionally,
* conditions on the 'entity_type' and 'bundle' columns from the
* 'field_config_instance' table are supported (select fields having an
* instance on a given bundle).
* @param array $include_additional
* The default behavior of this function is to not return fields that
* are inactive or have been deleted. Setting
......@@ -337,8 +341,21 @@ function field_read_fields($params = array(), $include_additional = array()) {
// Turn the conditions into a query.
foreach ($params as $key => $value) {
// Allow filtering on the 'entity_type' and 'bundle' columns of the
// field_config_instance table.
if ($key == 'entity_type' || $key == 'bundle') {
if (empty($fci_join)) {
$fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id');
}
$key = 'fci.' . $key;
}
else {
$key = 'fc.' . $key;
}
$query->condition($key, $value);
}
if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
$query
->condition('fc.active', 1)
......
......@@ -5,6 +5,7 @@ version = VERSION
core = 7.x
files[] = field.module
files[] = field.attach.inc
files[] = field.info.class.inc
files[] = tests/field.test
dependencies[] = field_sql_storage
required = TRUE
......
This diff is collapsed.
This diff is collapsed.
......@@ -459,6 +459,13 @@ function field_update_7002() {
}
}
/**
* Add the FieldInfo class to the class registry.
*/
function field_update_7003() {
// Empty update to force a rebuild of the registry.
}
/**
* @} End of "addtogroup updates-7.x-extra".
*/
......@@ -873,7 +873,8 @@ function field_view_field($entity_type, $entity, $field_name, $display = array()
if ($field = field_info_field($field_name)) {
if (is_array($display)) {
// When using custom display settings, fill in default values.
$display = _field_info_prepare_instance_display($field, $display);
$cache = _field_info_field_cache();
$display = $cache->prepareInstanceDisplay($display, $field["type"]);
}
// Hook invocations are done through the _field_invoke() functions in
......
......@@ -324,11 +324,14 @@ function field_sql_storage_field_storage_delete_field($field) {
* Implements hook_field_storage_load().
*/
function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) {
$field_info = field_info_field_by_ids();
$load_current = $age == FIELD_LOAD_CURRENT;
foreach ($fields as $field_id => $ids) {
$field = $field_info[$field_id];
// By the time this hook runs, the relevant field definitions have been
// populated and cached in FieldInfo, so calling field_info_field_by_id()
// on each field individually is more efficient than loading all fields in
// memory upfront with field_info_field_by_ids().
$field = field_info_field_by_id($field_id);
$field_name = $field['field_name'];
$table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
......
......@@ -1144,6 +1144,16 @@ class FieldInfoTestCase extends FieldTestCase {
$this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.');
$instances = field_info_instances('user', 'user');
$this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array.");
// Test that querying for invalid entity types does not add entries in the
// list returned by field_info_instances().
field_info_cache_clear();
field_info_instances('invalid_entity', 'invalid_bundle');
// Simulate new request by clearing static caches.
drupal_static_reset();
field_info_instances('invalid_entity', 'invalid_bundle');
$instances = field_info_instances();
$this->assertFalse(isset($instances['invalid_entity']), 'field_info_instances() does not contain entries for the invalid entity type that was queried before');
}
/**
......@@ -1253,6 +1263,80 @@ class FieldInfoTestCase extends FieldTestCase {
$this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), t('No instances are returned on disabled entity types.'));
}
/**
* Test field_info_field_map().
*/
function testFieldMap() {
// We will overlook fields created by the 'standard' install profile.
$exclude = field_info_field_map();
// Create a new bundle for 'test_entity' entity type.
field_test_create_bundle('test_bundle_2');
// Create a couple fields.
$fields = array(
array(
'field_name' => 'field_1',
'type' => 'test_field',
),
array(
'field_name' => 'field_2',
'type' => 'hidden_test_field',
),
);
foreach ($fields as $field) {
field_create_field($field);
}
// Create a couple instances.
$instances = array(
array(
'field_name' => 'field_1',
'entity_type' => 'test_entity',
'bundle' => 'test_bundle',
),
array(
'field_name' => 'field_1',
'entity_type' => 'test_entity',
'bundle' => 'test_bundle_2',
),
array(
'field_name' => 'field_2',
'entity_type' => 'test_entity',
'bundle' => 'test_bundle',
),
array(
'field_name' => 'field_2',
'entity_type' => 'test_cacheable_entity',
'bundle' => 'test_bundle',
),
);
foreach ($instances as $instance) {
field_create_instance($instance);
}
$expected = array(
'field_1' => array(
'type' => 'test_field',
'bundles' => array(
'test_entity' => array('test_bundle', 'test_bundle_2'),
),
),
'field_2' => array(
'type' => 'hidden_test_field',
'bundles' => array(
'test_entity' => array('test_bundle'),
'test_cacheable_entity' => array('test_bundle'),
),
),
);
// Check that the field map is correct.
$map = field_info_field_map();
$map = array_diff_key($map, $exclude);
$this->assertEqual($map, $expected);
}
/**
* Test that the field_info settings convenience functions work.
*/
......@@ -1277,6 +1361,31 @@ class FieldInfoTestCase extends FieldTestCase {
$this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings");
}
}
/**
* Tests that the field info cache can be built correctly.
*/
function testFieldInfoCache() {
// Create a test field and ensure it's in the array returned by
// field_info_fields().
$field_name = drupal_strtolower($this->randomName());
$field = array(
'field_name' => $field_name,
'type' => 'test_field',
);
field_create_field($field);
$fields = field_info_fields();
$this->assertTrue(isset($fields[$field_name]), 'The test field is initially found in the array returned by field_info_fields().');
// Now rebuild the field info cache, and set a variable which will cause
// the cache to be cleared while it's being rebuilt; see
// field_test_entity_info(). Ensure the test field is still in the returned
// array.
field_info_cache_clear();
variable_set('field_test_clear_info_cache_in_hook_entity_info', TRUE);
$fields = field_info_fields();
$this->assertTrue(isset($fields[$field_name]), 'The test field is found in the array returned by field_info_fields() even if its cache is cleared while being rebuilt.');
}
}
class FieldFormTestCase extends FieldTestCase {
......@@ -2178,6 +2287,41 @@ class FieldCrudTestCase extends FieldTestCase {
$this->assertTrue($field_definition < $field, t('The field was properly read.'));
}
/**
* Tests reading field definitions.
*/
function testReadFields() {
$field_definition = array(
'field_name' => 'field_1',
'type' => 'test_field',
);
field_create_field($field_definition);
// Check that 'single column' criteria works.
$fields = field_read_fields(array('field_name' => $field_definition['field_name']));
$this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
// Check that 'multi column' criteria works.
$fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => $field_definition['type']));
$this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
$fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => 'foo'));
$this->assertTrue(empty($fields), 'No field was found.');
// Create an instance of the field.
$instance_definition = array(
'field_name' => $field_definition['field_name'],
'entity_type' => 'test_entity',
'bundle' => 'test_bundle',
);
field_create_instance($instance_definition);
// Check that criteria spanning over the field_config_instance table work.
$fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle']));
$this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
$fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name']));
$this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.');
}
/**
* Test creation of indexes on data column.
*/
......
......@@ -9,6 +9,12 @@
* Implements hook_entity_info().
*/
function field_test_entity_info() {
// If requested, clear the field cache while this hook is being called. See
// testFieldInfoCache().
if (variable_get('field_test_clear_info_cache_in_hook_entity_info', FALSE)) {
field_info_cache_clear();
}
$bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle')));
$test_entity_modes = array(
'full' => array(
......
......@@ -332,23 +332,30 @@ function _field_ui_bundle_admin_path($entity_type, $bundle_name) {
* Identifies inactive fields within a bundle.
*/
function field_ui_inactive_instances($entity_type, $bundle_name = NULL) {
if (!empty($bundle_name)) {
$inactive = array($bundle_name => array());
$params = array('bundle' => $bundle_name);
$params = array('entity_type' => $entity_type);
if (empty($bundle_name)) {
$active = field_info_instances($entity_type);
$inactive = array();
}
else {
$inactive = array();
$params = array();
// Restrict to the specified bundle. For consistency with the case where
// $bundle_name is NULL, the $active and $inactive arrays are keyed by
// bundle name first.
$params['bundle'] = $bundle_name;
$active = array($bundle_name => field_info_instances($entity_type, $bundle_name));
$inactive = array($bundle_name => array());
}
$params['entity_type'] = $entity_type;
$active_instances = field_info_instances($entity_type);
// Iterate on existing definitions, and spot those that do not appear in the
// $active list collected earlier.
$all_instances = field_read_instances($params, array('include_inactive' => TRUE));
foreach ($all_instances as $instance) {
if (!isset($active_instances[$instance['bundle']][$instance['field_name']])) {
if (!isset($active[$instance['bundle']][$instance['field_name']])) {
$inactive[$instance['bundle']][$instance['field_name']] = $instance;
}
}
if (!empty($bundle_name)) {
return $inactive[$bundle_name];
}
......
......@@ -269,7 +269,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
*/
function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') {
// Reset the fields info.
_field_info_collate_fields(TRUE);
field_info_cache_clear();
// Assert field settings.
$field = field_info_field($field_name);
$this->assertTrue($field['settings']['test_field_setting'] == $string, t('Field settings were found.'));
......@@ -360,7 +360,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
$this->fieldUIDeleteField($bundle_path1, $this->field_name, $this->field_label, $this->type);
// Reset the fields info.
_field_info_collate_fields(TRUE);
field_info_cache_clear();
// Check that the field instance was deleted.
$this->assertNull(field_info_instance('node', $this->field_name, $this->type), t('Field instance was deleted.'));
// Check that the field was not deleted
......@@ -370,7 +370,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
$this->fieldUIDeleteField($bundle_path2, $this->field_name, $this->field_label, $type_name2);
// Reset the fields info.
_field_info_collate_fields(TRUE);
field_info_cache_clear();
// Check that the field instance was deleted.
$this->assertNull(field_info_instance('node', $this->field_name, $type_name2), t('Field instance was deleted.'));
// Check that the field was deleted too.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment