diff --git CHANGELOG.txt CHANGELOG.txt
index 4eb3123..5382c32 100644
--- CHANGELOG.txt
+++ CHANGELOG.txt
@@ -57,7 +57,11 @@ Views 3.x-7.x-dev
     o by dereine: Fix views_get_module_apis, registry is not in anymore.
 
 
-Views 3.x-6.x-dev
+Views 6.x-3.x-dev
+    o #396380 by merlinofchaos, dereine and dagmar: Initial support for GROUP BY queries!!!!!!!!!!!!
+    o #535868 by dagmar: Exposed forms as plugins, helping to isolate exposed form code and increase the ability for modules to change the behavior of exposed forms.
+
+Views 6.x-3.0-alpha1
   Bug fixes:
     o Table style when not overriding sorts put sorts in wrong order.
     o #488908 by sl27257: "Node: Has new content" only worked when exposed.
diff --git handlers/views_handler_argument.inc handlers/views_handler_argument.inc
index 34c46c0..18d19c1 100644
--- handlers/views_handler_argument.inc
+++ handlers/views_handler_argument.inc
@@ -553,10 +553,9 @@ class views_handler_argument extends views_handler {
    */
   function summary_basics($count_field = TRUE) {
     // Add the number of nodes counter
-    $field = $this->query->base_table . '.' . $this->query->base_field;
     $distinct = ($this->view->display_handler->get_option('distinct') && empty($this->query->no_distinct));
 
-    $count_alias = $this->query->add_field(NULL, $field, 'num_records',
+    $count_alias = $this->query->add_field($this->query->base_table, $this->query->base_field, 'num_records',
                                            array('count' => TRUE, 'distinct' => $distinct));
     $this->query->add_groupby($this->name_alias);
 
diff --git handlers/views_handler_argument_group_by_numeric.inc handlers/views_handler_argument_group_by_numeric.inc
new file mode 100644
index 0000000..d83bd72
--- /dev/null
+++ handlers/views_handler_argument_group_by_numeric.inc
@@ -0,0 +1,18 @@
+<?php
+// $Id: views_handler_argument_group_by_numeric.inc,v 1.1.2.2 2009-11-23 20:43:06 merlinofchaos Exp $
+
+/**
+ * Simple handler for arguments using group by.
+ */
+class views_handler_argument_group_by_numeric extends views_handler_argument  {
+  function query($group_by = FALSE) {
+    $this->ensure_my_table();
+    $field = $this->get_field();
+
+    $this->query->add_having(0, $field, $this->argument);
+  }
+
+  function ui_name() {
+    return $this->get_field(parent::ui_name());
+  }
+}
diff --git handlers/views_handler_field.inc handlers/views_handler_field.inc
index 1ae6f51..826ed99 100644
--- handlers/views_handler_field.inc
+++ handlers/views_handler_field.inc
@@ -94,7 +94,12 @@ class views_handler_field extends views_handler {
           else {
             $table_alias = $this->table_alias;
           }
-          $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field']);
+          $params = array();
+          if (!empty($info['params'])) {
+            $params = $info['params'];
+          }
+
+          $this->aliases[$identifier] = $this->query->add_field($table_alias, $info['field'], NULL, $params);
         }
         else {
           $this->aliases[$info] = $this->query->add_field($this->table_alias, $info);
@@ -173,6 +178,8 @@ class views_handler_field extends views_handler {
    * should have.
    */
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+
     $form['label'] = array(
       '#type' => 'textfield',
       '#title' => t('Label'),
diff --git handlers/views_handler_field_group_by_numeric.inc handlers/views_handler_field_group_by_numeric.inc
new file mode 100644
index 0000000..030b470
--- /dev/null
+++ handlers/views_handler_field_group_by_numeric.inc
@@ -0,0 +1,45 @@
+<?php
+// $Id: views_handler_field_group_by_numeric.inc,v 1.1.2.1 2009-11-10 23:23:27 merlinofchaos Exp $
+
+/**
+ * Handler for GROUP BY on simple numeric fields.
+ */
+class views_handler_field_group_by_numeric extends views_handler_field {
+  function init(&$view, $options) {
+    parent::init($view, $options);
+
+    // Initialize the original handler.
+    $this->handler = views_get_handler($options['table'], $options['field'], 'field');
+    $this->handler->init($view, $options);
+  }
+
+  /**
+   * Called to add the field to a query.
+   */
+  function query() {
+    $this->ensure_my_table();
+    // Add the field, taking care of any aggregation that may affect it.
+    $params = array(
+      'function' => $this->options['group_type'],
+    );
+
+    debug($this->real_field);
+    $this->field_alias = $this->query->add_field($this->table_alias, $this->real_field, NULL, $params);
+    $this->add_additional_fields();
+  }
+
+  /**
+   * Called to determine what to tell the clicksorter.
+   */
+  function click_sort($order) {
+    $params = array(
+      'function' => $this->options['group_type'],
+    );
+
+    $this->query->add_orderby($this->table, $this->field, $order, $this->field_alias, $params);
+  }
+
+  function ui_name() {
+    return $this->get_field(parent::ui_name());
+  }
+}
diff --git handlers/views_handler_filter_group_by_numeric.inc handlers/views_handler_filter_group_by_numeric.inc
new file mode 100644
index 0000000..45a5b86
--- /dev/null
+++ handlers/views_handler_filter_group_by_numeric.inc
@@ -0,0 +1,47 @@
+<?php
+// $Id: views_handler_filter_group_by_numeric.inc,v 1.1.2.1 2009-11-10 23:23:27 merlinofchaos Exp $
+
+/**
+ * Simple filter to handle greater than/less than filters
+ */
+class views_handler_filter_group_by_numeric extends views_handler_filter_numeric {
+  function query() {
+    $this->ensure_my_table();
+    $field = $this->get_field();
+
+    $info = $this->operators();
+    if (!empty($info[$this->operator]['method'])) {
+      $this->{$info[$this->operator]['method']}($field);
+    }
+  }
+  function op_between($field) {
+    if ($this->operator == 'between') {
+      $this->query->add_having($this->options['group'], $field, $this->value['min'], '>=');
+      $this->query->add_having($this->options['group'], $field, $this->value['max'], '<=');
+    }
+    else {
+      $this->query->add_having($this->options['group'], $field, db_or()->condition($field, $this->value['min'], '>=')->condition($field, $this->value['max'], '<=')
+      );
+    }
+  }
+
+  function op_simple($field) {
+    $this->query->add_having($this->options['group'], $field, $this->value['value'], $this->operator);
+  }
+
+  function op_empty($field) {
+    if ($this->operator == 'empty') {
+      $operator = "IS NULL";
+    }
+    else {
+      $operator = "IS NOT NULL";
+    }
+
+    $this->query->add_having($this->options['group'], $field, NULL, $operator);
+  }
+
+  function ui_name() {
+    return $this->get_field(parent::ui_name());
+  }
+}
+
diff --git handlers/views_handler_sort_group_by_numeric.inc handlers/views_handler_sort_group_by_numeric.inc
new file mode 100644
index 0000000..e24f0fa
--- /dev/null
+++ handlers/views_handler_sort_group_by_numeric.inc
@@ -0,0 +1,29 @@
+<?php
+// $Id: views_handler_sort_group_by_numeric.inc,v 1.1.2.1 2009-11-10 23:23:27 merlinofchaos Exp $
+/**
+ * Handler for GROUP BY on simple numeric fields.
+ */
+class views_handler_sort_group_by_numeric extends views_handler_sort {
+  function init(&$view, $options) {
+    parent::init($view, $options);
+
+    // Initialize the original handler.
+    $this->handler = views_get_handler($options['table'], $options['field'], 'sort');
+    $this->handler->init($view, $options);
+  }
+
+  /**
+   * Called to add the field to a query.
+   */
+  function query() {
+    $params = array(
+      'function' => $this->options['group_type'],
+    );
+
+    $this->query->add_orderby($this->table, $this->field, $this->options['order'], NULL, $params);
+  }
+
+  function ui_name() {
+    return $this->get_field(parent::ui_name());
+  }
+}
diff --git includes/admin.inc includes/admin.inc
index 81f45fc..069ab07 100644
--- includes/admin.inc
+++ includes/admin.inc
@@ -1273,28 +1273,21 @@ function template_preprocess_views_ui_edit_item(&$vars) {
     // Get relationship labels
     $relationships = array();
     // @todo: get_handlers()
-    foreach ($display->handler->get_option('relationships') as $id => $relationship) {
-      $handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
-      if (empty($handler)) {
-        continue;
-      }
-      $handler->init($view, $relationship);
+    foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
       $relationships[$id] = $handler->label();
     }
   }
 
-  // @todo: get_handlers()
   foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
     $fields[$id] = array();
 
-    $handler = views_get_handler($field['table'], $field['field'], $type);
+    $handler = $display->handler->get_handler($type, $id);
     if (empty($handler)) {
       $fields[$id]['class'][] = 'broken';
       $fields[$id]['title'] = t("Error: handler for @table > @field doesn't exist!", array('@table' => $field['table'], '@field' => $field['field']));
       $fields[$id]['info'] = '';
       continue;
     }
-    $handler->init($view, $field);
 
     $field_name = $handler->ui_name(TRUE);
     if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
@@ -1308,6 +1301,10 @@ function template_preprocess_views_ui_edit_item(&$vars) {
     }
     $fields[$id]['info'] = $handler->admin_summary();
 
+    if ($display->handler->use_group_by()) {
+      $fields[$id]['links'] = l('<span>' . t('Group settings') . '</span>', "admin/structure/views/nojs/config-item-group/$view->name/$display->id/$type/$id", array('attributes' => array('class' => 'views-button-configure views-ajax-link', 'title' => t('Group settings')), 'html' => true));
+    }
+
     if ($handler->has_extra_options()) {
       $fields[$id]['links'] = l('<span>' . t('Settings') . '</span>', "admin/structure/views/nojs/config-item-extra/$view->name/$display->id/$type/$id", array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => t('Settings')), 'html' => true));
     }
@@ -1496,6 +1493,10 @@ function views_ui_ajax_forms($key = NULL) {
       'form_id' => 'views_ui_config_item_extra_form',
       'args' => array('type', 'id'),
     ),
+    'config-item-group' => array(
+      'form_id' => 'views_ui_config_item_group_form',
+      'args' => array('type', 'id'),
+    ),
     'change-style' => array(
       'form_id' => 'views_ui_change_style_form',
       'args' => array('type', 'id'),
@@ -2062,17 +2063,10 @@ function views_ui_rearrange_form($form, &$form_state) {
 
   // Get relationship labels
   $relationships = array();
-  // @todo: get_handlers()
-  foreach ($display->handler->get_option('relationships') as $id => $relationship) {
-    $handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
-    if (empty($handler)) {
-      continue;
-    }
-    $handler->init($view, $relationship);
+  foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
     $relationships[$id] = $handler->label();
   }
 
-  // @todo: get_handlers()
   foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
     $form[$id] = array('#tree' => TRUE);
     $form[$id]['weight'] = array(
@@ -2080,9 +2074,8 @@ function views_ui_rearrange_form($form, &$form_state) {
       '#delta' => 200,
       '#default_value' => ++$count,
     );
-    $handler = views_get_handler($field['table'], $field['field'], $type);
+    $handler = $display->handler->get_handler($type, $id);
     if ($handler) {
-      $handler->init($view, $field);
       $name = $handler->ui_name() . ' ' . $handler->admin_summary();
       if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
         $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
@@ -2204,7 +2197,7 @@ function views_ui_add_item_form($form, &$form_state) {
 
   // Figure out all the base tables allowed based upon what the relationships provide.
   $base_tables = $view->get_base_tables();
-  $options = views_fetch_fields(array_keys($base_tables), $type);
+  $options = views_fetch_fields(array_keys($base_tables), $type, $display->handler->use_group_by());
 
   if (!empty($options)) {
     $groups = array('all' => t('<All>'));
@@ -2270,6 +2263,11 @@ function views_ui_add_item_form_submit($form, &$form_state) {
       list($table, $field) = explode('.', $field, 2);
       $id = $form_state['view']->add_item($form_state['display_id'], $type, $table, $field);
 
+      // check to see if we have group by settings
+      if ($form_state['view']->display_handler->use_group_by()) {
+        views_ui_add_form_to_stack('config-item-group', $form_state['view'], $form_state['display_id'], array($type, $id));
+      }
+
       // check to see if this type has settings, if so add the settings form first
       $handler = views_get_handler($table, $field, $type);
       if ($handler && $handler->has_extra_options()) {
@@ -2301,12 +2299,11 @@ function views_ui_config_item_form($form, &$form_state) {
   $item = $view->get_item($display_id, $type, $id);
 
   if ($item) {
-    $handler = views_get_handler($item['table'], $item['field'], $type);
+    $handler = $view->display_handler->get_handler($type, $id);
     if (empty($handler)) {
       $form['markup'] = array('#value' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
     }
     else {
-      $handler->init($view, $item);
       $types = views_object_types();
 
       if ($view->display_handler->defaultable_sections($types[$type]['plural'])) {
@@ -2334,7 +2331,7 @@ function views_ui_config_item_form($form, &$form_state) {
         // If this relationship is valid for this type, add it to the list.
         $data = views_fetch_data($relationship['table']);
         $base = $data[$relationship['field']]['relationship']['base'];
-        $base_fields = views_fetch_fields($base, $form_state['type']);
+        $base_fields = views_fetch_fields($base, $form_state['type'], $view->display_handler->use_group_by());
         if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
           $relationship_handler->init($view, $relationship);
           $relationship_options[$relationship['id']] = $relationship_handler->label();
@@ -2344,7 +2341,7 @@ function views_ui_config_item_form($form, &$form_state) {
       if (!empty($relationship_options)) {
         // Make sure the existing relationship is even valid. If not, force
         // it to none.
-        $base_fields = views_fetch_fields($view->base_table, $form_state['type']);
+        $base_fields = views_fetch_fields($view->base_table, $form_state['type'], $view->display_handler->use_group_by());
         if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
           $relationship_options = array_merge(array('none' => t('Do not use a relationship')), $relationship_options);
         }
@@ -2419,9 +2416,7 @@ function views_ui_config_item_form_submit($form, &$form_state) {
   // Store the item back on the view
   $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
 
-  $handler = views_get_handler($item['table'], $item['field'], $form_state['type']);
-  $handler->init($form_state['view'], $item);
-  if ($handler && $handler->needs_style_plugin()) {
+  if ($form_state['handler'] && $form_state['handler']->needs_style_plugin()) {
     views_ui_add_form_to_stack('change-style', $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE);
   }
 
@@ -2430,6 +2425,73 @@ function views_ui_config_item_form_submit($form, &$form_state) {
 }
 
 /**
+ * Form to config_item items in the views UI.
+ */
+function views_ui_config_item_group_form($type, &$form_state) {
+ $view = &$form_state['view'];
+ $display_id = $form_state['display_id'];
+ $type = $form_state['type'];
+ $id = $form_state['id'];
+ 
+ $view->init_query();
+ 
+ $form = array('options' => array('#tree' => TRUE));
+ if (!$view->set_display($display_id)) {
+   views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
+ }
+ 
+ $item = $view->get_item($display_id, $type, $id);
+ 
+ if ($item) {
+   $handler = $view->display_handler->get_handler($type, $id);
+   if (empty($handler)) {
+     $form['markup'] = array('#value' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
+   }
+   else {
+     $handler->init($view, $item);
+     $types = views_object_types();
+     
+     $form['#title'] = check_plain($view->display[$display_id]->display_title) . ': ';
+     $form['#title'] .= t('Configure group settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
+     
+     $form['#section'] = $display_id . '-' . $type . '-' . $id;
+     
+     $info = $view->query->get_aggregation_info();
+     foreach ($info as $id => $aggregate) {
+       $group_types[$id] = $aggregate['title'];
+     }
+     
+     $form['group_type'] = array(
+     '#type' => 'select',
+     '#title' => t('Group type'),
+     '#default_value' => $handler->options['group_type'],
+     '#description' => t('Grouping is enabled for this display. You must select what function to use on this field.'),
+     '#options' => $group_types,
+   );
+     $form_state['handler'] = &$handler;
+   }
+
+   views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_group_form');
+ }
+ return $form;
+ }
+
+/**
+ * Submit handler for configing group settings on a view.
+ */
+function views_ui_config_item_group_form_submit($form, &$form_state) {
+  $item = $form_state['handler']->options;
+
+  $item['group_type'] = $form_state['values']['group_type'];
+
+  // Store the item back on the view
+  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+  // Write to cache
+  views_ui_cache_set($form_state['view']);
+}
+
+/**
  * Submit handler for removing an item from a view
  */
 function views_ui_config_item_form_remove($form, &$form_state) {
@@ -2493,8 +2555,7 @@ function views_ui_config_item_extra_form($form, &$form_state) {
 
       // Get form from the handler.
       $handler->extra_options_form($form['options'], $form_state);
-        $form_state['handler'] = &$handler;
-
+      $form_state['handler'] = &$handler;
     }
 
     views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_extra_form');
@@ -2938,7 +2999,7 @@ function _views_sort_types($a, $b) {
  * @return
  *   A keyed array of in the form of 'base_table' => 'Description'.
  */
-function views_fetch_fields($base, $type) {
+function views_fetch_fields($base, $type, $grouping = FALSE) {
   static $fields = array();
   if (empty($fields)) {
     $data = views_fetch_data();
@@ -2970,68 +3031,71 @@ function views_fetch_fields($base, $type) {
         }
         foreach (array('field', 'sort', 'filter', 'argument', 'relationship') as $key) {
           if (!empty($info[$key])) {
-            if (!empty($info[$key]['skip base'])) {
-              foreach ((array) $info[$key]['skip base'] as $base_name) {
-                $skip_bases[$field][$key][$base_name] = TRUE;
-              }
-            }
-            elseif (!empty($info['skip base'])) {
-              foreach ((array) $info['skip base'] as $base_name) {
-                $skip_bases[$field][$key][$base_name] = TRUE;
-              }
-            }
-            foreach (array('title', 'group', 'help', 'base') as $string) {
-              // First, try the lowest possible level
-              if (!empty($info[$key][$string])) {
-                $strings[$field][$key][$string] = $info[$key][$string];
-              }
-              // Then try the field level
-              elseif (!empty($info[$string])) {
-                $strings[$field][$key][$string] = $info[$string];
+            if ($grouping && !empty($info[$key]['no group by'])) {
+              continue;
+              if (!empty($info[$key]['skip base'])) {
+                foreach ((array) $info[$key]['skip base'] as $base_name) {
+                  $skip_bases[$field][$key][$base_name] = TRUE;
+                }
               }
-              // Finally, try the table level
-              elseif (!empty($table_data['table'][$string])) {
-                $strings[$field][$key][$string] = $table_data['table'][$string];
+              elseif (!empty($info['skip base'])) {
+                foreach ((array) $info['skip base'] as $base_name) {
+                  $skip_bases[$field][$key][$base_name] = TRUE;
+                }
               }
-              else {
-                if ($string != 'base') {
-                  $strings[$field][$key][$string] = t("Error: missing @component", array('@component' => $string));
+              foreach (array('title', 'group', 'help', 'base') as $string) {
+                // First, try the lowest possible level
+                if (!empty($info[$key][$string])) {
+                  $strings[$field][$key][$string] = $info[$key][$string];
+                }
+                // Then try the field level
+                elseif (!empty($info[$string])) {
+                  $strings[$field][$key][$string] = $info[$string];
+                }
+                // Finally, try the table level
+                elseif (!empty($table_data['table'][$string])) {
+                  $strings[$field][$key][$string] = $table_data['table'][$string];
+                }
+                else {
+                  if ($string != 'base') {
+                    $strings[$field][$key][$string] = t("Error: missing @component", array('@component' => $string));
+                  }
                 }
               }
             }
           }
         }
-      }
-      foreach ($bases as $base_name) {
-        foreach ($strings as $field => $field_strings) {
-          foreach ($field_strings as $type_name => $type_strings) {
-            if (empty($skip_bases[$field][$type_name][$base_name])) {
-              $fields[$base_name][$type_name]["$table.$field"] = $type_strings;
+        foreach ($bases as $base_name) {
+          foreach ($strings as $field => $field_strings) {
+            foreach ($field_strings as $type_name => $type_strings) {
+              if (empty($skip_bases[$field][$type_name][$base_name])) {
+                $fields[$base_name][$type_name]["$table.$field"] = $type_strings;
+              }
             }
           }
         }
       }
+      //    vsm('Views UI data build time: ' . (microtime(TRUE) - $start) * 1000 . ' ms');
     }
-//    vsm('Views UI data build time: ' . (microtime(TRUE) - $start) * 1000 . ' ms');
-  }
 
-  // If we have an array of base tables available, go through them
-  // all and add them together. Duplicate keys will be lost and that's
-  // Just Fine.
-  if (is_array($base)) {
-    $strings = array();
-    foreach ($base as $base_table) {
-      if (isset($fields[$base_table][$type])) {
-        $strings += $fields[$base_table][$type];
+    // If we have an array of base tables available, go through them
+    // all and add them together. Duplicate keys will be lost and that's
+    // Just Fine.
+    if (is_array($base)) {
+      $strings = array();
+      foreach ($base as $base_table) {
+        if (isset($fields[$base_table][$type])) {
+          $strings += $fields[$base_table][$type];
+        }
       }
+      uasort($strings, '_views_sort_types');
+      return $strings;
     }
-    uasort($strings, '_views_sort_types');
-    return $strings;
-  }
 
-  if (isset($fields[$base][$type])) {
-    uasort($fields[$base][$type], '_views_sort_types');
-    return $fields[$base][$type];
+    if (isset($fields[$base][$type])) {
+      uasort($fields[$base][$type], '_views_sort_types');
+      return $fields[$base][$type];
+    }
   }
   return array();
 }
diff --git includes/handlers.inc includes/handlers.inc
index 7a26a05..b788696 100644
--- includes/handlers.inc
+++ includes/handlers.inc
@@ -15,11 +15,22 @@ function _views_create_handler($definition, $type = 'handler') {
   }
 
   // class_exists will automatically load the code file.
+  if (!empty($definition['override handler']) &&
+      !class_exists($definition['override handler'])) {
+    return;
+  }
+  
   if (!class_exists($definition['handler'])) {
     return;
   }
+  
+   if (!empty($definition['override handler'])) {
+     $handler = new $definition['override handler'];
+   }
+   else {
+     $handler = new $definition['handler'];
+   }
 
-  $handler = new $definition['handler'];
   $handler->set_definition($definition);
   // let the handler have something like a constructor.
   $handler->construct();
@@ -194,6 +205,14 @@ class views_handler extends views_object {
     $this->query = &$view->query;
   }
 
+  function option_definition() {
+    $options = parent::option_definition();
+
+    $options['group_type'] = array('default' => 'group');
+
+    return $options;
+  }
+
   /**
    * Return a string representing this handler's name in the UI.
    */
@@ -203,15 +222,39 @@ class views_handler extends views_object {
   }
 
   /**
-   * Provide a form for setting options.
+   * Shortcut to get a handler's raw field value.
+   *
+   * This should be overridden for handlers with formulae or other
+   * non-standard fields. Because this takes an argument, fields
+   * overriding this can just call return parent::get_field($formula)
    */
-  function options_form(&$form, &$form_state) { }
+  function get_field($field = NULL) {
+    if (!isset($field)) {
+      if (!empty($this->formula)) {
+        $field = $this->get_formula();
+      }
+      else {
+        $field = $this->table_alias . '.' . $this->real_field;
+      }
+    }
+
+    // If grouping, check to see if the aggregation method needs to modify the field.
+    if ($this->view->display_handler->use_group_by()) {
+      $info = $this->query->get_aggregation_info();
+      if (!empty($info[$this->options['group_type']]['method']) && function_exists($info[$this->options['group_type']]['method'])) {
+        return $info[$this->options['group_type']]['method']($this->options['group_type'], $field);
+      }
+    }
+
+    return $field;
+  }
 
   /**
    * Validate the options form.
    */
   function options_validate($form, &$form_state) { }
 
+  function options_form(&$form, &$form_state) { }
   /**
    * Perform any necessary changes to the form values prior to storage.
    * There is no need for this function to actually store the data.
@@ -437,7 +480,7 @@ class views_handler extends views_object {
    *
    * If we were using PHP5, this would be abstract.
    */
-  function query() { }
+  function query($group_by = FALSE) { }
 
   /**
    * Ensure the main table for this handler is in the query. This is used
@@ -1096,6 +1139,20 @@ function views_views_handlers() {
     'views_handler_sort_date',
     'views_handler_sort_menu_hierarchy',
     'views_handler_sort_random',
+  
+    // group by handlers
+    'views_handler_argument_group_by_numeric' => array(
+      'parent' => 'views_handler_argument',
+    ),
+    'views_handler_field_group_by_numeric' => array(
+      'parent' => 'views_handler_field',
+    ),
+    'views_handler_filter_group_by_numeric' => array(
+      'parent' => 'views_handler_filter_numeric',
+    ),
+    'views_handler_sort_group_by_numeric' => array(
+      'parent' => 'views_handler_sort',
+    ),
   );
 }
 
@@ -1287,7 +1344,8 @@ class views_join {
  */
 function views_views_api() {
   return array(
-    'api' => 2,
+    // in your modules do *not* use views_api_version()!!!
+    'api' => views_api_version(),
     'path' => drupal_get_path('module', 'views') . '/modules',
   );
 }
diff --git includes/view.inc includes/view.inc
index 8c0169f..79e7cf5 100644
--- includes/view.inc
+++ includes/view.inc
@@ -51,17 +51,19 @@ class view extends views_db_object {
 
   // Used to store views that were previously running if we recurse.
   var $old_view = array();
+
+  // Where the $query object will reside:
+  var $query = NULL;
+
   /**
    * Constructor
    */
-  function view() {
+  function __construct() {
     parent::init();
     // Make sure all of our sub objects are arrays.
     foreach ($this->db_objects() as $object) {
       $this->$object = array();
     }
-
-    $this->query = new stdClass();
   }
 
   /**
@@ -477,7 +479,7 @@ class view extends views_db_object {
         }
         else {
           $arg_title = $argument->get_title();
-          $argument->query();
+          $argument->query($this->display_handler->use_group_by());
         }
 
         // Add this argument's substitution
@@ -528,6 +530,14 @@ class view extends views_db_object {
    * Do some common building initialization.
    */
   function init_query() {
+    if (!empty($this->query)) {
+      $class = get_class($this->query);
+      if ($class && $class != 'stdClass') {
+        // return if query is already initialized.
+        return;
+      }
+    }
+
     // Create and initialize the query object.
     $views_data = views_fetch_data($this->base_table);
     $this->base_field = $views_data['table']['base']['field'];
@@ -633,10 +643,10 @@ class view extends views_db_object {
     }
 
     // Allow display handler to affect the query:
-    $this->display_handler->query();
+    $this->display_handler->query($this->display_handler->use_group_by());
 
     // Allow style handler to affect the query:
-    $this->style_plugin->query();
+    $this->style_plugin->query($this->display_handler->use_group_by());
 
     // Allow exposed form to affect the query:
     if (isset($exposed_form)) {
@@ -680,7 +690,7 @@ class view extends views_db_object {
           }
         }
         $handlers[$id]->set_relationship();
-        $handlers[$id]->query();
+        $handlers[$id]->query($this->display_handler->use_group_by());
       }
     }
   }
diff --git modules/comment.views.inc modules/comment.views.inc
index e73f943..6653e3f 100644
--- modules/comment.views.inc
+++ modules/comment.views.inc
@@ -459,6 +459,7 @@ function comment_views_data_alter(&$data) {
     'help' => t('The number of new comments on the node.'),
     'field' => array(
       'handler' => 'views_handler_field_node_new_comments',
+      'no group by' => TRUE,
     ),
   );
 
diff --git modules/node.views.inc modules/node.views.inc
index 7f49d70..5091f28 100644
--- modules/node.views.inc
+++ modules/node.views.inc
@@ -663,6 +663,7 @@ function template_preprocess_views_view_row_node(&$vars) {
   // Make sure the variables are defined.
   $vars['node'] = '';
   $vars['comments'] = '';
+  dsm($vars);
 
   $nid = $vars['row']->{$vars['field_alias']};
   if (!is_numeric($nid)) {
diff --git modules/node/views_handler_field_node.inc modules/node/views_handler_field_node.inc
index 523b8de..0ce93de 100644
--- modules/node/views_handler_field_node.inc
+++ modules/node/views_handler_field_node.inc
@@ -9,14 +9,14 @@
  * Field handler to provide simple renderer that allows linking to a node.
  */
 class views_handler_field_node extends views_handler_field {
-  /**
-   * Constructor to provide additional field to add.
-   */
-  function construct() {
-    parent::construct();
-    $this->additional_fields['nid'] = 'nid';
-    if (module_exists('translation')) {
-      $this->additional_fields['language'] = array('table' => 'node', 'field' => 'language');
+
+  function init(&$view, $options) {
+    parent::init($view, $options);
+    if (!empty($this->options['link_to_node'])) {
+      $this->additional_fields['nid'] = 'nid';
+      if (module_exists('translation')) {
+        $this->additional_fields['language'] = array('table' => 'node', 'field' => 'language');
+      }
     }
   }
 
diff --git modules/profile.views.inc modules/profile.views.inc
index 4cbfe5a..9538653 100644
--- modules/profile.views.inc
+++ modules/profile.views.inc
@@ -195,6 +195,7 @@ function profile_views_fetch_field($field) {
         'help' => t('Profile freeform list %field-name.', array('%field-name' => $field->title)),
         'field' => array(
           'handler' => 'views_handler_field_profile_list',
+          'no group by' => TRUE,
         ),
         'filter' => array(
           'handler' => 'views_handler_filter_string',
diff --git modules/search/views_handler_argument_search.inc modules/search/views_handler_argument_search.inc
index 5b16f77..3bfea98 100644
--- modules/search/views_handler_argument_search.inc
+++ modules/search/views_handler_argument_search.inc
@@ -44,6 +44,7 @@ class views_handler_argument_search extends views_handler_argument {
       }
 
       $this->query->add_groupby("$search_index.sid");
-      $this->query->add_having($this->options['group'], 'COUNT(*) >= %d', $this->search_query[4]);    }
+      $this->query->add_having($this->options['group'], 'COUNT(*)', $this->search_query[4], '>=');
+    }
   }
 }
diff --git modules/taxonomy.views.inc modules/taxonomy.views.inc
index 71f15be..4ddcf32 100644
--- modules/taxonomy.views.inc
+++ modules/taxonomy.views.inc
@@ -218,6 +218,7 @@ function taxonomy_views_data() {
       'help' => t('Display all taxonomy terms associated with a node from specified vocabularies.'),
       'handler' => 'views_handler_field_term_node_tid',
       'skip base' => 'term_data',
+      'no group by' => TRUE,
     ),
     'argument' => array(
       'handler' => 'views_handler_argument_term_node_tid',
diff --git modules/upload.views.inc modules/upload.views.inc
index bcb8570..d71d433 100644
--- modules/upload.views.inc
+++ modules/upload.views.inc
@@ -116,6 +116,7 @@ function upload_views_data_alter(&$data) {
     'real field' => 'vid',
     'field' => array(
       'handler' => 'views_handler_field_upload_fid',
+      'no group by' => TRUE,
     ),
     'filter' => array(
       'handler' => 'views_handler_filter_upload_fid',
diff --git modules/user.views.inc modules/user.views.inc
index cc94ddd..1c1eb1f 100644
--- modules/user.views.inc
+++ modules/user.views.inc
@@ -280,6 +280,7 @@ function user_views_data() {
     'help' => t('Roles that a user belongs to.'),
     'field' => array(
       'handler' => 'views_handler_field_user_roles',
+      'no group by' => TRUE,
     ),
     'filter' => array(
       'handler' => 'views_handler_filter_user_roles',
diff --git plugins/views_plugin_display.inc plugins/views_plugin_display.inc
index 439c3cb..5a3aa43 100644
--- plugins/views_plugin_display.inc
+++ plugins/views_plugin_display.inc
@@ -137,6 +137,13 @@ class views_plugin_display extends views_plugin {
   }
 
   /**
+   * Does the display have a more link enabled?
+   */
+  function use_group_by() {
+    return $this->get_option('group_by');
+  }
+
+  /**
    * Should the enabled display more link be shown when no more items?
    */
   function use_more_always() {
@@ -265,6 +272,7 @@ class views_plugin_display extends views_plugin {
           'exposed_form' => TRUE,
 
           'link_display' => TRUE,
+          'group_by' => TRUE,
 
           'style_plugin' => TRUE,
           'style_options' => TRUE,
@@ -370,6 +378,9 @@ class views_plugin_display extends views_plugin {
       'distinct' => array(
         'default' => FALSE,
       ),
+      'group_by' => array(
+        'default' => FALSE,
+      ),
 
       'style_plugin' => array(
         'default' => 'default',
@@ -590,7 +601,21 @@ class views_plugin_display extends views_plugin {
       $types = views_object_types();
       $plural = $types[$type]['plural'];
       foreach ($this->get_option($plural) as $id => $info) {
-        $handler = views_get_handler($info['table'], $info['field'], $type);
+        // If aggregation is on, the group type might override the actual
+        // handler that is in use. This piece of code checks that and,
+        // if necessary, sets the override handler.
+        $override = NULL;
+        if ($this->use_group_by() && !empty($info['group_type'])) {
+          if (empty($this->view->query)) {
+            $this->view->init_query();
+          }
+          $aggregate = $this->view->query->get_aggregation_info();
+          if (!empty($aggregate[$info['group_type']]['handler'][$type])) {
+            $override = $aggregate[$info['group_type']]['handler'][$type];
+          }
+        }
+
+        $handler = views_get_handler($info['table'], $info['field'], $type, $override);
         if ($handler) {
           $handler->init($this->view, $info);
           $this->handlers[$type][$id] = &$handler;
@@ -772,6 +797,16 @@ class views_plugin_display extends views_plugin {
       'desc' => t('Display only distinct items, without duplicates.'),
     );
 
+    $this->view->init_query();
+    if ($this->view->query->get_aggregation_info()) {
+      $options['group_by'] = array(
+        'category' => 'advanced',
+        'title' => t('Use grouping'),
+        'value' => $this->get_option('group_by') ? t('Yes') : t('No'),
+        'desc' => t('Allow grouping and aggregation (calculation) of fields.'),
+      );
+    }
+
     $access_plugin = $this->get_access_plugin();
     if (!$access_plugin) {
       // default to the no access control plugin.
@@ -1013,6 +1048,15 @@ class views_plugin_display extends views_plugin {
           '#default_value' => $this->get_option('distinct'),
         );
         break;
+      case 'group_by':
+        $form['#title'] .= t('Allow grouping and aggregation (calculation) of fields.');
+        $form['group_by'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('Group by'),
+          '#description' => t('If enabled, some fields may become unavailable. All fields that are selected for grouping will be collapsed to one record per distinct value. Other fields which are selected for aggregation will have the function run on them. For example, you can group nodes on title and count the number of nids in order to get a list of duplicate titles.'),
+          '#default_value' => $this->get_option('distinct'),
+        );
+        break;
       case 'access':
         $form['#title'] .= t('Access restrictions');
         $form['access'] = array(
@@ -1639,6 +1683,9 @@ class views_plugin_display extends views_plugin {
       case 'distinct':
         $this->set_option($section, $form_state['values'][$section]);
         break;
+      case 'group_by':
+        $this->set_option($section, $form_state['values'][$section]);
+        break;
       case 'row_plugin':
         // This if prevents resetting options to default if they don't change
         // the plugin.
diff --git plugins/views_plugin_query.inc plugins/views_plugin_query.inc
index a13c828..caa5559 100644
--- plugins/views_plugin_query.inc
+++ plugins/views_plugin_query.inc
@@ -50,4 +50,11 @@ class views_plugin_query extends views_plugin {
    * discern where particular queries might be coming from.
    */
   function add_signature(&$view) { }
+
+  /**
+   * Get aggregation info for group by queries.
+   *
+   * If NULL, aggregation is not allowed.
+   */
+  function get_aggregation_info() { }
 }
diff --git plugins/views_plugin_query_default.inc plugins/views_plugin_query_default.inc
index 2f214fe..e81ac4f 100644
--- plugins/views_plugin_query_default.inc
+++ plugins/views_plugin_query_default.inc
@@ -68,6 +68,13 @@ class views_plugin_query_default extends views_plugin_query {
 
   var $placeholder_counter = 0;
 
+  var $has_aggregate = FALSE;
+
+  /**
+   * Should this query be optimized for counts, for example no sorts.
+   */
+  var $get_count_optimized = NULL;
+
   /**
    * Constructor; Create the basic query object and fill with default values.
    */
@@ -95,6 +102,8 @@ class views_plugin_query_default extends views_plugin_query {
       'alias' => $base_table,
     );
 
+/**
+ * -- we no longer want the base field to appear automatigically.
     if ($base_field) {
       $this->fields[$base_field] = array(
         'table' => $base_table,
@@ -102,6 +111,7 @@ class views_plugin_query_default extends views_plugin_query {
         'alias' => $base_field,
       );
     }
+ */
 
     $this->count_field = array(
       'table' => $base_table,
@@ -619,11 +629,14 @@ class views_plugin_query_default extends views_plugin_query {
    *   The alias to create. If not specified, the alias will be $table_$field
    *   unless $table is NULL. When adding formulae, it is recommended that an
    *   alias be used.
+   * @param $params
+   *   An array of parameters additional to the field that will control items
+   *   such as aggregation functions and DISTINCT.
    *
    * @return $name
    *   The name that this field can be referred to as. Usually this is the alias.
    */
-  function add_field($table, $field, $alias = '', $params = NULL) {
+  function add_field($table, $field, $alias = '', $params = array()) {
     // We check for this specifically because it gets a special alias.
     if ($table == $this->base_table && $field == $this->base_field && empty($alias)) {
       $alias = $this->base_field;
@@ -637,22 +650,30 @@ class views_plugin_query_default extends views_plugin_query {
       $alias = $table . '_' . $field;
     }
 
-    $name = $alias ? $alias : $field;
+    // Make sure an alias is assigned
+    $alias = $alias ? $alias : $field;
 
-    // @todo FIXME -- $alias, then $name is inconsistent
-    if (empty($this->fields[$alias])) {
-      $this->fields[$name] = array(
-        'field' => $field,
-        'table' => $table,
-        'alias' => $alias,
-      );
+    // Create a field info array.
+    $field_info = array(
+      'field' => $field,
+      'table' => $table,
+      'alias' => $alias,
+    ) + $params;
+
+    // Test to see if the field is actually the same or not. Due to
+    // differing parameters changing the aggregation function, we need
+    // to do some automatic alias collision detection:
+    $base = $alias;
+    $counter = 0;
+    while (!empty($this->fields[$alias]) && $this->fields[$alias] != $field_info) {
+      $field_info['alias'] = $alias = $base . '_' . ++$counter;
     }
 
-    foreach ((array)$params as $key => $value) {
-      $this->fields[$name][$key] = $value;
+    if (empty($this->fields[$alias])) {
+      $this->fields[$alias] = $field_info;
     }
 
-    return $name;
+    return $alias;
   }
 
   /**
@@ -788,8 +809,10 @@ class views_plugin_query_default extends views_plugin_query {
    *   must also be in the SELECT portion. If an $alias isn't specified
    *   one will be generated for from the $field; however, if the
    *   $field is a formula, this alias will likely fail.
+   * @param $params
+   *   Any params that should be passed through to the add_field.
    */
-  function add_orderby($table, $field, $order, $alias = '') {
+  function add_orderby($table, $field, $order, $alias = '', $params = array()) {
     if ($table) {
       $this->ensure_table($table);
     }
@@ -804,7 +827,7 @@ class views_plugin_query_default extends views_plugin_query {
     }
 
     if ($field) {
-      $this->add_field($table, $field, $as);
+      $this->add_field($table, $field, $as, $params);
     }
 
     $this->orderby[] = array(
@@ -812,12 +835,16 @@ class views_plugin_query_default extends views_plugin_query {
       'direction' => strtoupper($order)
     );
 
-    // If grouping, all items in the order by must also be in the
-    // group by clause. Check $table to ensure that this is not a
-    // formula.
-    if ($this->groupby && $table) {
-      $this->add_groupby($as);
-    }
+	/**
+ 	 * -- removing, this should be taken care of by field adding now.
+     * -- leaving commented because I am unsure.
+      // If grouping, all items in the order by must also be in the
+      // group by clause. Check $table to ensure that this is not a
+      // formula.
+      if ($this->groupby && $table) {
+        $this->add_groupby($as);
+      }
+    */
   }
 
   /**
@@ -851,6 +878,72 @@ class views_plugin_query_default extends views_plugin_query {
   }
 
   /**
+   * Build fields array.
+   */
+  function compile_fields($fields_array, $query) {
+    $non_aggregates = array();
+    foreach ($fields_array as $field) {
+      $string = '';
+      if (!empty($field['table'])) {
+        $string .= $field['table'] . '.';
+      }
+      $string .= $field['field'];
+      $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
+
+      if (!empty($field['distinct'])) {
+        throw new Exception("d7cx: Column-level distinct is not supported yet.");
+      }
+
+      if (!empty($field['count'])) {
+        // Retained for compatibility
+        $field['function'] = 'COUNT';
+        // It seems there's no way to abstract the table+column reference
+        // without adding a field, aliasing, and then using the alias.
+        $table = !empty($field['table']) ? $field['table'] : $this->base_table;
+        $alias = $table . '_' . $field['field'] . '_viewscount';
+        $query->addField($table, $field['field'], $alias);
+        $query->addExpression('COUNT(' . $alias . ')', $fieldname);
+      }
+
+      if (!empty($field['function'])) {
+        $info = $this->get_aggregation_info();
+        if (!empty($info[$field['function']]['method']) && function_exists($info[$field['function']]['method'])) {
+          $string = $info[$field['function']]['method']($field['function'], $string);
+          $query->addExpression($string, $fieldname);
+        }
+
+        $this->has_aggregate = TRUE;
+      }
+
+      elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
+        // d7cx: This code was there, apparently needed for PostgreSQL
+        // $string = db_driver() == 'pgsql' ? "FIRST($string)" : $string;
+        $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
+      }
+      elseif (empty($field['aggregate'])) {
+        $non_aggregates[] = $fieldname;
+        $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
+      }
+
+      // @TODO Remove this old code.
+      if (!empty($field['distinct']) && empty($field['function'])) {
+        $distinct[] = $string;
+      }
+      else {
+        $fields[] = $string;
+      }
+
+      if ($this->get_count_optimized) {
+        // We only want the first field in this case.
+        break;
+      }
+    }
+    return array(
+      $non_aggregates,
+    );
+  }
+
+  /**
    * Generate a query and a countquery from all of the information supplied
    * to the object.
    *
@@ -860,10 +953,8 @@ class views_plugin_query_default extends views_plugin_query {
   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;
-        $this->add_groupby($this->base_field);
-      }
+      $base_field_alias = $this->add_field($this->base_table, $this->base_field, NULL, array('distinct' => TRUE));
+      $this->add_groupby($base_field_alias);
     }
 
     /**
@@ -873,23 +964,24 @@ class views_plugin_query_default extends views_plugin_query {
     $fields_array = $this->fields;
     if ($get_count && !$this->groupby) {
       foreach ($fields_array as $field) {
-        if (!empty($field['distinct'])) {
-          $get_count_optimized = FALSE;
+        if (!empty($field['distinct']) || !empty($field['function'])) {
+          $this->get_count_optimized = FALSE;
           break;
         }
       }
     }
     else {
-      $get_count_optimized = FALSE;
+      $this->get_count_optimized = FALSE;
     }
-    if (!isset($get_count_optimized)) {
-      $get_count_optimized = TRUE;
+    if (!isset($this->get_count_optimized)) {
+      $this->get_count_optimized = TRUE;
     }
 
     // Go ahead and build the query.
     $query = db_select($this->base_table, $this->base_table);
 
-    $joins = $fields = $where = $having = $orderby = $groupby = '';
+    $joins = $where = $having = $orderby = $groupby = '';
+    $fields = $distinct = array();
 
     // Add all the tables to the query via joins. We assume all LEFT joins.
     foreach ($this->table_queue as $table) {
@@ -898,52 +990,26 @@ class views_plugin_query_default extends views_plugin_query {
       }
     }
 
-    $has_aggregate = FALSE;
+    $this->has_aggregate = FALSE;
     $non_aggregates = array();
 
-    foreach ($fields_array as $field) {
-      $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
-
-      if (!empty($field['distinct'])) {
-        throw new Exception("d7cx: Column-level distinct is not supported yet.");
-      }
-
-      if (!empty($field['count'])) {
-        // It seems there's no way to abstract the table+column reference
-        // without adding a field, aliasing, and then using the alias.
-        $table = !empty($field['table']) ? $field['table'] : $this->base_table;
-        $alias = $table . '_' . $field . '_viewscount';
-        $query->addField($table, $field['field'], $alias);
-        $query->addExpression('COUNT(' . $alias . ')', $fieldname);
-      }
-      elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
-        // d7cx: This code was there, apparently needed for PostgreSQL
-        // $string = db_driver() == 'pgsql' ? "FIRST($string)" : $string;
-        $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
-      }
-      else {
-        $non_aggregates[] = $fieldname;
-        $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
-      }
+    list($non_aggregates) = $this->compile_fields($fields_array, $query);
 
-      if ($get_count_optimized) {
-        // We only want the first field in this case.
-        break;
-      }
+    if (count($this->having)) {
+      $this->has_aggregate = TRUE;
     }
-
-    if ($has_aggregate || $this->groupby) {
+    if ($this->has_aggregate || $this->groupby) {
       $groupby = array_unique(array_merge($this->groupby, $non_aggregates));
       foreach ($groupby as $field) {
         $query->groupBy($field);
       }
       if (!empty($this->having)) {
-        $query->having($this->build_condition('having'));
+        $query->havingCondition($this->build_condition('having'));
       }
     }
 
-    if (!$get_count_optimized) {
-      // we only add the groupby if we're not counting.
+    if (!$this->get_count_optimized) {
+      // we only add the orderby if we're not counting.
       if ($this->orderby) {
         foreach ($this->orderby as $order) {
           $query->orderBy($order['field'], $order['direction']);
@@ -1005,7 +1071,8 @@ class views_plugin_query_default extends views_plugin_query {
     $items = array();
     if ($query) {
       $additional_arguments = module_invoke_all('views_query_substitutions', $view);
-      $count_query = $count_query->countQuery();
+      // We have already build a working count query. Views optimeses it automatically.
+      //$count_query = $count_query->countQuery();
 
       // Add additional arguments as a fake condition.
       // XXX: this doesn't work... because PDO mandates that all bound arguments
@@ -1089,4 +1156,69 @@ class views_plugin_query_default extends views_plugin_query {
     $view->query->add_field(NULL, "'" . $view->name . ':' . $view->current_display . "'", 'view_name');
   }
 
+  function get_aggregation_info() {
+    // @todo -- need a way to get database specific and customized aggregation
+    // functions into here.
+    return array(
+      'group' => array(
+        'title' => t('Group results together'),
+        'is aggregate' => FALSE,
+      ),
+      'count' => array(
+        'title' => t('Count'),
+        'method' => 'views_query_default_aggregation_method_simple',
+        'handler' => array(
+          'argument' => 'views_handler_argument_group_by_numeric',
+          'field' => 'views_handler_field_group_by_numeric',
+          'filter' => 'views_handler_filter_group_by_numeric',
+          'sort' => 'views_handler_sort_group_by_numeric',
+        ),
+      ),
+      'sum' => array(
+        'title' => t('Sum'),
+        'method' => 'views_query_default_aggregation_method_simple',
+        'handler' => array(
+          'argument' => 'views_handler_argument_group_by_numeric',
+          'field' => 'views_handler_field_group_by_numeric',
+          'filter' => 'views_handler_filter_group_by_numeric',
+          'sort' => 'views_handler_sort_group_by_numeric',
+        ),
+      ),
+      'avg' => array(
+        'title' => t('Average'),
+        'method' => 'views_query_default_aggregation_method_simple',
+        'handler' => array(
+          'argument' => 'views_handler_argument_group_by_numeric',
+          'field' => 'views_handler_field_group_by_numeric',
+          'filter' => 'views_handler_filter_group_by_numeric',
+          'sort' => 'views_handler_sort_group_by_numeric',
+        ),
+      ),
+      'min' => array(
+        'title' => t('Minimum'),
+        'method' => 'views_query_default_aggregation_method_simple',
+        'handler' => array(
+          'argument' => 'views_handler_argument_group_by_numeric',
+          'field' => 'views_handler_field_group_by_numeric',
+          'filter' => 'views_handler_filter_group_by_numeric',
+          'sort' => 'views_handler_sort_group_by_numeric',
+        ),
+      ),
+      'max' => array(
+        'title' => t('Maximum'),
+        'method' => 'views_query_default_aggregation_method_simple',
+        'handler' => array(
+          'argument' => 'views_handler_argument_group_by_numeric',
+          'field' => 'views_handler_field_group_by_numeric',
+          'filter' => 'views_handler_filter_group_by_numeric',
+          'sort' => 'views_handler_sort_group_by_numeric',
+        ),
+      ),
+    );
+  }
 }
+
+function views_query_default_aggregation_method_simple($group_type, $field) {
+  return strtoupper($group_type) . '(' . $field . ')';
+}
+
diff --git tests/views_groupby.test tests/views_groupby.test
new file mode 100644
index 0000000..b19cf34
--- /dev/null
+++ tests/views_groupby.test
@@ -0,0 +1,308 @@
+<?php
+// $Id: views_groupby.test,v 1.1.2.2 2010-01-04 23:55:02 merlinofchaos Exp $
+
+module_load_include('inc', 'views', 'tests/views_query');
+
+class ViewsQueryGroupByTest extends ViewsSqlTest {
+  public static function getInfo() {
+    return array(
+      'name' => 'Tests groupby feature of views3',
+      'description' => 'tests aggregate functionality of views3, for example count',
+      'group' => 'Views',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp('views', 'views_ui', 'views_test');
+    module_enable(array('views_ui', 'views_test'));
+  }
+
+  /**
+   * Test aggregatate count feature.
+   */
+  public function testAggregateCount() {
+    // Create 2 nodes of type1 and 3 nodes of type2
+    $type1 = $this->drupalCreateContentType();
+    $type2 = $this->drupalCreateContentType();
+
+    $node_1 = array(
+      'type' => $type1->type,
+    );
+    $this->drupalCreateNode($node_1);
+    $this->drupalCreateNode($node_1);
+    $this->drupalCreateNode($node_1);
+    $this->drupalCreateNode($node_1);
+
+    $node_2 = array(
+      'type' => $type2->type,
+    );
+    $this->drupalCreateNode($node_2);
+    $this->drupalCreateNode($node_2);
+    $this->drupalCreateNode($node_2);
+
+    $view = $this->viewsAggregateCountView();
+    $view->set_display('default');
+    $output = $view->execute_display();
+
+    // FIXME
+    $this->assertEqual(count($view->result), 2, 'Make sure the count of items is right.');
+
+    debug($view->result);
+
+    $types = array();
+    foreach ($view->result as $item) {
+      $types[$item->node_type] = $item->node_title;
+    }
+
+    // FIXME
+    $this->assertEqual($types[$type1->type], 4);
+    $this->assertEqual($types[$type2->type], 3);
+  }
+
+  //public function testAggregateSum() {
+  //}
+
+  public function viewsAggregateCountView() {
+    $view = new view;
+    $view->name = 'aggregate_count';
+    $view->description = '';
+    $view->tag = '';
+    $view->view_php = '';
+    $view->base_table = 'node';
+    $view->is_cacheable = FALSE;
+    $view->api_version = 2;
+    $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+    /* Display: Defaults */
+    $handler = $view->new_display('default', 'Defaults', 'default');
+    $handler->display->display_options['group_by'] = TRUE;
+    $handler->display->display_options['access']['type'] = 'none';
+    $handler->display->display_options['cache']['type'] = 'none';
+    $handler->display->display_options['exposed_form']['type'] = 'basic';
+    $handler->display->display_options['pager']['type'] = 'some';
+    $handler->display->display_options['style_plugin'] = 'default';
+    $handler->display->display_options['row_plugin'] = 'fields';
+    /* Field: Node: Nid */
+    $handler->display->display_options['fields']['nid']['id'] = 'nid';
+    $handler->display->display_options['fields']['nid']['table'] = 'node';
+    $handler->display->display_options['fields']['nid']['field'] = 'nid';
+    $handler->display->display_options['fields']['nid']['alter']['alter_text'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['make_link'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['trim'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['word_boundary'] = 1;
+    $handler->display->display_options['fields']['nid']['alter']['ellipsis'] = 1;
+    $handler->display->display_options['fields']['nid']['alter']['strip_tags'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['html'] = 0;
+    $handler->display->display_options['fields']['nid']['hide_empty'] = 0;
+    $handler->display->display_options['fields']['nid']['empty_zero'] = 0;
+    $handler->display->display_options['fields']['nid']['link_to_node'] = 0;
+    /* Argument: Node: Type */
+    $handler->display->display_options['arguments']['type']['id'] = 'type';
+    $handler->display->display_options['arguments']['type']['table'] = 'node';
+    $handler->display->display_options['arguments']['type']['field'] = 'type';
+    $handler->display->display_options['arguments']['type']['default_action'] = 'summary asc';
+    $handler->display->display_options['arguments']['type']['style_plugin'] = 'default_summary';
+    $handler->display->display_options['arguments']['type']['default_argument_type'] = 'fixed';
+
+    return $view;
+  }
+
+  /**
+   * @param $group_by
+   *   Which group_by function should be used, for example sum or count.
+   */
+  function GroupByTestHelper($group_by, $values) {
+    // Create 2 nodes of type1 and 3 nodes of type2
+    $type1 = $this->drupalCreateContentType();
+    $type2 = $this->drupalCreateContentType();
+
+    $node_1 = array(
+      'type' => $type1->type,
+    );
+    // Nids from 1 to 4.
+    $this->drupalCreateNode($node_1);
+    $this->drupalCreateNode($node_1);
+    $this->drupalCreateNode($node_1);
+    $this->drupalCreateNode($node_1);
+    $node_2 = array(
+      'type' => $type2->type,
+    );
+    // Nids from 5 to 7.
+    $this->drupalCreateNode($node_2);
+    $this->drupalCreateNode($node_2);
+    $this->drupalCreateNode($node_2);
+
+    $view = $this->viewsGroupByViewHelper($group_by);
+    $view->set_display('default');
+    $output = $view->execute_display();
+
+    $this->assertEqual(count($view->result), 2, 'Make sure the count of items is right.');
+    // Group by nodetype to identify the right count.
+    foreach ($view->result as $item) {
+      $results[$item->node_type] = $item->nid;
+    }
+    $this->assertEqual($results[$type1->type], $values[0]);
+    $this->assertEqual($results[$type2->type], $values[1]);
+  }
+
+  function viewsGroupByViewHelper($group_by) {
+    $view = new view;
+    $view->name = 'group_by_count';
+    $view->description = '';
+    $view->tag = '';
+    $view->view_php = '';
+    $view->base_table = 'node';
+    $view->is_cacheable = FALSE;
+    $view->api_version = 2;
+    $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+    /* Display: Defaults */
+    $handler = $view->new_display('default', 'Defaults', 'default');
+    $handler->display->display_options['group_by'] = TRUE;
+    $handler->display->display_options['access']['type'] = 'none';
+    $handler->display->display_options['cache']['type'] = 'none';
+    $handler->display->display_options['exposed_form']['type'] = 'basic';
+    $handler->display->display_options['pager']['type'] = 'some';
+    $handler->display->display_options['style_plugin'] = 'default';
+    $handler->display->display_options['row_plugin'] = 'fields';
+    /* Field: Node: Nid */
+    $handler->display->display_options['fields']['nid']['id'] = 'nid';
+    $handler->display->display_options['fields']['nid']['table'] = 'node';
+    $handler->display->display_options['fields']['nid']['field'] = 'nid';
+    $handler->display->display_options['fields']['nid']['group_type'] = $group_by;
+    $handler->display->display_options['fields']['nid']['alter']['alter_text'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['make_link'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['trim'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['word_boundary'] = 1;
+    $handler->display->display_options['fields']['nid']['alter']['ellipsis'] = 1;
+    $handler->display->display_options['fields']['nid']['alter']['strip_tags'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['html'] = 0;
+    $handler->display->display_options['fields']['nid']['hide_empty'] = 0;
+    $handler->display->display_options['fields']['nid']['empty_zero'] = 0;
+    $handler->display->display_options['fields']['nid']['link_to_node'] = 0;
+    /* Field: Node: Type */
+    $handler->display->display_options['fields']['type']['id'] = 'type';
+    $handler->display->display_options['fields']['type']['table'] = 'node';
+    $handler->display->display_options['fields']['type']['field'] = 'type';
+    $handler->display->display_options['fields']['type']['alter']['alter_text'] = 0;
+    $handler->display->display_options['fields']['type']['alter']['make_link'] = 0;
+    $handler->display->display_options['fields']['type']['alter']['trim'] = 0;
+    $handler->display->display_options['fields']['type']['alter']['word_boundary'] = 1;
+    $handler->display->display_options['fields']['type']['alter']['ellipsis'] = 1;
+    $handler->display->display_options['fields']['type']['alter']['strip_tags'] = 0;
+    $handler->display->display_options['fields']['type']['alter']['html'] = 0;
+    $handler->display->display_options['fields']['type']['hide_empty'] = 0;
+    $handler->display->display_options['fields']['type']['empty_zero'] = 0;
+    $handler->display->display_options['fields']['type']['link_to_node'] = 0;
+
+    return $view;
+  }
+
+  public function testGroupByCount() {
+    $this->GroupByTestHelper('count', array(4, 3));
+  }
+
+  function testGroupBySum() {
+    $this->GroupByTestHelper('sum', array(10, 18));
+  }
+
+
+  function testGroupByAverage() {
+    $this->GroupByTestHelper('avg', array(2.5, 6));
+  }
+
+  function testGroupByMin() {
+    $this->GroupByTestHelper('min', array(1, 5));
+  }
+
+  function testGroupByMax() {
+    $this->GroupByTestHelper('max', array(4, 7));
+  }
+
+  function testGroupBySave() {
+    $admin_user = $this->drupalCreateUser(array('administer views', 'administer site configuration'));
+    $this->drupalLogin($admin_user);
+    views_invalidate_cache();
+    menu_rebuild();
+
+    $this->drupalGet('admin/structure/views');
+    $this->drupalGet('admin/structure/views/edit/test_views_groupby_save');
+
+    $edit = array(
+      'group_by' => TRUE,
+    );
+    $this->drupalPost('admin/structure/views/nojs/display/test_views_groupby_save/default/group_by', $edit, t('Update'));
+
+    $this->drupalGet('admin/structure/views/edit/test_views_groupby_save');
+    $this->drupalPost('admin/structure/views/edit/test_views_groupby_save', array(), t('Save'));
+
+    $this->drupalGet('admin/structure/views/nojs/display/test_views_groupby_save/default/group_by');
+  }
+
+  public function testGroupByCountOnlyFilters() {
+    // Check if GROUP BY and HAVING are included when a view
+    // Doesn't display SUM, COUNT, MAX... functions in SELECT statment
+
+    $type1 = $this->drupalCreateContentType();
+
+    $node_1 = array(
+      'type' => $type1->type,
+    );
+    for ($x = 0; $x < 10; $x++) {
+      $this->drupalCreateNode($node_1);
+    }
+
+    $view = $this->viewsGroupByCountViewOnlyFilters();
+    $view->set_display('default');
+    $output = $view->execute_display();
+
+    $this->assertTrue(strpos($view->build_info['query'], 'GROUP BY'), t('Make sure that GROUP BY is in the query'));
+    $this->assertTrue(strpos($view->build_info['query'], 'HAVING'), t('Make sure that HAVING is in the query'));
+  }
+
+  function viewsGroupByCountViewOnlyFilters() {
+    $view = new view;
+    $view->name = 'group_by_in_filters';
+    $view->description = '';
+    $view->tag = '';
+    $view->view_php = '';
+    $view->base_table = 'node';
+    $view->is_cacheable = FALSE;
+    $view->api_version = 2;
+    $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+    /* Display: Defaults */
+    $handler = $view->new_display('default', 'Defaults', 'default');
+    $handler->display->display_options['group_by'] = TRUE;
+    $handler->display->display_options['access']['type'] = 'none';
+    $handler->display->display_options['cache']['type'] = 'none';
+    $handler->display->display_options['exposed_form']['type'] = 'basic';
+    $handler->display->display_options['pager']['type'] = 'some';
+    $handler->display->display_options['style_plugin'] = 'default';
+    $handler->display->display_options['row_plugin'] = 'fields';
+    /* Field: Nodo: Tipo */
+    $handler->display->display_options['fields']['type']['id'] = 'type';
+    $handler->display->display_options['fields']['type']['table'] = 'node';
+    $handler->display->display_options['fields']['type']['field'] = 'type';
+    $handler->display->display_options['fields']['type']['alter']['alter_text'] = 0;
+    $handler->display->display_options['fields']['type']['alter']['make_link'] = 0;
+    $handler->display->display_options['fields']['type']['alter']['trim'] = 0;
+    $handler->display->display_options['fields']['type']['alter']['word_boundary'] = 1;
+    $handler->display->display_options['fields']['type']['alter']['ellipsis'] = 1;
+    $handler->display->display_options['fields']['type']['alter']['strip_tags'] = 0;
+    $handler->display->display_options['fields']['type']['alter']['html'] = 0;
+    $handler->display->display_options['fields']['type']['hide_empty'] = 0;
+    $handler->display->display_options['fields']['type']['empty_zero'] = 0;
+    $handler->display->display_options['fields']['type']['link_to_node'] = 0;
+    /* Filtrar: Nodo: Nid */
+    $handler->display->display_options['filters']['nid']['id'] = 'nid';
+    $handler->display->display_options['filters']['nid']['table'] = 'node';
+    $handler->display->display_options['filters']['nid']['field'] = 'nid';
+    $handler->display->display_options['filters']['nid']['group_type'] = 'count';
+    $handler->display->display_options['filters']['nid']['operator'] = '>';
+    $handler->display->display_options['filters']['nid']['value']['value'] = '3';
+
+    return $view;
+  }
+}
+
diff --git tests/views_test.info tests/views_test.info
index 65b0f51..c182fc1 100644
--- tests/views_test.info
+++ tests/views_test.info
@@ -7,3 +7,4 @@ dependencies[] = views
 hidden = TRUE
 files[] = views_test.module
 files[] = views_test.install
+files[] = views_test.views_default.inc
diff --git tests/views_test.module tests/views_test.module
index f6c4bc9..db920e5 100644
--- tests/views_test.module
+++ tests/views_test.module
@@ -2,6 +2,15 @@
 // $Id: views_test.module,v 1.1.2.1 2009-11-02 22:01:27 merlinofchaos Exp $
 
 /**
+ * Implements hook_views_api().
+ */
+function views_test_views_api() {
+  return array(
+    'api' => 2.0,
+  );
+}
+
+/**
  * Implements hook_views_data()
  */
 function views_test_views_data() {
diff --git tests/views_test.views_default.inc tests/views_test.views_default.inc
new file mode 100644
index 0000000..52b6e1e
--- /dev/null
+++ tests/views_test.views_default.inc
@@ -0,0 +1,34 @@
+<?php
+// $Id$
+/**
+ * @file
+ *   Test views
+ */
+
+/**
+ * Implements hook_views_default_views().
+ */
+function views_test_views_default_views() {
+  $view = new view;
+  $view->name = 'test_views_groupby_save';
+  $view->description = '';
+  $view->tag = '';
+  $view->view_php = '';
+  $view->base_table = 'node';
+  $view->is_cacheable = FALSE;
+  $view->api_version = 2;
+  $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+  /* Display: Defaults */
+  $handler = $view->new_display('default', 'Defaults', 'default');
+  $handler->display->display_options['access']['type'] = 'none';
+  $handler->display->display_options['cache']['type'] = 'none';
+  $handler->display->display_options['exposed_form']['type'] = 'basic';
+  $handler->display->display_options['pager']['type'] = 'none';
+  $handler->display->display_options['style_plugin'] = 'default';
+  $handler->display->display_options['row_plugin'] = 'fields';
+
+  $views[$view->name] = $view;
+
+  return $views;
+}
diff --git views.info views.info
index 77fecb9..2845e6e 100644
--- views.info
+++ views.info
@@ -13,7 +13,9 @@ files[] = handlers/views_handler_argument_many_to_one.inc
 files[] = handlers/views_handler_argument_null.inc
 files[] = handlers/views_handler_argument_numeric.inc
 files[] = handlers/views_handler_argument_string.inc
+files[] = handlers/views_handler_argument_group_by_numeric.inc
 files[] = handlers/views_handler_field.inc
+files[] = handlers/views_handler_field_group_by_numeric.inc
 files[] = handlers/views_handler_field_boolean.inc
 files[] = handlers/views_handler_field_custom.inc
 files[] = handlers/views_handler_field_date.inc
@@ -22,6 +24,7 @@ files[] = handlers/views_handler_field_numeric.inc
 files[] = handlers/views_handler_field_prerender_list.inc
 files[] = handlers/views_handler_field_url.inc
 files[] = handlers/views_handler_filter.inc
+files[] = handlers/views_handler_filter_group_by_numeric.inc
 files[] = handlers/views_handler_filter_boolean_operator.inc
 files[] = handlers/views_handler_filter_boolean_operator_string.inc
 files[] = handlers/views_handler_filter_date.inc
@@ -32,6 +35,7 @@ files[] = handlers/views_handler_filter_numeric.inc
 files[] = handlers/views_handler_filter_string.inc
 files[] = handlers/views_handler_relationship.inc
 files[] = handlers/views_handler_sort.inc
+files[] = handlers/views_handler_sort_group_by_numeric.inc
 files[] = handlers/views_handler_sort_date.inc
 files[] = handlers/views_handler_sort_formula.inc
 files[] = handlers/views_handler_sort_menu_hierarchy.inc
@@ -229,3 +233,5 @@ files[] = theme/views-view-unformatted.tpl.php
 files[] = theme/views-view.tpl.php
 ; Tests
 files[] = tests/views_query.test
+files[] = tests/views_groupby.test
+files[] = tests/views_pager.test
diff --git views.module views.module
index 42500a8..c5445f2 100644
--- views.module
+++ views.module
@@ -13,7 +13,14 @@
  * Advertise the current views api version
  */
 function views_api_version() {
-  return 2.0;
+  return '3.0-alpha1';
+}
+
+/**
+ * Views will not load plugins advertising a version older than this.
+ */
+function views_api_minimum_version() {
+  return '2';
 }
 
 /**
@@ -545,7 +552,8 @@ function views_get_module_apis() {
     $cache = array();
     foreach (module_implements('views_api') as $module) {
       $info = module_invoke($module, 'views_api');
-      if (isset($info['api']) && $info['api'] == 2.000) {
+      if (version_compare($info['api'], views_api_minimum_version(), '>=') &&
+          version_compare($info['api'], views_api_version(), '<=')) {
         $cache[$module] = $info;
         if (!isset($info['path'])) {
           $info['path'] = drupal_get_path('module', $module);
@@ -638,19 +646,35 @@ function views_include_default_views() {
  *   The name of the field this handler is from.
  * @param $key
  *   The type of handler. i.e, sort, field, argument, filter, relationship
+ * @param $override
+ *   Override the actual handler object with this class. Used for
+ *   aggregation when the handler is redirected to the aggregation
+ *   handler.
  *
  * @return
  *   An instance of a handler object. May be views_handler_broken.
  */
-function views_get_handler($table, $field, $key) {
+function views_get_handler($table, $field, $key, $override = NULL) {
   $data = views_fetch_data($table);
+  $handler = NULL;
+
   if (isset($data[$field][$key])) {
     // Set up a default handler:
     if (empty($data[$field][$key]['handler'])) {
       $data[$field][$key]['handler'] = 'views_handler_' . $key;
     }
-    return _views_prepare_handler($data[$field][$key], $data, $field);
+
+    if ($override) {
+      $data[$field][$key]['override handler'] = $override;
+    }
+
+    $handler = _views_prepare_handler($data[$field][$key], $data, $field);
   }
+
+  if ($handler) {
+    return $handler;
+  }
+
   // DEBUG -- identify missing handlers
   debug("Missing handler: $table $field $key");
   $broken = array(
diff --git views_ui.module views_ui.module
index 332fc71..aa20f7b 100644
--- views_ui.module
+++ views_ui.module
@@ -261,6 +261,7 @@ function views_ui_cache_set(&$view) {
   unset($view->display_handler);
   unset($view->current_display);
   unset($view->default_display);
+  $view->query = NULL;
   foreach (array_keys($view->display) as $id) {
     unset($view->display[$id]->handler);
     unset($view->display[$id]->default_display);
