Index: handlers/views_handler_relationship_groupwise_max.inc
===================================================================
RCS file: handlers/views_handler_relationship_groupwise_max.inc
diff -N handlers/views_handler_relationship_groupwise_max.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ handlers/views_handler_relationship_groupwise_max.inc	22 Jun 2009 20:06:36 -0000
@@ -0,0 +1,287 @@
+<?php
+// $Id$
+/*
+ * @file
+ * Relationship for groupwise maximum handler.
+ */
+
+/**
+ * Relationship handler that allows a groupwise maximum of the linked in table. 
+ * For a definition, see: 
+ * http://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html
+ * In lay terms, instead of joining to get all matching records in the linked 
+ * table, we get only one record, a 'representative record' picked according
+ * to a given criterion.
+ * 
+ * Example:
+ * Suppose we have a term view that gives us the terms: Horse, Cat, Aardvark.
+ * We wish to show for each term the most recent node of that term.
+ * What we want is some kind of relationship from term to node.
+ * But a regular relationship will give us all the nodes for each term, 
+ * giving the view multiple rows per term. What we want is just one 
+ * representative node per term, the node that is the 'best' in some way:
+ * eg, the most recent, the most commented on, the first in alphabetical order.
+ * 
+ * This handler gives us that kind of relationship from term to node.
+ * The method of choosing the 'best' implemented with a sort
+ * that the user selects in the relationship settings. 
+ * 
+ * So if we want our term view to show the most commented node for each term, 
+ * add the relationship and in its options, pick the 'Comment count' sort.
+ * 
+ * Relationship definition
+ *  - 'outer field': The outer field to substitute into the correlated subquery.
+ *       This must be the full field name, not the alias. 
+ *       Eg: 'term_data.tid'.
+ *  - 'argument table',
+ *    'argument field': These options define a views argument that the subquery
+ *     must add to itself to filter by the main view.
+ *     Example: the main view shows terms, this handler is being used to get to
+ *     the nodes base table. Your argument must be 'term_node', 'tid', as this 
+ *     is the argument that should be added to a node view to filter on terms.
+ * 
+ * A note on performance:
+ * This relationship uses a correlated subquery, which is expensive.
+ * Subsequent versions of this handler could also implement the alternative way 
+ * of doing this, with a join -- though this looks like it could be pretty messy
+ * to implement. This is also an expensive method, so providing both methods and
+ * allowing the user to choose which one works fastest for their data might be 
+ * the best way.
+ * If your use of this relationship handler is likely to result in large 
+ * data sets, you might want to consider storing statistics in a separate table,
+ * in the same way as node_comment_statistics.
+ */
+class views_handler_relationship_groupwise_max extends views_handler_relationship {
+  
+  /**
+   * Defines default values for options.
+   */
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['subquery_sort'] = array('default' => array(NULL)); // TODO: Correct structure?
+    $options['subquery_order'] = array('default' => 'DESC'); // Descending more useful.
+    $options['subquery_regenerate'] = array('default' => FALSE);
+
+    return $options;
+  }
+
+  /**
+   * Extends the relationship's basic options, allowing the user to pick
+   * a sort and an order for it.
+   */
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    
+    // Get the sorts that apply to our base.
+    $sorts = views_fetch_fields($this->definition['base'], 'sort');    
+    foreach ($sorts as $sort_id => $sort) {
+      $options[$sort_id] = "$sort[group]: $sort[title]";
+    }
+    
+    $form['subquery_sort'] = array(
+      '#type' => 'select',
+      '#title' => t('Representative sort criterion'),
+      '#default_value' => $this->options['subquery_sort'],
+      '#options' => $options,
+      '#description' => theme('advanced_help_topic', 'views', 'relationship-representative') .
+      t('This sort determines how the representative item is chosen. Eg, to show the most recent node for each term in a term view, select "Node: Post date".'),
+    );
+
+    $form['subquery_order'] = array(
+      '#type' => 'radios',
+      '#title' => t('Representative sort order'),
+      '#options' => array('ASC' => t('Ascending'), 'DESC' => t('Descending')),
+      '#default_value' => $this->options['subquery_order'],
+    ); 
+    
+    // WIP: This stuff doens't work yet: namespacing issues.
+    /*
+    // A list of suitable views to pick one as the subview.   
+    $views = array('' => '<none>');
+    $all_views = views_get_all_views();   
+    foreach ($all_views as $view) {
+      // Only get views that are suitable:
+      // - base must the base that our relationship joins towards
+      // - must have fields.
+      if ($view->base_table == $this->definition['base'] && !empty($view->display['default']->display_options['fields'])) {
+        // TODO: check the field is the correct sort?
+        // or let users hang themselves at this stage and check later?
+        if ($view->type == 'Default') {
+          $views[t('Default Views')][$view->name] = $view->name;
+        }
+        else {
+          $views[t('Existing Views')][$view->name] = $view->name;
+        }
+      }
+    }
+    
+    
+    $form['subquery_view'] = array(
+      '#type' => 'select',
+      '#title' => t('Representative view'),
+      '#default_value' => $this->options['subquery_view'],
+      '#options' => $views,
+      '#description' => t('Advanced. Use another view to generate the relationship subquery. This allows you to use filtering and more than one sort. If you pick a view here, the sort options above are ignored. Your view must have the ID of its base as its only field, and should have some kind of sorting.'),
+    ); 
+    */
+    
+    $form['subquery_regenerate'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Generate subquery each time view is run.'),
+      '#default_value' => $this->options['subquery_regenerate'],
+      '#description' => t('Will re-generate the subquery for this relationship every time the view is run, instead of only when these options are saved. Use for testing if you are making changes elsewhere. WARNING: seriously impairs performance.'),
+    );        
+  } 
+   
+  /**
+   * Perform any necessary changes to the form values prior to storage.
+   * There is no need for this function to actually store the data.
+   *
+   * Generate the subquery string when the user submits the options, and store 
+   * it. This saves the expense of generating it when the view is run.
+   */
+  function options_submit($form, &$form_state) { 
+    // Get the new user options from the form values.
+    $new_options = $form_state['values']['options'];   
+    $subquery = $this->left_query($new_options);
+    // Add the subquery string to the options we're about to store.
+    $this->options['subquery_string'] = $subquery;
+  }
+   
+  /**
+   * Generate a subquery given the user options, as set in the options. 
+   * These are passed in rather than picked up from the object because we 
+   * generate the subquery when the options are saved, rather than when the view
+   * is run. This saves considerable time.
+   *
+   * @param $options
+   *   An array of options:
+   *    - subquery_sort: the id of a views sort.
+   *    - subquery_order: either ASC or DESC.
+   * @return
+   *    The subquery SQL string, ready for use in the main query.
+   */
+  function left_query($options) {
+    $namespace = "_inner"; // String to add to aliases.
+    
+    require_once('views_handler_relationship_groupwise_max_helpers.inc');
+    
+    /* WIP:
+    // Either load another view, or create one on the fly.
+    if ($options['subquery_view']) {
+      // We don't use views_get_view because we want our own class of view.
+      views_include('view');      
+      $temp_view = view_aliased::load($options['subquery_view']);
+      dsm($temp_view);
+    }
+    */
+
+    // Create a new view object on the fly.
+    // We use this to generate a query from the chosen sort.
+    // This has to be a special class: see this for details.
+    $temp_view = views_new_view_aliased();
+    $temp_view->namespace = $namespace;
+    
+    // Add the sort from the options to the default display.
+    $sort = $options['subquery_sort'];
+    list($sort_table, $sort_field) = explode('.', $sort);
+    $sort_options = array('order' => $options['subquery_order']);
+    $temp_view->add_item('default', 'sort', $sort_table, $sort_field, $sort_options);
+            
+    // Add the correct argument for our relationship's base
+    // ie the 'how to get back to base' argument.
+    // The relationship definition tells us which one to use.
+    $temp_view->add_item(
+      'default', 
+      'argument', 
+      $this->definition['argument table'], // eg 'term_node', 
+      $this->definition['argument field'] //  eg 'tid'
+    );
+    
+    // The value we add here does nothing, but doing this adds the right tables 
+    // and puts in a WHERE clause with a placeholder we can grab later.
+    $temp_view->args[] = '**CORRELATED**';
+
+    //dsm($temp_view);
+    
+    // Build the view. The creates the query object and produces the query 
+    // string but does not run any queries.
+    $temp_view->build();
+    
+    // Now collect the query SQL string.
+    $subquery = $temp_view->build_info['query'];
+    //dsm("$subquery");\
+  
+    // We need to prevent the last %d placeholder from getting replaced with an
+    // argument value, because it's the one that needs to get the outer
+    // reference field.
+    // Replacing the %d with %dd protects it.
+    $subquery = preg_replace('/%d(?!.*%d.*)/', '%%d', $subquery);
+
+    // Get the arguments from the view build info.
+    $args = $temp_view->build_info['query_args'];  
+ 
+    // Replace the placeholders with the arguments.
+    _db_query_callback($args, TRUE);
+    $subquery = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $subquery);
+        
+    // Add in the outer field.
+    $subquery = preg_replace('/%d/', $this->definition['outer field'], $subquery);
+
+    // The query we get doesn't include the LIMIT.
+    // TODO: Is there a better way than adding it by hand?
+    $subquery .= ' LIMIT 1';
+        
+    //dsm($subquery);    
+    return $subquery;
+  }
+      
+  /**
+   * Called to implement a relationship in a query.
+   * This is mostly a copy of our parent's query() except for this bit with
+   * the join class.
+   */
+  function query() {
+    // Figure out what base table this relationship brings to the party.
+    $table_data = views_fetch_data($this->definition['base']);
+    $base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field'];
+
+    $this->ensure_my_table();
+
+    $def = $this->definition;
+    $def['table'] = $this->definition['base'];
+    $def['field'] = $base_field;
+    $def['left_table'] = $this->table_alias;
+    $def['left_field'] = $this->field;
+    if (!empty($this->options['required'])) {
+      $def['type'] = 'INNER';
+    }
+    
+    if ($this->options['subquery_regenerate']) {
+      // For testing only, regenerate the subquery each time.
+      $def['left_query'] = $this->left_query($this->options);     
+    }
+    else {
+      // Get the stored subquery SQL string.
+      $def['left_query'] = $this->options['subquery_string'];
+    }
+
+    if (!empty($def['join_handler']) && class_exists($def['join_handler'])) {
+      $join = new $def['join_handler'];
+    }
+    else {
+      $join = new views_join_subquery();
+    }
+
+    $join->definition = $def;
+    $join->construct();
+    $join->adjusted = TRUE;
+
+    // use a short alias for this:
+    $alias = $def['table'] . '_' . $this->table;
+
+    $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship);
+  }
+}
+
Index: handlers/views_handler_relationship_groupwise_max_helpers.inc
===================================================================
RCS file: handlers/views_handler_relationship_groupwise_max_helpers.inc
diff -N handlers/views_handler_relationship_groupwise_max_helpers.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ handlers/views_handler_relationship_groupwise_max_helpers.inc	22 Jun 2009 20:06:39 -0000
@@ -0,0 +1,205 @@
+<?php
+// $Id$
+/*
+ * @file
+ * Helper classes for relationship for groupwise maximum handler.
+ * These are only needed when the user saves the handler options.
+ */
+
+
+/**
+ * Create a new view of our dummy class.
+ */
+function views_new_view_aliased() {
+  views_include('view');
+  $view = new view_aliased();
+  $view->vid = 'new';
+  $view->add_display('default');
+
+  return $view;
+}
+
+/**
+ * Dummy class: serves only to get our own query class into the view.
+ */
+class view_aliased extends view {
+  function init_query() {
+    // Create and initialize the query object.
+    $views_data = views_fetch_data($this->base_table);
+    $this->base_field = $views_data['table']['base']['field'];
+    if (!empty($views_data['table']['base']['database'])) {
+      $this->base_database = $views_data['table']['base']['database'];
+    }
+    views_include('query');
+    $this->query = new views_query_aliased($this->base_table, $this->base_field);
+    // doesn't work! constructor needs this!
+    //$this->query->namespace = $this->namespace;
+  } 
+}
+
+// We don't have this when saving the handler options.
+views_include('query');
+
+/**
+ * Our own query class: just namespaces all our tables.
+ * This ensures the outer reference field can't match anything in the query.
+ */
+class views_query_aliased extends views_query {
+  /**
+   * Constructor. 
+   * Define the namespace, and add it to the base table.
+   */
+  function views_query_aliased($base_table = 'node', $base_field = 'nid') {
+    // Call the parent constructor.
+    $this->views_query($base_table, $base_field);
+    
+    // Set our namespace. Hardcoded for now but you never know.
+    $this->namespace = "_INNER";
+    //$this->namespace = $this->view->namespace;
+    //dsm($this);
+    
+    // Now add a namespace suffix to the base table alias.
+    $this->table_queue[$base_table]['alias'] .= $this->namespace;
+  }
+    
+  /**
+   * Add the namespace to subsequent tables.
+   * Interpose ourselves between the caller and the parent function.
+   */
+  function queue_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
+    // Call the parent implementation, getting the alias.
+    $alias = parent::queue_table($table, $relationship, $join, $alias);
+    
+    // Add the namespace to the table alias.
+    $alias_namespaced = $alias . $this->namespace;
+    $this->table_queue[$alias]['alias'] = $alias_namespaced;
+    // Return the alias to the handler.
+    return $alias_namespaced;
+    // For reasons I don't understand, the key in the table_queue should
+    // stay as it is, otherwise we get two of them.
+  }
+  
+  /**
+   * ORDER BY clauses must be properly referred to by table,
+   * as we remove the explicit field they refer to in the SELECT.
+   */
+  function add_orderby($table, $field, $order, $alias = '') {
+    $alias = $this->table_queue[$table]['alias'] . '.' . $field;
+    
+    parent::add_orderby($table, $field, $order, $alias);
+  }
+  
+  /**
+   * Build the query string.
+   * Deviations from the parent class are marked.
+   */
+  function query($get_count = FALSE) {   
+    // Check query distinct value.
+    if (empty($this->no_distinct) && $this->distinct && !empty($this->fields)) {
+      if (!empty($this->fields[$this->base_field])) {
+        $this->fields[$this->base_field]['distinct'] = TRUE;
+      }
+    }
+    
+    /**
+     * An optimized count query includes just the base field instead of all the fields.
+     * Determine of this query qualifies by checking for a groupby or distinct.
+     */
+    $fields_array = $this->fields;
+    
+    if ($get_count && !$this->groupby) {
+      foreach ($fields_array as $field) {
+        if (!empty($field['distinct'])) {
+          $get_count_optimized = FALSE;
+          break;
+        }
+      }
+    }
+    else {
+      $get_count_optimized = FALSE;
+    }
+    if (!isset($get_count_optimized)) {
+      $get_count_optimized = TRUE;
+    }
+    
+    $joins = $fields = $where = $having = $orderby = $groupby = '';
+    // Add all the tables to the query via joins. We assume all LEFT joins.
+        
+    foreach ($this->table_queue as $table) {
+      if (is_object($table['join'])) {
+        $joins .= $table['join']->join($table, $this) . "\n";
+      }
+    }
+
+    $has_aggregate = FALSE;
+    $non_aggregates = array();
+
+    foreach ($fields_array as $field) {
+      if ($fields) {
+        $fields .= ",\n   ";
+      }
+      
+      $string = '';
+      if (!empty($field['table'])) {
+        // Deviation: add our namespace to the fields.
+        $string .= $field['table'] . $this->namespace . '.';
+      }
+      $string .= $field['field'];
+
+      // store for use with non-aggregates below
+      $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
+
+      if (!empty($field['distinct'])) {
+        $string = "DISTINCT($string)";
+      }
+      if (!empty($field['count'])) {
+        $string = "COUNT($string)";
+        $has_aggregate = TRUE;
+      }
+      else if (!empty($field['aggregate'])) {
+        $has_aggregate = TRUE;
+      }
+      else {
+        $non_aggregates[] = $fieldname;
+      }
+      if ($field['alias']) {
+        $string .= " AS $field[alias]";
+      }
+      $fields .= $string;
+
+      if ($get_count_optimized) {
+        // We only want the first field in this case.
+        break;
+      }
+      
+      // Deviation: we only want the first field.
+      break;
+    }
+
+    if ($has_aggregate || $this->groupby) {
+      $groupby = "GROUP BY " . implode(', ', array_unique(array_merge($this->groupby, $non_aggregates))) . "\n";
+      if ($this->having) {
+        $having = $this->condition_sql('having');
+      }
+    }
+
+    if (!$get_count_optimized) {
+      // we only add the groupby if we're not counting.
+      if ($this->orderby) {
+        $orderby = "ORDER BY " . implode(', ', $this->orderby) . "\n";
+      }
+    }
+
+    $where = $this->condition_sql();
+    
+    // Deviation: alias the base table.
+    $base_table_alias = $this->table_queue[$this->base_table]['alias'];
+    // Deviation: and add it here.
+    $query = "SELECT $fields\n FROM {" . $this->base_table . "} $base_table_alias \n$joins $where $groupby $having $orderby";
+
+    $replace = array('&gt;' => '>', '&lt;' => '<');
+    $query = strtr($query, $replace);
+
+    return $query;
+  }   
+}
Index: help/relationship-representative.html
===================================================================
RCS file: help/relationship-representative.html
diff -N help/relationship-representative.html
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ help/relationship-representative.html	22 Jun 2009 20:06:41 -0000
@@ -0,0 +1,15 @@
+<!-- $Id$ -->
+A representative relationship obtains just one object from the linked in objects. 
+
+This is best explained with an example. Suppose you have a term view that shows you the terms in your vocabulary: Horse, Cat, Aardvark.
+In addition to the term names, you want to show the title of the most recent node in that term. This would give you a view that shows: Horse - latest horse node title, Cat - latest cat node title, Aardvark - latest aardvark node title.
+Each of these is the title of the <em>representative node</em> for that term, chosen by creation time.
+
+Any sort criterion can be used to choose the representative node. You might instead want to show the node with the most comments for each term, the first node in alphabetical order, or if you have a voting module installed, the most popular node.
+
+The options for a representative relationship let you choose a sort criterion and a sort order. This determines how the representative object is chosen: the first object returned by the sort is shown. For example, choose 'Node: title' and 'Ascending' to get the first node by title as the representative; or 'Node: comment count' and 'Descending' to get the node with the most comments.
+
+<h2>Performance</h2>
+These relationships require a correlated subquery. This can be slow to run on large amounts of data, as the subquery must be run for every row of the main query.
+
+For more on this topic, see the <a href="http://dev.mysql.com/doc/refman/5.0/en/example-maximum-column-group-row.html">MySQL tutorial on group-wise maximum queries</a>.
Index: help/views.help.ini
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/help/views.help.ini,v
retrieving revision 1.17
diff -u -p -r1.17 views.help.ini
--- help/views.help.ini	22 Apr 2009 07:03:35 -0000	1.17
+++ help/views.help.ini	22 Jun 2009 20:06:44 -0000
@@ -80,6 +80,10 @@ parent = about
 title = "Relationships"
 parent = about
 
