diff options
author | Dries Buytaert <dries@buytaert.net> | 2009-09-27 12:52:55 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2009-09-27 12:52:55 +0000 |
commit | 803b8b3940257330e3945e243d559718c85cc481 (patch) | |
tree | ba082ac801da31f06385ead5bea4338eb4ccc4c0 /modules/simpletest/tests/field_test.module | |
parent | 252996066042b290bff9bebba8f8256cab62a999 (diff) | |
download | brdo-803b8b3940257330e3945e243d559718c85cc481.tar.gz brdo-803b8b3940257330e3945e243d559718c85cc481.tar.bz2 |
- 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.
Diffstat (limited to 'modules/simpletest/tests/field_test.module')
-rw-r--r-- | modules/simpletest/tests/field_test.module | 440 |
1 files changed, 430 insertions, 10 deletions
diff --git a/modules/simpletest/tests/field_test.module b/modules/simpletest/tests/field_test.module index 1f21e5dc4..93b8084e5 100644 --- a/modules/simpletest/tests/field_test.module +++ b/modules/simpletest/tests/field_test.module @@ -641,6 +641,436 @@ function field_test_entity_info_translatable($obj_type = NULL, $translatable = N } /** + * + * 'Field storage' API. + * + */ + +/** + * Implement hook_field_storage_info(). + */ +function field_test_field_storage_info() { + return array( + 'field_test_storage' => array( + 'label' => t('Test storage'), + 'description' => t('Dummy test storage backend. Stores field values in the variable table.'), + ), + 'field_test_storage_failure' => array( + 'label' => t('Test storage failure'), + 'description' => t('Dummy test storage backend. Always fails to create fields.'), + ), + ); +} + +/** + * Helper function: store or retrieve data from the 'storage backend'. + */ +function _field_test_storage_data($data = NULL) { + if (is_null($data)) { + return variable_get('field_test_storage_data', array()); + } + else { + variable_set('field_test_storage_data', $data); + } +} + +/** + * Implement hook_field_storage_load(). + */ +function field_test_field_storage_load($obj_type, $objects, $age, $fields, $options) { + $data = _field_test_storage_data(); + + $load_current = $age == FIELD_LOAD_CURRENT; + + foreach ($fields as $field_id => $ids) { + $field = field_info_field_by_id($field_id); + $field_name = $field['field_name']; + $field_data = $data[$field['id']]; + $sub_table = $load_current ? 'current' : 'revisions'; + $delta_count = array(); + foreach ($field_data[$sub_table] as $row) { + if ($row->type == $obj_type && (!$row->deleted || $options['deleted'])) { + if (($load_current && in_array($row->entity_id, $ids)) || (!$load_current && in_array($row->revision_id, $ids))) { + if (in_array($row->language, field_multilingual_available_languages($obj_type, $field))) { + if (!isset($delta_count[$row->entity_id][$row->language])) { + $delta_count[$row->entity_id][$row->language] = 0; + } + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->language] < $field['cardinality']) { + $item = array(); + foreach ($field['columns'] as $column => $attributes) { + $item[$column] = $row->{$column}; + } + $objects[$row->entity_id]->{$field_name}[$row->language][] = $item; + $delta_count[$row->entity_id][$row->language]++; + } + } + } + } + } + } +} + +/** + * Implement hook_field_storage_write(). + */ +function field_test_field_storage_write($obj_type, $object, $op, $fields) { + $data = _field_test_storage_data(); + + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); + + foreach ($fields as $field_id) { + $field = field_info_field_by_id($field_id); + $field_name = $field['field_name']; + $field_data = &$data[$field_id]; + + $all_languages = field_multilingual_available_languages($obj_type, $field); + $field_languages = array_intersect($all_languages, array_keys((array) $object->$field_name)); + + // Delete and insert, rather than update, in case a value was added. + if ($op == FIELD_STORAGE_UPDATE) { + // Delete languages present in the incoming $object->$field_name. + // Delete all languages if $object->$field_name is empty. + $languages = !empty($object->$field_name) ? $field_languages : $all_languages; + if ($languages) { + foreach ($field_data['current'] as $key => $row) { + if ($row->type == $obj_type && $row->entity_id == $id && in_array($row->language, $languages)) { + unset($field_data['current'][$key]); + } + } + if (isset($vid)) { + foreach ($field_data['revisions'] as $key => $row) { + if ($row->type == $obj_type && $row->revision_id == $vid) { + unset($field_data['revisions'][$key]); + } + } + } + } + } + + foreach ($field_languages as $langcode) { + $items = (array) $object->{$field_name}[$langcode]; + $delta_count = 0; + foreach ($items as $delta => $item) { + $row = (object) array( + 'field_id' => $field_id, + 'type' => $obj_type, + 'entity_id' => $id, + 'revision_id' => $vid, + 'bundle' => $bundle, + 'delta' => $delta, + 'deleted' => FALSE, + 'language' => $langcode, + ); + foreach ($field['columns'] as $column => $attributes) { + $row->{$column} = isset($item[$column]) ? $item[$column] : NULL; + } + + $field_data['current'][] = $row; + if (isset($vid)) { + $field_data['revisions'][] = $row; + } + + if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { + break; + } + } + } + } + + _field_test_storage_data($data); +} + +/** + * Implement hook_field_storage_delete(). + */ +function field_test_field_storage_delete($obj_type, $object, $fields) { + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); + + // Note: reusing field_test_storage_purge(), like field_sql_storage.module + // does, is highly inefficient in our case... + foreach (field_info_instances($bundle) as $instance) { + if (isset($fields[$instance['field_id']])) { + $field = field_info_field_by_id($instance['field_id']); + field_test_field_storage_purge($obj_type, $object, $field, $instance); + } + } +} + +/** + * Implement hook_field_storage_purge(). + */ +function field_test_field_storage_purge($obj_type, $object, $field, $instance) { + $data = _field_test_storage_data(); + + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); + + $field_data = &$data[$field['id']]; + foreach (array('current', 'revisions') as $sub_table) { + foreach ($field_data[$sub_table] as $key => $row) { + if ($row->type == $obj_type && $row->entity_id == $id) { + unset($field_data[$sub_table][$key]); + } + } + } + + _field_test_storage_data($data); +} + +/** + * Implement hook_field_storage_delete_revision(). + */ +function field_test_field_storage_delete_revision($obj_type, $object, $fields) { + $data = _field_test_storage_data(); + + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); + foreach ($fields as $field_id) { + $field_data = &$data[$field_id]; + foreach (array('current', 'revisions') as $sub_table) { + foreach ($field_data[$sub_table] as $key => $row) { + if ($row->type == $obj_type && $row->entity_id == $id && $row->revision_id == $vid) { + unset($field_data[$sub_table][$key]); + } + } + } + } + + _field_test_storage_data($data); +} + +/** + * Implement hook_field_storage_query(). + */ +function field_test_field_storage_query($field_id, $conditions, $count, &$cursor = NULL, $age) { + $data = _field_test_storage_data(); + + $load_current = $age == FIELD_LOAD_CURRENT; + + $field = field_info_field_by_id($field_id); + $field_columns = array_keys($field['columns']); + + $field_data = $data[$field['id']]; + $sub_table = $load_current ? 'current' : 'revisions'; + // We need to sort records by object type and object id. + usort($field_data[$sub_table], '_field_test_field_storage_query_sort_helper'); + + // Initialize results array. + $return = array(); + $obj_count = 0; + $rows_count = 0; + $rows_total = count($field_data[$sub_table]); + $skip = $cursor; + $skipped = 0; + + foreach ($field_data[$sub_table] as $row) { + if ($count != FIELD_QUERY_NO_LIMIT && $obj_count >= $count) { + break; + } + + if ($row->field_id == $field['id']) { + $match = TRUE; + $condition_deleted = FALSE; + // Add conditions. + foreach ($conditions as $condition) { + @list($column, $value, $operator) = $condition; + if (empty($operator)) { + $operator = is_array($value) ? 'IN' : '='; + } + switch ($operator) { + case '=': + $match = $match && $row->{$column} == $value; + break; + case '!=': + case '<': + case '<=': + case '>': + case '>=': + eval('$match = $match && '. $row->{$column} . ' ' . $operator . ' '. $value); + break; + case 'IN': + $match = $match && in_array($row->{$column}, $value); + break; + case 'NOT IN': + $match = $match && !in_array($row->{$column}, $value); + break; + case 'BETWEEN': + $match = $match && $row->{$column} >= $value[0] && $row->{$column} <= $value[1]; + break; + case 'STARTS_WITH': + case 'ENDS_WITH': + case 'CONTAINS': + // Not supported. + $match = FALSE; + break; + } + // Track condition on 'deleted'. + if ($column == 'deleted') { + $condition_deleted = TRUE; + } + } + + // Exclude deleted data unless we have a condition on it. + if (!$condition_deleted && $row->deleted) { + $match = FALSE; + } + + if ($match) { + if (is_null($skip) || $skipped >= $skip) { + $cursor++; + // If querying all revisions and the entity type has revisions, we need + // to key the results by revision_ids. + $entity_type = field_info_fieldable_types($row->type); + $id = ($load_current || empty($entity_type['object keys']['revision'])) ? $row->entity_id : $row->revision_id; + + if (!isset($return[$row->type][$id])) { + $return[$row->type][$id] = field_create_stub_entity($row->type, array($row->entity_id, $row->revision_id, $row->bundle)); + $obj_count++; + } + } + else { + $skipped++; + } + } + } + $rows_count++; + + // The query is complete if we walked the whole array. + if ($count != FIELD_QUERY_NO_LIMIT && $rows_count >= $rows_total) { + $cursor = FIELD_QUERY_COMPLETE; + } + } + + return $return; +} + +/** + * Sort helper for field_test_field_storage_query(). + * + * Sort by object type and object id. + */ +function _field_test_field_storage_query_sort_helper($a, $b) { + if ($a->type == $b->type) { + if ($a->entity_id == $b->entity_id) { + return 0; + } + else { + return $a->entity_id < $b->entity_id ? -1 : 1; + } + } + else { + return $a->type < $b->type ? -1 : 1; + } +} + +/** + * Implement hook_field_storage_create_field(). + */ +function field_test_field_storage_create_field($field) { + if ($field['storage']['type'] == 'field_test_storage_failure') { + throw new Exception('field_test_storage_failure engine always fails to create fields'); + } + + $data = _field_test_storage_data(); + + $data[$field['id']] = array( + 'current' => array(), + 'revisions' => array(), + ); + + _field_test_storage_data($data); +} + +/** + * Implement hook_field_storage_delete_field(). + */ +function field_test_field_storage_delete_field($field) { + $data = _field_test_storage_data(); + + $field_data = &$data[$field['id']]; + foreach (array('current', 'revisions') as $sub_table) { + foreach ($field_data[$sub_table] as &$row) { + $row->deleted = TRUE; + } + } + + _field_test_storage_data($data); +} + +/** + * Implement hook_field_storage_delete_instance(). + */ +function field_test_field_storage_delete_instance($instance) { + $data = _field_test_storage_data(); + + $field = field_info_field($instance['field_name']); + $field_data = &$data[$field['id']]; + foreach (array('current', 'revisions') as $sub_table) { + foreach ($field_data[$sub_table] as &$row) { + if ($row->bundle == $instance['bundle']) { + $row->deleted = TRUE; + } + } + } + + _field_test_storage_data($data); +} + +/** + * Implement hook_field_attach_create_bundle(). + */ +function field_test_field_attach_create_bundle($bundle) { + // We don't need to do anything here. +} + +/** + * Implement hook_field_attach_rename_bundle(). + */ +function field_test_field_attach_rename_bundle($bundle_old, $bundle_new) { + $data = _field_test_storage_data(); + + // We need to account for deleted or inactive fields and instances. + $instances = field_read_instances(array('bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE)); + foreach ($instances as $field_name => $instance) { + $field = field_info_field_by_id($instance['field_id']); + if ($field['storage']['type'] == 'field_test_storage') { + $field_data = &$data[$field['id']]; + foreach (array('current', 'revisions') as $sub_table) { + foreach ($field_data[$sub_table] as &$row) { + if ($row->bundle == $bundle_old) { + $row->bundle = $bundle_new; + } + } + } + } + } + + _field_test_storage_data($data); +} + +/** + * Implement hook_field_attach_delete_bundle(). + */ +function field_test_field_attach_delete_bundle($bundle, $instances) { + $data = _field_test_storage_data(); + + $instances = field_info_instances($bundle); + foreach ($instances as $field_name => $instance) { + $field = field_info_field($field_name); + if ($field['storage']['type'] == 'field_test_storage') { + $field_data = &$data[$field['id']]; + foreach (array('current', 'revisions') as $sub_table) { + foreach ($field_data[$sub_table] as &$row) { + if ($row->bundle == $bundle_old) { + $row->deleted = TRUE; + } + } + } + } + } + + _field_test_storage_data($data); +} + +/** * Store and retrieve keyed data for later verification by unit tests. * * This function is a simple in-memory key-value store with the @@ -725,13 +1155,3 @@ function field_test_field_delete($obj_type, $object, $field, $instance, $items) $args = func_get_args(); field_test_memorize(__FUNCTION__, $args); } - -/** - * - * 'Field storage' API. - * - */ - -function field_test_field_storage_create_field($field) { - throw new Exception('field_test storage module always fails to create fields'); -} |