Commit 803b8b39 authored by Dries's avatar Dries

- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach,...

- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.

The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
parent 25299606
......@@ -70,10 +70,10 @@ function hook_field_extra_fields($bundle) {
/**
* @defgroup field_types Field Types API
* @{
* Define field types, widget types, and display formatter types.
* Define field types, widget types, display formatter types, storage types.
*
* The bulk of the Field Types API are related to field types. A field type
* represents a particular data storage type (integer, string, date, etc.) that
* represents a particular type of data (integer, string, date, etc.) that
* can be attached to a fieldable object. hook_field_info() defines the basic
* properties of a field type, and a variety of other field hooks are called by
* the Field Attach API to perform field-type-specific actions.
......@@ -97,6 +97,9 @@ function hook_field_extra_fields($bundle) {
* behavior of existing field types.
* @see hook_field_widget_info().
* @see hook_field_formatter_info().
*
* A third kind of pluggable handlers, storage backends, is defined by the
* @link field_storage Field Storage API @endlink.
*/
/**
......@@ -1096,6 +1099,45 @@ function hook_field_attach_delete_bundle($bundle, $instances) {
* @{
*/
/**
* Expose Field API storage backends.
*
* @return
* An array describing the storage backends implemented by the module.
* The keys are storage backend names. To avoid name clashes, storage backend
* names should be prefixed with the name of the module that exposes them.
* The values are arrays describing the storage backend, with the following
* key/value pairs:
* - label: The human-readable name of the storage backend.
* - description: A short description for the storage backend.
* - settings: An array whose keys are the names of the settings available
* for the storage backend, and whose values are the default values for
* those settings.
*/
function hook_field_storage_info() {
return array(
'field_sql_storage' => array(
'label' => t('Default SQL storage'),
'description' => t('Stores fields in the local SQL database, using per-field tables.'),
'settings' => array(),
),
);
}
/**
* Perform alterations on Field API storage types.
*
* @param $info
* Array of informations on storage types exposed by
* hook_field_field_storage_info() implementations.
*/
function hook_field_storage_info_alter(&$info) {
// Add a setting to a storage type.
$info['field_sql_storage']['settings'] += array(
'mymodule_additional_setting' => 'default value',
);
}
/**
* Load field data for a set of objects.
*
......@@ -1107,15 +1149,15 @@ function hook_field_attach_delete_bundle($bundle, $instances) {
* FIELD_LOAD_CURRENT to load the most recent revision for all
* fields, or FIELD_LOAD_REVISION to load the version indicated by
* each object.
* @param $skip_fields
* An array keyed by field ids whose data has already been loaded and
* therefore should not be loaded again. The values associated to these keys
* are not specified.
* @param $fields
* An array listing the fields to be loaded. The keys of the array are field
* ids, the values of the array are the object ids (or revision ids,
* depending on the $age parameter) to be loaded for each field.
* @return
* Loaded field values are added to $objects. Fields with no values should be
* set as an empty array.
*/
function hook_field_storage_load($obj_type, $objects, $age, $skip_fields) {
function hook_field_storage_load($obj_type, $objects, $age, $fields) {
}
/**
......@@ -1128,12 +1170,11 @@ function hook_field_storage_load($obj_type, $objects, $age, $skip_fields) {
* @param $op
* FIELD_STORAGE_UPDATE when updating an existing object,
* FIELD_STORAGE_INSERT when inserting a new object.
* @param $skip_fields
* An array keyed by field ids whose data has already been written and
* therefore should not be written again. The values associated to these keys
* are not specified.
* @param $fields
* An array listing the fields to be written. The keys and values of the
* array are field ids.
*/
function hook_field_storage_write($obj_type, $object, $op, $skip_fields) {
function hook_field_storage_write($obj_type, $object, $op, $fields) {
}
/**
......@@ -1143,8 +1184,11 @@ function hook_field_storage_write($obj_type, $object, $op, $skip_fields) {
* The entity type of object, such as 'node' or 'user'.
* @param $object
* The object on which to operate.
* @param $fields
* An array listing the fields to delete. The keys and values of the
* array are field ids.
*/
function hook_field_storage_delete($obj_type, $object) {
function hook_field_storage_delete($obj_type, $object, $fields) {
}
/**
......@@ -1159,8 +1203,11 @@ function hook_field_storage_delete($obj_type, $object) {
* The object on which to operate. The revision to delete is
* indicated by the object's revision id property, as identified by
* hook_fieldable_info() for $obj_type.
* @param $fields
* An array listing the fields to delete. The keys and values of the
* array are field ids.
*/
function hook_field_storage_delete_revision($obj_type, $object) {
function hook_field_storage_delete_revision($obj_type, $object, $fields) {
}
/**
......@@ -1185,26 +1232,6 @@ function hook_field_storage_delete_revision($obj_type, $object) {
function hook_field_storage_query($field_name, $conditions, $count, &$cursor = NULL, $age) {
}
/**
* Act on creation of a new bundle.
*
* @param $bundle
* The name of the bundle being created.
*/
function hook_field_storage_create_bundle($bundle) {
}
/**
* Act on a bundle being renamed.
*
* @param $bundle_old
* The old name of the bundle.
* @param $bundle_new
* The new name of the bundle.
*/
function hook_field_storage_rename_bundle($bundle_old, $bundle_new) {
}
/**
* Act on creation of a new field.
*
......@@ -1217,21 +1244,19 @@ function hook_field_storage_create_field($field) {
/**
* Act on deletion of a field.
*
* @param $field_name
* The name of the field being deleted.
* @param $field
* The field being deleted.
*/
function hook_field_storage_delete_field($field_name) {
function hook_field_storage_delete_field($field) {
}
/**
* Act on deletion of a field instance.
*
* @param $field_name
* The name of the field in the new instance.
* @param $bundle
* The name of the bundle in the new instance.
* @param $instance
* The instance being deleted.
*/
function hook_field_storage_delete_instance($field_name, $bundle) {
function hook_field_storage_delete_instance($instance) {
}
/**
......
This diff is collapsed.
This diff is collapsed.
......@@ -78,6 +78,7 @@ function _field_info_collate_types($reset = FALSE) {
'field types' => array(),
'widget types' => array(),
'formatter types' => array(),
'storage types' => array(),
'fieldable types' => array(),
);
......@@ -110,7 +111,7 @@ function _field_info_collate_types($reset = FALSE) {
}
drupal_alter('field_widget_info', $info['widget types']);
// Populate formatters.
// Populate formatter types.
foreach (module_implements('field_formatter_info') as $module) {
$formatter_types = (array) module_invoke($module, 'field_formatter_info');
foreach ($formatter_types as $name => $formatter_info) {
......@@ -124,6 +125,20 @@ function _field_info_collate_types($reset = FALSE) {
}
drupal_alter('field_formatter_info', $info['formatter types']);
// Populate storage types.
foreach (module_implements('field_storage_info') as $module) {
$storage_types = (array) module_invoke($module, 'field_storage_info');
foreach ($storage_types as $name => $storage_info) {
// Provide defaults.
$storage_info += array(
'settings' => array(),
);
$info['storage types'][$name] = $storage_info;
$info['storage types'][$name]['module'] = $module;
}
}
drupal_alter('field_storage_info', $info['storage types']);
// Populate information about 'fieldable' entities.
foreach (module_implements('entity_info') as $module) {
$entities = (array) module_invoke($module, 'entity_info');
......@@ -246,6 +261,7 @@ function _field_info_collate_fields($reset = FALSE) {
function _field_info_prepare_field($field) {
// Make sure all expected field settings are present.
$field['settings'] += field_info_field_settings($field['type']);
$field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
return $field;
}
......@@ -371,8 +387,8 @@ function field_info_field_types($field_type = NULL) {
* returned.
* @return
* Either a widget type description, as provided by
* hook_field_widget_info(), or an array of all existing widget
* types, keyed by widget type name.
* hook_field_widget_info(), or an array of all existing widget types, keyed
* by widget type name.
*/
function field_info_widget_types($widget_type = NULL) {
$info = _field_info_collate_types();
......@@ -394,8 +410,9 @@ function field_info_widget_types($widget_type = NULL) {
* (optional) A formatter type name. If ommitted, all formatter types will be
* returned.
* @return
* Either a formatter type description, as provided by hook_field_formatter_info(),
* or an array of all existing widget types, keyed by widget type name.
* Either a formatter type description, as provided by
* hook_field_formatter_info(), or an array of all existing formatter types,
* keyed by formatter type name.
*/
function field_info_formatter_types($formatter_type = NULL) {
$info = _field_info_collate_types();
......@@ -410,6 +427,30 @@ function field_info_formatter_types($formatter_type = NULL) {
}
}
/**
* Return hook_field_storage_info() data.
*
* @param $storage_type
* (optional) A storage type name. If ommitted, all storage types will be
* returned.
* @return
* Either a storage type description, as provided by
* hook_field_storage_info(), or an array of all existing storage types,
* keyed by storage type name.
*/
function field_info_storage_types($storage_type = NULL) {
$info = _field_info_collate_types();
$storage_types = $info['storage types'];
if ($storage_type) {
if (isset($storage_types[$storage_type])) {
return $storage_types[$storage_type];
}
}
else {
return $storage_types;
}
}
/**
* Return hook_fieldable_info() data.
*
......@@ -574,8 +615,8 @@ function field_info_instance_settings($type) {
* @param $type
* A widget type name.
* @return
* The field type's default settings, as provided by hook_field_info(), or an
* empty array.
* The widget type's default settings, as provided by
* hook_field_widget_info(), or an empty array.
*/
function field_info_widget_settings($type) {
$info = field_info_widget_types($type);
......@@ -588,14 +629,28 @@ function field_info_widget_settings($type) {
* @param $type
* A field formatter type name.
* @return
* The field formatter's default settings, as provided by
* hook_field_info(), or an empty array.
* The formatter type's default settings, as provided by
* hook_field_formatter_info(), or an empty array.
*/
function field_info_formatter_settings($type) {
$info = field_info_formatter_types($type);
return isset($info['settings']) ? $info['settings'] : array();
}
/**
* Return a field formatter's default settings.
*
* @param $type
* A field storage type name.
* @return
* The storage type's default settings, as provided by
* hook_field_storage_info(), or an empty array.
*/
function field_info_storage_settings($type) {
$info = field_info_storage_types($type);
return isset($info['settings']) ? $info['settings'] : array();
}
/**
* @} End of "defgroup field_info"
*/
......@@ -28,41 +28,63 @@ function field_schema() {
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'description' => 'The type of this field, coming from a field module',
'description' => 'The type of this field.',
),
'locked' => array(
'module' => array(
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
'description' => 'The module that implements the field type.',
),
'active' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'description' => '@TODO',
'description' => 'Boolean indicating whether the module that implements the field type is enabled.',
),
'data' => array(
'type' => 'text',
'size' => 'medium',
'storage_type' => array(
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'serialize' => TRUE,
'description' => 'Field specific settings, for example maximum length',
'description' => 'The storage backend for the field.',
),
'module' => array(
'storage_module' => array(
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
'description' => 'The module that implements the storage backend.',
),
'cardinality' => array(
'storage_active' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'description' => 'Boolean indicating whether the module that implements the storage backend is enabled.',
),
'translatable' => array(
'locked' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
'description' => '@TODO',
),
'active' => array(
'data' => array(
'type' => 'text',
'size' => 'medium',
'not null' => TRUE,
'serialize' => TRUE,
'description' => 'Serialized data containing the field properties that do not warrant a dedicated column.',
),
'cardinality' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
),
'translatable' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
......@@ -77,14 +99,17 @@ function field_schema() {
),
'primary key' => array('id'),
'indexes' => array(
// used by field_delete_field() among others
'field_name' => array('field_name'),
// used by field_read_fields()
'active_deleted' => array('active', 'deleted'),
// used by field_modules_disabled()
// Used by field_read_fields().
'active' => array('active'),
'storage_active' => array('storage_active'),
'deleted' => array('deleted'),
// Used by field_modules_disabled().
'module' => array('module'),
// used by field_associate_fields()
'storage_module' => array('storage_module'),
// Used by field_associate_fields().
'type' => array('type'),
'storage_type' => array('storage_type'),
),
);
$schema['field_config_instance'] = array(
......@@ -124,13 +149,14 @@ function field_schema() {
),
'primary key' => array('id'),
'indexes' => array(
// used by field_delete_instance()
// Used by field_delete_instance().
'field_name_bundle' => array('field_name', 'bundle'),
// used by field_read_instances()
'widget_active_deleted' => array('widget_active', 'deleted'),
// used by field_modules_disabled()
// Used by field_read_instances().
'widget_active' => array('widget_active'),
'deleted' => array('deleted'),
// Used by field_modules_disabled().
'widget_module' => array('widget_module'),
// used by field_associate_fields()
// Used by field_associate_fields().
'widget_type' => array('widget_type'),
),
);
......
......@@ -219,6 +219,10 @@ function field_modules_disabled($modules) {
->fields(array('active' => 0))
->condition('module', $module)
->execute();
db_update('field_config')
->fields(array('storage_active' => 0))
->condition('storage_module', $module)
->execute();
db_update('field_config_instance')
->fields(array('widget_active' => 0))
->condition('widget_module', $module)
......@@ -234,25 +238,32 @@ function field_modules_disabled($modules) {
* The name of the module to update on.
*/
function field_associate_fields($module) {
$module_fields = module_invoke($module, 'field_info');
if ($module_fields) {
foreach ($module_fields as $name => $field_info) {
watchdog('field', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module));
db_update('field_config')
->fields(array('module' => $module, 'active' => 1))
->condition('type', $name)
->execute();
}
// Associate field types.
$field_types =(array) module_invoke($module, 'field_info');
foreach ($field_types as $name => $field_info) {
watchdog('field', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module));
db_update('field_config')
->fields(array('module' => $module, 'active' => 1))
->condition('type', $name)
->execute();
}
$module_widgets = module_invoke($module, 'widget_info');
if ($module_widgets) {
foreach ($module_widgets as $name => $widget_info) {
watchdog('field', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module));
db_update('field_config_instance')
->fields(array('widget_module' => $module, 'widget_active' => 1))
->condition('widget_type', $name)
->execute();
}
// Associate storage backends.
$storage_types = (array) module_invoke($module, 'field_storage_info');
foreach ($storage_types as $name => $storage_info) {
watchdog('field', 'Updating field storage %type with module %module.', array('%type' => $name, '%module' => $module));
db_update('field_config')
->fields(array('storage_module' => $module, 'storage_active' => 1))
->condition('storage_type', $name)
->execute();
}
// Associate widget types.
$widget_types = (array) module_invoke($module, 'field_widget_info');
foreach ($widget_types as $name => $widget_info) {
watchdog('field', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module));
db_update('field_config_instance')
->fields(array('widget_module' => $module, 'widget_active' => 1))
->condition('widget_type', $name)
->execute();
}
}
......
This diff is collapsed.
......@@ -37,7 +37,9 @@ function field_sql_storage_schema() {
$fields = field_read_fields(array(), array('include_deleted' => TRUE, 'include_inactive' => TRUE));
drupal_load('module', 'field_sql_storage');
foreach ($fields as $field) {
$schema += _field_sql_storage_schema($field);
if ($field['storage']['type'] == 'field_sql_storage') {
$schema += _field_sql_storage_schema($field);
}
}
}
return $schema;
......
......@@ -183,8 +183,8 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
$this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}");
// Check that update leaves the field data untouched if
// $object->{$field_name} has no language key.
unset($entity->{$this->field_name}[$langcode]);
// $object->{$field_name} is absent.
unset($entity->{$this->field_name});
field_attach_update($entity_type, $entity);
$rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
foreach ($values as $delta => $value) {
......@@ -194,7 +194,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
}
// Check that update with an empty $object->$field_name empties the field.
$entity->{$this->field_name}[$langcode] = NULL;
$entity->{$this->field_name} = NULL;
field_attach_update($entity_type, $entity);
$rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
$this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field."));
......@@ -217,7 +217,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
$this->assertEqual($count, 0, 'Missing field results in no inserts');
// Insert: Field is NULL
$entity->{$this->field_name}[$langcode] = NULL;
$entity->{$this->field_name} = NULL;
field_attach_insert($entity_type, $entity);
$count = db_select($this->table)
->countQuery()
......
This diff is collapsed.
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