+[relationship-representative]
+title = "Representative relationships"
+parent = relationship
+
 [style]
 title = "Output styles (View styles)"
 weight = -20 
Index: includes/handlers.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/handlers.inc,v
retrieving revision 1.114
diff -u -p -r1.114 handlers.inc
--- includes/handlers.inc	4 Jun 2009 20:20:45 -0000	1.114
+++ includes/handlers.inc	22 Jun 2009 20:06:55 -0000
@@ -1105,6 +1105,9 @@ function views_views_handlers() {
       'views_handler_relationship' => array(
         'parent' => 'views_handler',
       ),
+      'views_handler_relationship_groupwise_max' => array(
+        'parent' => 'views_handler_relationship',
+      ),
 
 
       // sort handlers
@@ -1310,6 +1313,88 @@ class views_join {
 }
 
 /**
+ * Join handler for relationships that join with a subquery as the left field.
+ * eg:
+ *  LEFT JOIN node node_term_data ON ([YOUR SUBQUERY HERE]) = node_term_data.nid 
+ *
+ * join definition
+ *   same as views_join class above, except:
+ *   - left_query: The subquery to use in the left side of the join clause.
+ */
+class views_join_subquery extends views_join {
+  // PHP 4 doesn't call constructors of the base class automatically from a
+  // constructor of a derived class. It is your responsibility to propagate
+  // the call to constructors upstream where appropriate.
+  function construct($table = NULL, $left_table = NULL, $left_field = NULL, $field = NULL, $extra = array(), $type = 'LEFT') {
+    parent::construct($table, $left_table, $left_field, $field, $extra, $type);
+
+    $this->left_query = $this->definition['left_query'];
+  }
+
+  /**
+   * Build the SQL for the join this object represents.
+   */
+  function join($table, &$query) {
+    $output = " $this->type JOIN {" . $this->table . "} $table[alias] ON ($this->left_query) = $table[alias].$this->field";
+
+    // Tack on the extra.
+    if (isset($this->extra)) {
+      if (is_array($this->extra)) {
+        $extras = array();
+        foreach ($this->extra as $info) {
+          $extra = '';
+          // Figure out the table name. Remember, only use aliases provided
+          // if at all possible.
+          $join_table = '';
+          if (!array_key_exists('table', $info)) {
+            $join_table = $table['alias'] . '.';
+          }
+          elseif (isset($info['table'])) {
+            $join_table = $info['table'] . '.';
+          }
+
+          // And now deal with the value and the operator.  Set $q to
+          // a single-quote for non-numeric values and the
+          // empty-string for numeric values, then wrap all values in $q.
+          $raw_value = $this->db_safe($info['value']);
+          $q = (empty($info['numeric']) ? "'" : '');
+
+          if (is_array($raw_value)) {
+            $operator = !empty($info['operator']) ? $info['operator'] : 'IN';
+            // Transform from IN() notation to = notation if just one value.
+            if (count($raw_value) == 1) {
+              $value = $q . array_shift($raw_value) . $q;
+              $operator = $operator == 'NOT IN' ? '!=' : '=';
+            }
+            else {
+              $value = "($q" . implode("$q, $q", $raw_value) . "$q)";
+            }
+          }
+          else {
+            $operator = !empty($info['operator']) ? $info['operator'] : '=';
+            $value = "$q$raw_value$q";
+          }
+          $extras[] = "$join_table$info[field] $operator $value";
+        }
+
+        if ($extras) {
+          if (count($extras) == 1) {
+            $output .= ' AND ' . array_shift($extras);
+          }
+          else {
+            $output .= ' AND (' . implode(' ' . $this->extra_type . ' ', $extras) . ')';
+          }
+        }
+      }
+      else if ($this->extra && is_string($this->extra)) {
+        $output .= " AND ($this->extra)";
+      }
+    }
+    return $output;
+  }
+}
+
+/**
  * @}
  */
 
Index: modules/taxonomy.views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/modules/taxonomy.views.inc,v
retrieving revision 1.54
diff -u -p -r1.54 taxonomy.views.inc
--- modules/taxonomy.views.inc	5 Jun 2009 01:26:35 -0000	1.54
+++ modules/taxonomy.views.inc	22 Jun 2009 20:06:58 -0000
@@ -113,6 +113,17 @@ function taxonomy_views_data() {
       'numeric' => TRUE,
       'skip base' => array('node', 'node_revision'),
     ),
+    'relationship' => array(
+      'title' => t('Representative node'),
+      'label'  => t('Representative node'),
+      'help' => t('Obtains a single representative node for each term, acccording to a chosen sort criterion.'),
+      'handler' => 'views_handler_relationship_groupwise_max',
+      'base'   => 'node',
+      'field'  => 'nid',
+      'outer field' => 'term_data.tid',
+      'argument table' => 'term_node',
+      'argument field' =>  'tid',
+    ),
   );
 
   // Term name field
Index: modules/user.views.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/modules/user.views.inc,v
retrieving revision 1.57
diff -u -p -r1.57 user.views.inc
--- modules/user.views.inc	2 Jun 2009 20:18:26 -0000	1.57
+++ modules/user.views.inc	22 Jun 2009 20:07:01 -0000
@@ -62,6 +62,18 @@ function user_views_data() {
     'sort' => array(
       'handler' => 'views_handler_sort',
     ),
+    'relationship' => array(
+      'title' => t('Representative node'),
+      'label'  => t('Representative node'),
+      'help' => t('Obtains a single representative node for each user, acccording to a chosen sort criterion.'),
+      'handler' => 'views_handler_relationship_groupwise_max',
+      'base'   => 'node',
+      'field'  => 'nid',
+      'outer field' => 'users.uid',
+      'argument table' => 'users',
+      'argument field' =>  'uid',
+    ),
+    
   );
 
   // uid
