Index: relation.field.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/relation/relation.field.inc,v
retrieving revision 1.1
diff -u -p -r1.1 relation.field.inc
--- relation.field.inc	24 Nov 2010 20:57:49 -0000	1.1
+++ relation.field.inc	4 Dec 2010 11:31:13 -0000
@@ -27,38 +27,79 @@ function relation_field_info() {
 /**
  * Implements hook_field_is_empty().
  */
-function relation_field_is_empty() {
-  return FALSE;
+function relation_field_is_empty($item, $field) {
+  return empty($item['relation_id']);
 }
 
 /**
- * Implements hook_field_insert().
+ * Implements hook_field_presave().
  */
-function relation_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
-  list($entity_id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
-  $insert1 = db_insert('relation')->fields(array('predicate'));
-  $insert2 = db_insert('relation_data')->fields(array('relation_id', 'entity_type', 'entity_id'));
+function relation_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  $field_name = $field['field_name'];
+  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
+
+  if (isset($entity->relation_recursion)) {
+    return;
+  }
+
+  $relations = array();
   foreach ($items as $item) {
-    if (!empty($item['entity_id'])) {
-      $relation_id = $insert1->values(array($field['field_name']))->execute();
-      $insert2->values(array($relation_id, $item['entity_type'], $item['entity_id']));
-      $insert2->values(array($relation_id, $entity_type, $entity_id));
-    }
+    $relations[$item['entity_type']][$item['relation_id']] = $item['entity_id'];
   }
-  $insert2->execute();
-  $items = array();
-}
 
-/**
- * Implements hook_field_update().
- */
-function relation_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
-  relation_field_delete($entity_type, $entity, $field, $instance, $langcode, $items);
-  relation_field_insert($entity_type, $entity, $field, $instance, $langcode, $items);
+  foreach ($relations as $target_type => $source_relations) {
+    $targets = entity_load($target_type, $source_relations);
+    // @todo The else condition must not happen; implement hook_field_validate().
+    if (!$targets) {
+      continue;
+    }
+    foreach ($targets as $target_id => $target) {
+      // A new field instance on existing entities contains no structure yet.
+      if (!isset($target->{$field_name}[$langcode])) {
+        $target->{$field_name}[$langcode] = array();
+      }
+      $target_items = &$target->{$field_name}[$langcode];
+
+      // Transform current relations on the target into a lookup map keyed by
+      // relation_id.
+      $target_relations = array();
+      foreach ($target_items as $delta => $item) {
+        $target_relations[$item['relation_id']] = array(
+          'delta' => $delta,
+          'item' => $item,
+        );
+      }
+      // Add a new relation, if it does not exist yet.
+      foreach ($source_relations as $relation_id => $target_entity_id) {
+        if (!isset($target_relations[$relation_id])) {
+          $target_items[] = array(
+            'relation_id' => $relation_id,
+            'entity_type' => $entity_type,
+            'entity_id' => $id,
+            // @todo Add optional revision_id handling.
+            // 'revision_id' => $vid,
+          );
+        }
+      }
+      // Prevent infinite recursion; ENTITY_save() calls hook_field_presave().
+      $target->relation_recursion = TRUE;
+      // @todo Entity module's entity_save() does not handle core entities yet.
+      // @see http://drupal.org/node/988780
+      $result = entity_save($target_type, $target);
+      if ($result === FALSE && function_exists($target_type . '_save')) {
+        $function = $target_type . '_save';
+        $function($target);
+      }
+      unset($target->relation_recursion);
+    }
+  }
 }
 
 /**
  * Implements hook_field_delete().
+ *
+ * This hook is invoked when an entity is deleted. All entities that are
+ * referencing the deleted entity need to be updated to remove the relation(s).
  */
 function relation_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
   list($entity_id) = entity_extract_ids($entity_type, $entity);
@@ -73,25 +114,14 @@ function relation_field_delete($entity_t
 }
 
 /**
- * Implements hook_field_load().
+ * Implements hook_field_instance_delete().
+ *
+ * This hook is invoked after a field instance has been marked for deletion.
+ * This means that an entire relation endpoint has been removed, so all
+ * relations to entities of the field instance need to be deleted.
  */
-function relation_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
-  foreach ($entities as $entity) {
-    list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
-    $entity_ids[] = $id;
-  }
-  $query = db_select('relation', 'base')
-    ->condition('l.entity_type', $entity_type)
-    ->condition('l.entity_id', $entity_ids);
-  _relation_entity_query_helper($query);
-  foreach ($query->execute() as $item) {
-    $items[$item->left_entity_id][] = array(
-      'relation_id' => $item->relation_id,
-      'predicate' => $item->predicate,
-      'entity_id' => $item->right_entity_id,
-      'entity_type' => $item->right_entity_type,
-    );
-  }
+function relation_field_instance_delete($instance) {
+  // @todo
 }
 
 /**
@@ -114,7 +144,7 @@ function relation_field_widget_form(&$fo
   $element['entity_type'] = array(
     '#type' => 'select',
     '#title' => t('Entity type'),
-    '#options' => drupal_map_assoc(array_keys(entity_get_info())),
+    '#options' => drupal_map_assoc(relation_get_possible_targets($field['field_name'])),
     '#default_value' => isset($items[$delta]) ? $items[$delta]['entity_type'] : '',
   );
   $element['entity_id'] = array(
@@ -122,10 +152,26 @@ function relation_field_widget_form(&$fo
     '#type' => 'textfield',
     '#default_value' => isset($items[$delta]) ? $items[$delta]['entity_id'] : '',
   );
+  $element['relation_id'] = array(
+    '#type' => 'hidden',
+    '#value' => !empty($items[$delta]['relation_id']) ? $items[$delta]['relation_id'] : db_next_id(),
+  );
   return $element;
 }
 
 /**
+ * Helper function to get all entity types with given field attached.
+ */
+function relation_get_possible_targets($field_name) {
+  $instances = field_read_instances(array('field_name' => $field_name));
+  $entity_types = array();
+  foreach ($instances as $instance) {
+    $entity_types[] = $instance['entity_type'];
+  };
+  return $entity_types;
+}
+
+/**
  * Implements hook_field_formatter_info().
  */
 function relation_field_formatter_info() {
@@ -141,15 +187,22 @@ function relation_field_formatter_info()
  * Implements hook_field_formatter_view().
  */
 function relation_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
-  $list = array();
+  $links = array();
   foreach ($items as $item) {
+    $class = $item['entity_type'] . '-' . $item['entity_id'];
     $uri = entity_uri($item['entity_type'], $item['entity']);
-    $list[] = l($item['entity_type'], $uri['path'], $uri['options']);
+    $links[$class] = array(
+      'title' => entity_label($item['entity_type'], $item['entity']),
+      'href' => $uri['path'],
+    ) + $uri;
+  }
+  if ($links) {
+    $element[0] = array(
+      '#theme' => 'links',
+      '#links' => $links,
+    );
+    return $element;
   }
-  return array(
-    '#theme' => 'item_list',
-    '#items' => $list,
-  );
 }
 
 /**
Index: relation.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/relation/relation.info,v
retrieving revision 1.3
diff -u -p -r1.3 relation.info
--- relation.info	24 Nov 2010 21:12:52 -0000	1.3
+++ relation.info	4 Dec 2010 10:24:00 -0000
@@ -1,6 +1,7 @@
 ; $Id: relation.info,v 1.3 2010/11/24 21:12:52 sun Exp $
 name = Relation
 description = Describes relationships between entities.
+dependencies[] = entity
 core = 7.x
 files[] = relation.module
 files[] = relation.field.inc
Index: relation.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/relation/relation.install,v
retrieving revision 1.2
diff -u -p -r1.2 relation.install
--- relation.install	24 Nov 2010 20:57:49 -0000	1.2
+++ relation.install	4 Dec 2010 10:26:39 -0000
@@ -7,37 +7,15 @@
  */
 
 /**
- * Implements hook_schema().
+ * Implements hook_field_schema().
  */
-function relation_schema() {
-  $schema['relation'] = array(
-    'description' => 'Stores predicates of relations.',
-    'fields' => array(
-      'relation_id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'predicate' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-    ),
-    'primary key' => array('relation_id'),
-    'indexes' => array(
-      'predicates' => array('predicate', 'relation_id'),
-    ),
-  );
-  $schema['relation_data'] = array(
-    'description' => 'Stores relations between entities.',
-    'fields' => array(
+function relation_field_schema() {
+  $schema = array(
+    'columns' => array(
       'relation_id' => array(
         'type' => 'int',
-        'unsigned' => TRUE,
+        'unsigned' => FALSE,
         'not null' => TRUE,
-        'default' => 0,
       ),
       'entity_type' => array(
         'type' => 'varchar',
@@ -47,15 +25,19 @@ function relation_schema() {
       ),
       'entity_id' => array(
         'type' => 'int',
-        'unsigned' => TRUE,
+        'unsigned' => FALSE,
         'not null' => TRUE,
         'default' => 0,
       ),
+      'revision_id' => array(
+        'type' => 'int',
+        'unsigned' => FALSE,
+        'not null' => FALSE,
+      ),
     ),
-    'primary key' => array('relation_id', 'entity_type', 'entity_id'),
     'indexes' => array(
       'relation_id' => array('relation_id'),
-      'entity' => array('entity_type', 'entity_id'),
+      'type_id' => array('entity_type', 'entity_id'),
     ),
   );
   return $schema;
Index: relation.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/relation/relation.module,v
retrieving revision 1.2
diff -u -p -r1.2 relation.module
--- relation.module	24 Nov 2010 20:57:49 -0000	1.2
+++ relation.module	4 Dec 2010 11:20:57 -0000
@@ -14,6 +14,7 @@ require_once dirname(__FILE__) . '/relat
  * Implements hook_entity_info().
  */
 function relation_entity_info() {
+  return;
   $entities['relation'] = array(
     'label' => t('Relation'),
     'base table' => 'relation',
@@ -41,6 +42,9 @@ function relation_entity_info() {
  * Implements hook_entity_info_alter().
  */
 function relation_entity_info_alter(&$entity_info) {
+  $entity_info['node']['static cache'] = FALSE;
+  $entity_info['node']['field cache'] = FALSE;
+  return;
   $entity_info['node']['relationships']['user'] = array(
     // a handler -- this can be reusable or specific to the entity type
     // it will do things like list 'user' entities related to one or more nodes
@@ -72,15 +76,12 @@ class RelationEntityController extends D
   }
 }
 
-function _relation_entity_query_helper($query) {
-  // Add the machine name field from the {taxonomy_vocabulary} table.
-  $query->innerJoin('relation_data', 'l', 'base.relation_id = l.relation_id');
-  $query->innerJoin('relation_data', 'r', 'base.relation_id = r.relation_id AND NOT (l.entity_type = r.entity_type AND l.entity_id = r.entity_id)');
-  $query->addField('base', 'relation_id');
-  $query->addField('base', 'predicate');
-  $query->addField('l', 'entity_type', 'left_entity_type');
+function _relation_entity_query_helper($query, $table, $field_name) {
+  $query->innerJoin($table, 'r', "l.{$field_name}_relation_id = r.{$field_name}_relation_id AND NOT (l.etid = r.etid AND l.entity_id = r.entity_id)");
+  $query->addField('l', "{$field_name}_relation_id", 'relation_id');
+  $query->addField('l', 'etid', 'left_etid');
   $query->addField('l', 'entity_id', 'left_entity_id');
-  $query->addField('r', 'entity_type', 'right_entity_type');
+  $query->addField('r', 'etid', 'right_etid');
   $query->addField('r', 'entity_id', 'right_entity_id');
 }
 
