• Main Page
  • Related Pages
  • Modules
  • Classes
  • Files
  • File List
  • File Members

plugins/views_plugin_query_default.inc

Go to the documentation of this file.
00001 <?php
00011 class views_plugin_query_default extends views_plugin_query {
00012 
00016   var $table_queue = array();
00017 
00021   var $tables = array();
00022 
00027   var $relationships = array();
00028 
00034   var $where = array();
00040   var $having = array();
00045   var $group_operator = 'AND';
00046 
00050   var $orderby = array();
00051 
00055   var $groupby = array();
00056 
00061   var $header = array();
00062 
00066   var $distinct = FALSE;
00067 
00068   var $has_aggregate = FALSE;
00069 
00073   var $get_count_optimized = NULL;
00074 
00080    var $pager = NULL;
00081 
00085    var $field_aliases = array();
00086 
00090    var $tags = array();
00091 
00095   function init($base_table = 'node', $base_field = 'nid', $options) {
00096     parent::init($base_table, $base_field, $options);
00097     $this->base_table = $base_table;  // Predefine these above, for clarity.
00098     $this->base_field = $base_field;
00099     $this->relationships[$base_table] = array(
00100       'link' => NULL,
00101       'table' => $base_table,
00102       'alias' => $base_table,
00103       'base' => $base_table
00104     );
00105 
00106     // init the table queue with our primary table.
00107     $this->table_queue[$base_table] = array(
00108       'alias' => $base_table,
00109       'table' => $base_table,
00110       'relationship' => $base_table,
00111       'join' => NULL,
00112     );
00113 
00114     // init the tables with our primary table
00115     $this->tables[$base_table][$base_table] = array(
00116       'count' => 1,
00117       'alias' => $base_table,
00118     );
00119 
00131     $this->count_field = array(
00132       'table' => $base_table,
00133       'field' => $base_field,
00134       'alias' => $base_field,
00135       'count' => TRUE,
00136     );
00137   }
00138 
00139   // ----------------------------------------------------------------
00140   // Utility methods to set flags and data.
00141 
00145   function set_distinct($value = TRUE) {
00146     if (!(isset($this->no_distinct) && $value)) {
00147       $this->distinct = $value;
00148     }
00149   }
00150 
00154   function set_count_field($table, $field, $alias = NULL) {
00155     if (empty($alias)) {
00156       $alias = $table . '_' . $field;
00157     }
00158     $this->count_field = array(
00159       'table' => $table,
00160       'field' => $field,
00161       'alias' => $alias,
00162       'count' => TRUE,
00163     );
00164   }
00165 
00170   function set_header($header) {
00171     $this->header = $header;
00172   }
00173 
00174   function option_definition() {
00175     $options = parent::option_definition();
00176     $options['disable_sql_rewrite'] = array(
00177       'default' => FALSE,
00178       'translatable' => FALSE,
00179       'bool' => TRUE,
00180     );
00181     $options['distinct'] = array(
00182       'default' => FALSE,
00183       'bool' => TRUE,
00184     );
00185     $options['slave'] = array(
00186       'default' => FALSE,
00187       'bool' => TRUE,
00188     );
00189     $options['query_comment'] = array(
00190       'default' => '',
00191       'bool' => FALSE,
00192     );
00193 
00194     return $options;
00195   }
00196 
00200   function options_form(&$form, &$form_state) {
00201     parent::options_form($form, $form_state);
00202 
00203     $form['disable_sql_rewrite'] = array(
00204       '#title' => t('Disable SQL rewriting'),
00205       '#description' => t('Disabling SQL rewriting will disable node_access checks as well as other modules that implement hook_query_alter().'),
00206       '#type' => 'checkbox',
00207       '#default_value' => !empty($this->options['disable_sql_rewrite']),
00208       '#suffix' => '<div class="messages warning sql-rewrite-warning js-hide">' . t('WARNING: Disabling SQL rewriting means that node access security is disabled. This may allow users to see data they should not be able to see if your view is misconfigured. Please use this option only if you understand and accept this security risk.') . '</div>',
00209     );
00210     $form['distinct'] = array(
00211       '#type' => 'checkbox',
00212       '#title' => t('Distinct'),
00213       '#description' => t('This will make the view display only distinct items. If there are multiple identical items, each will be displayed only once. You can use this to try and remove duplicates from a view, though it does not always work. Note that this can slow queries down, so use it with caution.'),
00214       '#default_value' => !empty($this->options['distinct']),
00215     );
00216     $form['slave'] = array(
00217       '#type' => 'checkbox',
00218       '#title' => t('Use Slave Server'),
00219       '#description' => t('This will make the query attempt to connect to a slave server if available.  If no slave server is defined or available, it will fall back to the default server.'),
00220       '#default_value' => !empty($this->options['slave']),
00221     );
00222     $form['query_comment'] = array(
00223       '#type' => 'textfield',
00224       '#title' => t('Query Comment'),
00225       '#description' => t('If set, this comment will be embedded in the query and passed to the SQL server. This can be helpful for logging or debugging.'),
00226       '#default_value' => $this->options['query_comment'],
00227     );
00228   }
00229 
00230   // ----------------------------------------------------------------
00231   // Table/join adding
00232 
00261   function add_relationship($alias, $join, $base, $link_point = NULL) {
00262     if (empty($link_point)) {
00263       $link_point = $this->base_table;
00264     }
00265     elseif (!array_key_exists($link_point, $this->relationships)) {
00266       return FALSE;
00267     }
00268 
00269     // Make sure $alias isn't already used; if it, start adding stuff.
00270     $alias_base = $alias;
00271     $count = 1;
00272     while (!empty($this->relationships[$alias])) {
00273       $alias = $alias_base . '_' . $count++;
00274     }
00275 
00276     // Make sure this join is adjusted for our relationship.
00277     if ($link_point && isset($this->relationships[$link_point])) {
00278       $join = $this->adjust_join($join, $link_point);
00279     }
00280 
00281     // Add the table directly to the queue to avoid accidentally marking
00282     // it.
00283     $this->table_queue[$alias] = array(
00284       'table' => $join->table,
00285       'num' => 1,
00286       'alias' => $alias,
00287       'join' => $join,
00288       'relationship' => $link_point,
00289     );
00290 
00291     $this->relationships[$alias] = array(
00292       'link' => $link_point,
00293       'table' => $join->table,
00294       'base' => $base,
00295     );
00296 
00297     $this->tables[$this->base_table][$alias] = array(
00298       'count' => 1,
00299       'alias' => $alias,
00300     );
00301 
00302     return $alias;
00303   }
00304 
00335   function add_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
00336     if (!$this->ensure_path($table, $relationship, $join)) {
00337       return FALSE;
00338     }
00339 
00340     if ($join && $relationship) {
00341       $join = $this->adjust_join($join, $relationship);
00342     }
00343 
00344     return $this->queue_table($table, $relationship, $join, $alias);
00345   }
00346 
00374   function queue_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
00375     // If the alias is set, make sure it doesn't already exist.
00376     if (isset($this->table_queue[$alias])) {
00377       return $alias;
00378     }
00379 
00380     if (empty($relationship)) {
00381       $relationship = $this->base_table;
00382     }
00383 
00384     if (!array_key_exists($relationship, $this->relationships)) {
00385       return FALSE;
00386     }
00387 
00388     if (!$alias && $join && $relationship && !empty($join->adjusted) && $table != $join->table) {
00389       if ($relationship == $this->base_table) {
00390         $alias = $table;
00391       }
00392       else {
00393         $alias = $relationship . '_' . $table;
00394       }
00395     }
00396 
00397     // Check this again to make sure we don't blow up existing aliases for already
00398     // adjusted joins.
00399     if (isset($this->table_queue[$alias])) {
00400       return $alias;
00401     }
00402 
00403     $alias = $this->mark_table($table, $relationship, $alias);
00404 
00405     // If no alias is specified, give it the default.
00406     if (!isset($alias)) {
00407       $alias = $this->tables[$relationship][$table]['alias'] . $this->tables[$relationship][$table]['count'];
00408     }
00409 
00410     // If this is a relationship based table, add a marker with
00411     // the relationship as a primary table for the alias.
00412     if ($table != $alias) {
00413       $this->mark_table($alias, $this->base_table, $alias);
00414     }
00415 
00416     // If no join is specified, pull it from the table data.
00417     if (!isset($join)) {
00418       $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
00419       if (empty($join)) {
00420         return FALSE;
00421       }
00422 
00423       $join = $this->adjust_join($join, $relationship);
00424     }
00425 
00426     $this->table_queue[$alias] = array(
00427       'table' => $table,
00428       'num' => $this->tables[$relationship][$table]['count'],
00429       'alias' => $alias,
00430       'join' => $join,
00431       'relationship' => $relationship,
00432     );
00433 
00434     return $alias;
00435   }
00436 
00437   function mark_table($table, $relationship, $alias) {
00438     // Mark that this table has been added.
00439     if (empty($this->tables[$relationship][$table])) {
00440       if (!isset($alias)) {
00441         $alias = '';
00442         if ($relationship != $this->base_table) {
00443           // double underscore will help prevent accidental name
00444           // space collisions.
00445           $alias = $relationship . '__';
00446         }
00447         $alias .= $table;
00448       }
00449       $this->tables[$relationship][$table] = array(
00450         'count' => 1,
00451         'alias' => $alias,
00452       );
00453     }
00454     else {
00455       $this->tables[$relationship][$table]['count']++;
00456     }
00457 
00458     return $alias;
00459   }
00460 
00479   function ensure_table($table, $relationship = NULL, $join = NULL) {
00480     // ensure a relationship
00481     if (empty($relationship)) {
00482       $relationship = $this->base_table;
00483     }
00484 
00485     // If the relationship is the primary table, this actually be a relationship
00486     // link back from an alias. We store all aliases along with the primary table
00487     // to detect this state, because eventually it'll hit a table we already
00488     // have and that's when we want to stop.
00489     if ($relationship == $this->base_table && !empty($this->tables[$relationship][$table])) {
00490       return $this->tables[$relationship][$table]['alias'];
00491     }
00492 
00493     if (!array_key_exists($relationship, $this->relationships)) {
00494       return FALSE;
00495     }
00496 
00497     if ($table == $this->relationships[$relationship]['base']) {
00498       return $relationship;
00499     }
00500 
00501     // If we do not have join info, fetch it.
00502     if (!isset($join)) {
00503       $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
00504     }
00505 
00506     // If it can't be fetched, this won't work.
00507     if (empty($join)) {
00508       return;
00509     }
00510 
00511     // Adjust this join for the relationship, which will ensure that the 'base'
00512     // table it links to is correct. Tables adjoined to a relationship
00513     // join to a link point, not the base table.
00514     $join = $this->adjust_join($join, $relationship);
00515 
00516     if ($this->ensure_path($table, $relationship, $join)) {
00517       // Attempt to eliminate redundant joins.  If this table's
00518       // relationship and join exactly matches an existing table's
00519       // relationship and join, we do not have to join to it again;
00520       // just return the existing table's alias.  See
00521       // http://groups.drupal.org/node/11288 for details.
00522       //
00523       // This can be done safely here but not lower down in
00524       // queue_table(), because queue_table() is also used by
00525       // add_table() which requires the ability to intentionally add
00526       // the same table with the same join multiple times.  For
00527       // example, a view that filters on 3 taxonomy terms using AND
00528       // needs to join taxonomy_term_data 3 times with the same join.
00529 
00530       // scan through the table queue to see if a matching join and
00531       // relationship exists.  If so, use it instead of this join.
00532 
00533       // TODO: Scanning through $this->table_queue results in an
00534       // O(N^2) algorithm, and this code runs every time the view is
00535       // instantiated (Views 2 does not currently cache queries).
00536       // There are a couple possible "improvements" but we should do
00537       // some performance testing before picking one.
00538       foreach ($this->table_queue as $queued_table) {
00539         // In PHP 4 and 5, the == operation returns TRUE for two objects
00540         // if they are instances of the same class and have the same
00541         // attributes and values.
00542         if ($queued_table['relationship'] == $relationship && $queued_table['join'] == $join) {
00543           return $queued_table['alias'];
00544         }
00545       }
00546 
00547       return $this->queue_table($table, $relationship, $join);
00548     }
00549   }
00550 
00558   function ensure_path($table, $relationship = NULL, $join = NULL, $traced = array(), $add = array()) {
00559     if (!isset($relationship)) {
00560       $relationship = $this->base_table;
00561     }
00562 
00563     if (!array_key_exists($relationship, $this->relationships)) {
00564       return FALSE;
00565     }
00566 
00567     // If we do not have join info, fetch it.
00568     if (!isset($join)) {
00569       $join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
00570     }
00571 
00572     // If it can't be fetched, this won't work.
00573     if (empty($join)) {
00574       return FALSE;
00575     }
00576 
00577     // Does a table along this path exist?
00578     if (isset($this->tables[$relationship][$table]) ||
00579       ($join && $join->left_table == $relationship) ||
00580       ($join && $join->left_table == $this->relationships[$relationship]['table'])) {
00581 
00582       // Make sure that we're linking to the correct table for our relationship.
00583       foreach (array_reverse($add) as $table => $path_join) {
00584         $this->queue_table($table, $relationship, $this->adjust_join($path_join, $relationship));
00585       }
00586       return TRUE;
00587     }
00588 
00589     // Have we been this way?
00590     if (isset($traced[$join->left_table])) {
00591       // we looped. Broked.
00592       return FALSE;
00593     }
00594 
00595     // Do we have to add this table?
00596     $left_join = $this->get_join_data($join->left_table, $this->relationships[$relationship]['base']);
00597     if (!isset($this->tables[$relationship][$join->left_table])) {
00598       $add[$join->left_table] = $left_join;
00599     }
00600 
00601     // Keep looking.
00602     $traced[$join->left_table] = TRUE;
00603     return $this->ensure_path($join->left_table, $relationship, $left_join, $traced, $add);
00604   }
00605 
00610   function adjust_join($join, $relationship) {
00611     if (!empty($join->adjusted)) {
00612       return $join;
00613     }
00614 
00615     if (empty($relationship) || empty($this->relationships[$relationship])) {
00616       return $join;
00617     }
00618 
00619     // Adjusts the left table for our relationship.
00620     if ($relationship != $this->base_table) {
00621       // If we're linking to the primary table, the relationship to use will
00622       // be the prior relationship. Unless it's a direct link.
00623 
00624       // Safety! Don't modify an original here.
00625       $join = clone $join;
00626 
00627       // Do we need to try to ensure a path?
00628       if ($join->left_table != $this->relationships[$relationship]['table'] &&
00629           $join->left_table != $this->relationships[$relationship]['base'] &&
00630           !isset($this->tables[$relationship][$join->left_table]['alias'])) {
00631         $this->ensure_table($join->left_table, $relationship);
00632       }
00633 
00634       // First, if this is our link point/anchor table, just use the relationship
00635       if ($join->left_table == $this->relationships[$relationship]['table']) {
00636         $join->left_table = $relationship;
00637       }
00638       // then, try the base alias.
00639       elseif (isset($this->tables[$relationship][$join->left_table]['alias'])) {
00640         $join->left_table = $this->tables[$relationship][$join->left_table]['alias'];
00641       }
00642       // But if we're already looking at an alias, use that instead.
00643       elseif (isset($this->table_queue[$relationship]['alias'])) {
00644         $join->left_table = $this->table_queue[$relationship]['alias'];
00645       }
00646     }
00647 
00648     $join->adjusted = TRUE;
00649     return $join;
00650   }
00651 
00663   function get_join_data($table, $base_table) {
00664     // Check to see if we're linking to a known alias. If so, get the real
00665     // table's data instead.
00666     if (!empty($this->table_queue[$table])) {
00667       $table = $this->table_queue[$table]['table'];
00668     }
00669     return views_get_table_join($table, $base_table);
00670   }
00671 
00678   function get_table_info($table) {
00679     if (!empty($this->table_queue[$table])) {
00680       return $this->table_queue[$table];
00681     }
00682 
00683     // In rare cases we might *only* have aliased versions of the table.
00684     if (!empty($this->tables[$this->base_table][$table])) {
00685       $alias = $this->tables[$this->base_table][$table]['alias'];
00686       if (!empty($this->table_queue[$alias])) {
00687         return $this->table_queue[$alias];
00688       }
00689     }
00690   }
00691 
00714   function add_field($table, $field, $alias = '', $params = array()) {
00715     // We check for this specifically because it gets a special alias.
00716     if ($table == $this->base_table && $field == $this->base_field && empty($alias)) {
00717       $alias = $this->base_field;
00718     }
00719 
00720     if ($table && empty($this->table_queue[$table])) {
00721       $this->ensure_table($table);
00722     }
00723 
00724     if (!$alias && $table) {
00725       $alias = $table . '_' . $field;
00726     }
00727 
00728     // Make sure an alias is assigned
00729     $alias = $alias ? $alias : $field;
00730 
00731     // PostgreSQL truncates aliases to 63 characters: http://drupal.org/node/571548
00732 
00733     // We limit the length of the original alias up to 60 characters
00734     // to get a unique alias later if its have duplicates
00735     $alias = strtolower(substr($alias, 0, 60));
00736 
00737     // Create a field info array.
00738     $field_info = array(
00739       'field' => $field,
00740       'table' => $table,
00741       'alias' => $alias,
00742     ) + $params;
00743 
00744     // Test to see if the field is actually the same or not. Due to
00745     // differing parameters changing the aggregation function, we need
00746     // to do some automatic alias collision detection:
00747     $base = $alias;
00748     $counter = 0;
00749     while (!empty($this->fields[$alias]) && $this->fields[$alias] != $field_info) {
00750       $field_info['alias'] = $alias = $base . '_' . ++$counter;
00751     }
00752 
00753     if (empty($this->fields[$alias])) {
00754       $this->fields[$alias] = $field_info;
00755     }
00756 
00757     // Keep track of all aliases used.
00758     $this->field_aliases[$table][$field] = $alias;
00759 
00760     return $alias;
00761   }
00762 
00767   function clear_fields() {
00768     $this->fields = array();
00769   }
00770 
00793   function add_where($group, $field, $value = NULL, $operator = NULL) {
00794     // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
00795     // the default group.
00796     if (empty($group)) {
00797       $group = 0;
00798     }
00799 
00800     // Check for a group.
00801     if (!isset($this->where[$group])) {
00802       $this->set_where_group('AND', $group);
00803     }
00804 
00805     $this->where[$group]['conditions'][] = array(
00806       'field' => $field,
00807       'value' => $value,
00808       'operator' => $operator,
00809     );
00810   }
00811 
00831   function add_where_expression($group, $snippet, $args = array()) {
00832     // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
00833     // the default group.
00834     if (empty($group)) {
00835       $group = 0;
00836     }
00837 
00838     // Check for a group.
00839     if (!isset($this->where[$group])) {
00840       $this->set_where_group('AND', $group);
00841     }
00842 
00843     $this->where[$group]['conditions'][] = array(
00844       'field' => $snippet,
00845       'value' => $args,
00846       'operator' => 'formula',
00847     );
00848   }
00849 
00874   function add_having($group, $field, $value = NULL, $operator = NULL) {
00875     // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
00876     // the default group.
00877     if (empty($group)) {
00878       $group = 0;
00879     }
00880 
00881     // Check for a group.
00882     if (!isset($this->having[$group])) {
00883       $this->set_where_group('AND', $group, 'having');
00884     }
00885 
00886     // Add the clause and the args.
00887     $this->having[$group]['conditions'][] = array(
00888       'field' => $field,
00889       'value' => $value,
00890       'operator' => $operator,
00891     );
00892   }
00893 
00912   function add_having_expression($group, $snippet, $args = array()) {
00913     // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
00914     // the default group.
00915     if (empty($group)) {
00916       $group = 0;
00917     }
00918 
00919     // Check for a group.
00920     if (!isset($this->having[$group])) {
00921       $this->set_where_group('AND', $group, 'having');
00922     }
00923 
00924     // Add the clause and the args.
00925     $this->having[$group]['conditions'][] = array(
00926       'field' => $snippet,
00927       'value' => $args,
00928       'operator' => 'formula',
00929     );
00930   }
00931 
00951   function add_orderby($table, $field = NULL, $order = 'ASC', $alias = '', $params = array()) {
00952     // Only ensure the table if it's not the special random key.
00953     // @todo: Maybe it would make sense to just add a add_orderby_rand or something similar.
00954     if ($table && $table != 'rand') {
00955       $this->ensure_table($table);
00956     }
00957 
00958     // Only fill out this aliasing if there is a table;
00959     // otherwise we assume it is a formula.
00960     if (!$alias && $table) {
00961       $as = $table . '_' . $field;
00962     }
00963     else {
00964       $as = $alias;
00965     }
00966 
00967     if ($field) {
00968       $as = $this->add_field($table, $field, $as, $params);
00969     }
00970 
00971     $this->orderby[] = array(
00972       'field' => $as,
00973       'direction' => strtoupper($order)
00974     );
00975 
00986   }
00987 
00993   function add_groupby($clause) {
00994     // Only add it if it's not already in there.
00995     if (!in_array($clause, $this->groupby)) {
00996       $this->groupby[] = $clause;
00997     }
00998   }
00999 
01005   function get_field_alias($table_alias, $field) {
01006     return isset($this->field_aliases[$table_alias][$field]) ? $this->field_aliases[$table_alias][$field] : FALSE;
01007   }
01008 
01014   function add_tag($tag) {
01015     $this->tags[] = $tag;
01016   }
01017 
01021   function placeholder($base = 'views') {
01022     static $placeholders = array();
01023     if (!isset($placeholders[$base])) {
01024       $placeholders[$base] = 0;
01025       return ':' . $base;
01026     }
01027     else {
01028       return ':' . $base . ++$placeholders[$base];
01029     }
01030   }
01031 
01043   function build_condition($where = 'where') {
01044     $has_condition = FALSE;
01045     $has_arguments = FALSE;
01046     $has_filter = FALSE;
01047 
01048     $main_group = db_and();
01049     $filter_group = $this->group_operator == 'OR' ? db_or() : db_and();
01050 
01051     foreach ($this->$where as $group => $info) {
01052 
01053       if (!empty($info['conditions'])) {
01054         $sub_group = $info['type'] == 'OR' ? db_or() : db_and();
01055         foreach ($info['conditions'] as $key => $clause) {
01056           // DBTNG doesn't support to add the same subquery twice to the main
01057           // query and the count query, so clone the subquery to have two instances
01058           // of the same object. - http://drupal.org/node/1112854
01059           if (is_object($clause['value']) && $clause['value'] instanceof SelectQuery) {
01060             $clause['value'] = clone $clause['value'];
01061           }
01062           if ($clause['operator'] == 'formula') {
01063             $has_condition = TRUE;
01064             $sub_group->where($clause['field'], $clause['value']);
01065           }
01066           else {
01067             $has_condition = TRUE;
01068             $sub_group->condition($clause['field'], $clause['value'], $clause['operator']);
01069           }
01070         }
01071 
01072         // Add the item to the filter group.
01073         if ($group != 0) {
01074           $has_filter = TRUE;
01075           $filter_group->condition($sub_group);
01076         }
01077         else {
01078           $has_arguments = TRUE;
01079           $main_group->condition($sub_group);
01080         }
01081       }
01082     }
01083 
01084     if ($has_filter) {
01085       $main_group->condition($filter_group);
01086     }
01087 
01088     if (!$has_arguments && $has_condition) {
01089       return $filter_group;
01090     }
01091     if ($has_arguments && $has_condition) {
01092       return $main_group;
01093     }
01094   }
01095 
01099   function compile_fields($fields_array, $query) {
01100     $non_aggregates = array();
01101     foreach ($fields_array as $field) {
01102       $string = '';
01103       if (!empty($field['table'])) {
01104         $string .= $field['table'] . '.';
01105       }
01106       $string .= $field['field'];
01107       $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
01108 
01109       if (!empty($field['distinct'])) {
01110         throw new Exception("Column-level distinct is not supported anymore.");
01111       }
01112 
01113       if (!empty($field['count'])) {
01114         // Retained for compatibility
01115         $field['function'] = 'count';
01116         // It seems there's no way to abstract the table+column reference
01117         // without adding a field, aliasing, and then using the alias.
01118       }
01119 
01120       if (!empty($field['function'])) {
01121         $info = $this->get_aggregation_info();
01122         if (!empty($info[$field['function']]['method']) && function_exists($info[$field['function']]['method'])) {
01123           $string = $info[$field['function']]['method']($field['function'], $string);
01124           $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : array();
01125           $query->addExpression($string, $fieldname, $placeholders);
01126         }
01127 
01128         $this->has_aggregate = TRUE;
01129       }
01130       // This is a formula, using no tables.
01131       elseif (empty($field['table'])) {
01132         $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : array();
01133         $query->addExpression($string, $fieldname, $placeholders);
01134       }
01135 
01136       elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
01137         // d7cx: This code was there, apparently needed for PostgreSQL
01138         // $string = db_driver() == 'pgsql' ? "FIRST($string)" : $string;
01139         $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
01140       }
01141       elseif (empty($field['aggregate'])) {
01142         $non_aggregates[] = $fieldname;
01143         $query->addField(!empty($field['table']) ? $field['table'] : $this->base_table, $field['field'], $fieldname);
01144       }
01145 
01146       // @TODO Remove this old code.
01147       if (!empty($field['distinct']) && empty($field['function'])) {
01148         $distinct[] = $string;
01149       }
01150       else {
01151         $fields[] = $string;
01152       }
01153 
01154       if ($this->get_count_optimized) {
01155         // We only want the first field in this case.
01156         break;
01157       }
01158     }
01159     return array(
01160       $non_aggregates,
01161     );
01162   }
01163 
01171   function query($get_count = FALSE) {
01172     // Check query distinct value.
01173     if (empty($this->no_distinct) && $this->distinct && !empty($this->fields)) {
01174       $base_field_alias = $this->add_field($this->base_table, $this->base_field);
01175       $this->add_groupby($base_field_alias);
01176       $distinct = TRUE;
01177     }
01178 
01183     $fields_array = $this->fields;
01184     if ($get_count && !$this->groupby) {
01185       foreach ($fields_array as $field) {
01186         if (!empty($field['distinct']) || !empty($field['function'])) {
01187           $this->get_count_optimized = FALSE;
01188           break;
01189         }
01190       }
01191     }
01192     else {
01193       $this->get_count_optimized = FALSE;
01194     }
01195     if (!isset($this->get_count_optimized)) {
01196       $this->get_count_optimized = TRUE;
01197     }
01198 
01199     $options = array();
01200     $target = 'default';
01201     $key = 'default';
01202     // Detect an external database and set the
01203     if (isset($this->view->base_database)) {
01204       $key = $this->view->base_database;
01205     }
01206 
01207     // Set the slave target if the slave option is set
01208     if (!empty($this->options['slave'])) {
01209       $target = 'slave';
01210     }
01211 
01212     // Go ahead and build the query.
01213     // db_select doesn't support to specify the key, so use getConnection directly.
01214     $query = Database::getConnection($target, $key)
01215       ->select($this->base_table, $this->base_table, $options)
01216       ->addTag('views')
01217       ->addTag('views_' . $this->view->name);
01218 
01219     // Add the tags added to the view itself.
01220     foreach ($this->tags as $tag) {
01221       $query->addTag($tag);
01222     }
01223 
01224     if (!empty($distinct)) {
01225       $query->distinct();
01226     }
01227 
01228     $joins = $where = $having = $orderby = $groupby = '';
01229     $fields = $distinct = array();
01230 
01231     // Add all the tables to the query via joins. We assume all LEFT joins.
01232     foreach ($this->table_queue as $table) {
01233       if (is_object($table['join'])) {
01234         $table['join']->build_join($query, $table, $this);
01235       }
01236     }
01237 
01238     $this->has_aggregate = FALSE;
01239     $non_aggregates = array();
01240 
01241     list($non_aggregates) = $this->compile_fields($fields_array, $query);
01242 
01243     if (count($this->having)) {
01244       $this->has_aggregate = TRUE;
01245     }
01246     if ($this->has_aggregate && (!empty($this->groupby) || !empty($non_aggregates))) {
01247       $groupby = array_unique(array_merge($this->groupby, $non_aggregates));
01248       foreach ($groupby as $field) {
01249         $query->groupBy($field);
01250       }
01251       if (!empty($this->having) && $condition = $this->build_condition('having')) {
01252         $query->havingCondition($condition);
01253       }
01254     }
01255 
01256     if (!$this->get_count_optimized) {
01257       // we only add the orderby if we're not counting.
01258       if ($this->orderby) {
01259         foreach ($this->orderby as $order) {
01260           if ($order['field'] == 'rand_') {
01261             $query->orderRandom();
01262           }
01263           else {
01264             $query->orderBy($order['field'], $order['direction']);
01265           }
01266         }
01267       }
01268     }
01269 
01270     if (!empty($this->where) && $condition = $this->build_condition('where')) {
01271       $query->condition($condition);
01272     }
01273 
01274     // Add a query comment.
01275     if (!empty($this->options['query_comment'])) {
01276       $query->comment($this->options['query_comment']);
01277     }
01278 
01279     // Add all query substitutions as metadata.
01280     $query->addMetaData('views_substitutions', module_invoke_all('views_query_substitutions', $this));
01281 
01282     return $query;
01283   }
01284 
01288   function get_where_args() {
01289     $args = array();
01290     foreach ($this->where as $group => $where) {
01291       $args = array_merge($args, $where['args']);
01292     }
01293     foreach ($this->having as $group => $having) {
01294       $args = array_merge($args, $having['args']);
01295     }
01296     return $args;
01297   }
01298 
01302   function alter(&$view) {
01303     foreach (module_implements('views_query_alter') as $module) {
01304       $function = $module . '_views_query_alter';
01305       $function($view, $this);
01306     }
01307   }
01308 
01312   function build(&$view) {
01313     // Make the query distinct if the option was set.
01314     if (!empty($this->options['distinct'])) {
01315       $this->set_distinct();
01316     }
01317 
01318     // Store the view in the object to be able to use it later.
01319     $this->view = $view;
01320 
01321     $view->init_pager();
01322 
01323     // Let the pager modify the query to add limits.
01324     $this->pager->query();
01325 
01326     $view->build_info['query'] = $this->query();
01327     $view->build_info['count_query'] = $this->query(TRUE);
01328   }
01329 
01337   function execute(&$view) {
01338     $external = FALSE; // Whether this query will run against an external database.
01339     $query = $view->build_info['query'];
01340     $count_query = $view->build_info['count_query'];
01341 
01342     $query->addMetaData('view', $view);
01343     $count_query->addMetaData('view', $view);
01344 
01345     if (empty($this->options['disable_sql_rewrite'])) {
01346       $base_table_data = views_fetch_data($this->base_table);
01347       if (isset($base_table_data['table']['base']['access query tag'])) {
01348         $access_tag = $base_table_data['table']['base']['access query tag'];
01349         $query->addTag($access_tag);
01350         $count_query->addTag($access_tag);
01351       }
01352     }
01353 
01354     $items = array();
01355     if ($query) {
01356       $additional_arguments = module_invoke_all('views_query_substitutions', $view);
01357 
01358       // Count queries must be run through the preExecute() method.
01359       // If not, then hook_query_node_access_alter() may munge the count by
01360       // adding a distinct against an empty query string
01361       // (e.g. COUNT DISTINCT(1) ...) and no pager will return.
01362       // See pager.inc > PagerDefault::execute()
01363       // http://api.drupal.org/api/drupal/includes--pager.inc/function/PagerDefault::execute/7
01364       // See http://drupal.org/node/1046170.
01365       $count_query->preExecute();
01366 
01367       // Build the count query.
01368       $count_query = $count_query->countQuery();
01369 
01370       // Add additional arguments as a fake condition.
01371       // XXX: this doesn't work... because PDO mandates that all bound arguments
01372       // are used on the query. TODO: Find a better way to do this.
01373       if (!empty($additional_arguments)) {
01374         // $query->where('1 = 1', $additional_arguments);
01375         // $count_query->where('1 = 1', $additional_arguments);
01376       }
01377 
01378       $start = microtime(TRUE);
01379 
01380 
01381       try {
01382         if ($this->pager->use_count_query() || !empty($view->get_total_rows)) {
01383           $this->pager->execute_count_query($count_query);
01384         }
01385 
01386         // Let the pager modify the query to add limits.
01387         $this->pager->pre_execute($query);
01388 
01389         if (!empty($this->limit) || !empty($this->offset)) {
01390           // We can't have an offset without a limit, so provide a very large limit instead.
01391           $limit  = intval(!empty($this->limit) ? $this->limit : 999999);
01392           $offset = intval(!empty($this->offset) ? $this->offset : 0);
01393           $query->range($offset, $limit);
01394         }
01395 
01396         $result = $query->execute();
01397 
01398         $view->result = array();
01399         foreach ($result as $item) {
01400           $view->result[] = $item;
01401         }
01402 
01403         $this->pager->post_execute($view->result);
01404 
01405         if ($this->pager->use_pager()) {
01406           $view->total_rows = $this->pager->get_total_items();
01407         }
01408       }
01409       catch (Exception $e) {
01410         $view->result = array();
01411         if (!empty($view->live_preview)) {
01412           drupal_set_message(time());
01413           drupal_set_message($e->getMessage(), 'error');
01414         }
01415         else {
01416           vpr('Exception in @human_name[@view_name]: @message', array('@human_name' => $view->human_name, '@view_name' => $view->name, '@message' => $e->getMessage()));
01417         }
01418       }
01419 
01420     }
01421     $view->execute_time = microtime(TRUE) - $start;
01422   }
01423 
01424   function add_signature(&$view) {
01425     $view->query->add_field(NULL, "'" . $view->name . ':' . $view->current_display . "'", 'view_name');
01426   }
01427 
01428   function get_aggregation_info() {
01429     // @todo -- need a way to get database specific and customized aggregation
01430     // functions into here.
01431     return array(
01432       'group' => array(
01433         'title' => t('Group results together'),
01434         'is aggregate' => FALSE,
01435       ),
01436       'count' => array(
01437         'title' => t('Count'),
01438         'method' => 'views_query_default_aggregation_method_simple',
01439         'handler' => array(
01440           'argument' => 'views_handler_argument_group_by_numeric',
01441           'field' => 'views_handler_field_numeric',
01442           'filter' => 'views_handler_filter_group_by_numeric',
01443           'sort' => 'views_handler_sort_group_by_numeric',
01444         ),
01445       ),
01446       'count_distinct' => array(
01447         'title' => t('Count DISTINCT'),
01448         'method' => 'views_query_default_aggregation_method_distinct',
01449         'handler' => array(
01450           'argument' => 'views_handler_argument_group_by_numeric',
01451           'field' => 'views_handler_field_numeric',
01452           'filter' => 'views_handler_filter_group_by_numeric',
01453           'sort' => 'views_handler_sort_group_by_numeric',
01454         ),
01455       ),
01456       'sum' => array(
01457         'title' => t('Sum'),
01458         'method' => 'views_query_default_aggregation_method_simple',
01459         'handler' => array(
01460           'argument' => 'views_handler_argument_group_by_numeric',
01461           'filter' => 'views_handler_filter_group_by_numeric',
01462           'sort' => 'views_handler_sort_group_by_numeric',
01463         ),
01464       ),
01465       'avg' => array(
01466         'title' => t('Average'),
01467         'method' => 'views_query_default_aggregation_method_simple',
01468         'handler' => array(
01469           'argument' => 'views_handler_argument_group_by_numeric',
01470           'filter' => 'views_handler_filter_group_by_numeric',
01471           'sort' => 'views_handler_sort_group_by_numeric',
01472         ),
01473       ),
01474       'min' => array(
01475         'title' => t('Minimum'),
01476         'method' => 'views_query_default_aggregation_method_simple',
01477         'handler' => array(
01478           'argument' => 'views_handler_argument_group_by_numeric',
01479           'filter' => 'views_handler_filter_group_by_numeric',
01480           'sort' => 'views_handler_sort_group_by_numeric',
01481         ),
01482       ),
01483       'max' => array(
01484         'title' => t('Maximum'),
01485         'method' => 'views_query_default_aggregation_method_simple',
01486         'handler' => array(
01487           'argument' => 'views_handler_argument_group_by_numeric',
01488           'filter' => 'views_handler_filter_group_by_numeric',
01489           'sort' => 'views_handler_sort_group_by_numeric',
01490         ),
01491       ),
01492     );
01493   }
01494 
01499   function get_result_entities($results, $relationship = NULL) {
01500     $base_table = $this->base_table;
01501     $base_table_alias = $base_table;
01502 
01503     if (!empty($relationship)) {
01504       foreach ($this->view->relationship as $current) {
01505         if ($current->alias == $relationship) {
01506           $base_table = $current->definition['base'];
01507           $base_table_alias = $relationship;
01508           break;
01509         }
01510       }
01511     }
01512     $table_data = views_fetch_data($base_table);
01513 
01514     // Bail out if the table has not specified the according entity-type.
01515     if (!isset($table_data['table']['entity type'])) {
01516       return FALSE;
01517     }
01518     $entity_type = $table_data['table']['entity type'];
01519     $info = entity_get_info($entity_type);
01520     $id_alias = $this->get_field_alias($base_table_alias, $info['entity keys']['id']);
01521 
01522     // Assemble the ids of the entities to load.
01523     $ids = array();
01524     foreach ($results as $key => $result) {
01525       if (isset($result->$id_alias)) {
01526         $ids[$key] = $result->$id_alias;
01527       }
01528     }
01529 
01530     $entities = entity_load($entity_type, $ids);
01531     // Re-key the array by row-index.
01532     $result = array();
01533     foreach ($ids as $key => $id) {
01534       $result[$key] = isset($entities[$id]) ? $entities[$id] : FALSE;
01535     }
01536     return array($entity_type, $result);
01537   }
01538 }
01539 
01540 function views_query_default_aggregation_method_simple($group_type, $field) {
01541   return strtoupper($group_type) . '(' . $field . ')';
01542 }
01543 
01544 function views_query_default_aggregation_method_distinct($group_type, $field) {
01545   $group_type = str_replace('_distinct', '', $group_type);
01546   return strtoupper($group_type) . '(DISTINCT ' . $field . ')';
01547 }

Generated on Sun Feb 26 2012 12:52:52 for Views by  doxygen 1.7.1