| 1 |
<?php
|
| 2 |
// $Id: query.inc,v 1.49 2009/10/05 22:31:09 merlinofchaos Exp $
|
| 3 |
/**
|
| 4 |
* @file query.inc
|
| 5 |
* Defines the query object which is the underlying layer in a View.
|
| 6 |
*/
|
| 7 |
|
| 8 |
/**
|
| 9 |
* Object used to create a SELECT query.
|
| 10 |
*/
|
| 11 |
class views_query {
|
| 12 |
|
| 13 |
/**
|
| 14 |
* A list of tables in the order they should be added, keyed by alias.
|
| 15 |
*/
|
| 16 |
var $table_queue = array();
|
| 17 |
|
| 18 |
/**
|
| 19 |
* Holds an array of tables and counts added so that we can create aliases
|
| 20 |
*/
|
| 21 |
var $tables = array();
|
| 22 |
|
| 23 |
/**
|
| 24 |
* Holds an array of relationships, which are aliases of the primary
|
| 25 |
* table that represent different ways to join the same table in.
|
| 26 |
*/
|
| 27 |
var $relationships = array();
|
| 28 |
|
| 29 |
/**
|
| 30 |
* An array of sections of the WHERE query. Each section is in itself
|
| 31 |
* an array of pieces and a flag as to whether or not it should be AND
|
| 32 |
* or OR.
|
| 33 |
*/
|
| 34 |
var $where = array();
|
| 35 |
/**
|
| 36 |
* An array of sections of the HAVING query. Each section is in itself
|
| 37 |
* an array of pieces and a flag as to whether or not it should be AND
|
| 38 |
* or OR.
|
| 39 |
*/
|
| 40 |
var $having = array();
|
| 41 |
/**
|
| 42 |
* The default operator to use when connecting the WHERE groups. May be
|
| 43 |
* AND or OR.
|
| 44 |
*/
|
| 45 |
var $group_operator = 'AND';
|
| 46 |
|
| 47 |
/**
|
| 48 |
* A simple array of order by clauses.
|
| 49 |
*/
|
| 50 |
var $orderby = array();
|
| 51 |
|
| 52 |
/**
|
| 53 |
* A simple array of group by clauses.
|
| 54 |
*/
|
| 55 |
var $groupby = array();
|
| 56 |
|
| 57 |
/**
|
| 58 |
* The table header to use for tablesort. This matters because tablesort
|
| 59 |
* needs to modify the query and needs the header.
|
| 60 |
*/
|
| 61 |
var $header = array();
|
| 62 |
|
| 63 |
/**
|
| 64 |
* A flag as to whether or not to make the primary field distinct.
|
| 65 |
*/
|
| 66 |
var $distinct = FALSE;
|
| 67 |
|
| 68 |
/**
|
| 69 |
* Constructor; Create the basic query object and fill with default values.
|
| 70 |
*/
|
| 71 |
function views_query($base_table = 'node', $base_field = 'nid') {
|
| 72 |
$this->base_table = $base_table; // Predefine these above, for clarity.
|
| 73 |
$this->base_field = $base_field;
|
| 74 |
$this->relationships[$base_table] = array(
|
| 75 |
'link' => NULL,
|
| 76 |
'table' => $base_table,
|
| 77 |
'alias' => $base_table,
|
| 78 |
'base' => $base_table
|
| 79 |
);
|
| 80 |
|
| 81 |
// init the table queue with our primary table.
|
| 82 |
$this->table_queue[$base_table] = array(
|
| 83 |
'alias' => $base_table,
|
| 84 |
'table' => $base_table,
|
| 85 |
'relationship' => $base_table,
|
| 86 |
'join' => NULL,
|
| 87 |
);
|
| 88 |
|
| 89 |
// init the tables with our primary table
|
| 90 |
$this->tables[$base_table][$base_table] = array(
|
| 91 |
'count' => 1,
|
| 92 |
'alias' => $base_table,
|
| 93 |
);
|
| 94 |
|
| 95 |
if ($base_field) {
|
| 96 |
$this->fields[$base_field] = array(
|
| 97 |
'table' => $base_table,
|
| 98 |
'field' => $base_field,
|
| 99 |
'alias' => $base_field,
|
| 100 |
);
|
| 101 |
}
|
| 102 |
|
| 103 |
$this->count_field = array(
|
| 104 |
'table' => $base_table,
|
| 105 |
'field' => $base_field,
|
| 106 |
'alias' => $base_field,
|
| 107 |
'count' => TRUE,
|
| 108 |
);
|
| 109 |
}
|
| 110 |
|
| 111 |
// ----------------------------------------------------------------
|
| 112 |
// Utility methods to set flags and data.
|
| 113 |
|
| 114 |
/**
|
| 115 |
* Set the base field to be distinct.
|
| 116 |
*/
|
| 117 |
function set_distinct($value = TRUE) {
|
| 118 |
if (!(isset($this->no_distinct) && $value)) {
|
| 119 |
// Hack in a check to see if node_access is going to add its own DISTINCT
|
| 120 |
// and break this query.
|
| 121 |
// See http://drupal.org/node/284392 for the core bug.
|
| 122 |
//
|
| 123 |
// We want to bypass views distinct code if this bug will be triggered so
|
| 124 |
// if the base table is node and there is a node access module, bypass
|
| 125 |
// views distinct code.
|
| 126 |
if ($this->base_table != 'node' || !node_access_view_all_nodes()) {
|
| 127 |
$this->distinct = FALSE;
|
| 128 |
}
|
| 129 |
else {
|
| 130 |
$this->distinct = $value;
|
| 131 |
}
|
| 132 |
}
|
| 133 |
}
|
| 134 |
|
| 135 |
/**
|
| 136 |
* Set what field the query will count() on for paging.
|
| 137 |
*/
|
| 138 |
function set_count_field($table, $field, $alias = NULL) {
|
| 139 |
if (empty($alias)) {
|
| 140 |
$alias = $table . '_' . $field;
|
| 141 |
}
|
| 142 |
$this->count_field = array(
|
| 143 |
'table' => $table,
|
| 144 |
'field' => $field,
|
| 145 |
'alias' => $alias,
|
| 146 |
'count' => TRUE,
|
| 147 |
);
|
| 148 |
}
|
| 149 |
|
| 150 |
/**
|
| 151 |
* Set the table header; used for click-sorting because it's needed
|
| 152 |
* info to modify the ORDER BY clause.
|
| 153 |
*/
|
| 154 |
function set_header($header) {
|
| 155 |
$this->header = $header;
|
| 156 |
}
|
| 157 |
|
| 158 |
// ----------------------------------------------------------------
|
| 159 |
// Table/join adding
|
| 160 |
|
| 161 |
/**
|
| 162 |
* A relationship is an alternative endpoint to a series of table
|
| 163 |
* joins. Relationships must be aliases of the primary table and
|
| 164 |
* they must join either to the primary table or to a pre-existing
|
| 165 |
* relationship.
|
| 166 |
*
|
| 167 |
* An example of a relationship would be a nodereference table.
|
| 168 |
* If you have a nodereference named 'book_parent' which links to a
|
| 169 |
* parent node, you could set up a relationship 'node_book_parent'
|
| 170 |
* to 'node'. Then, anything that links to 'node' can link to
|
| 171 |
* 'node_book_parent' instead, thus allowing all properties of
|
| 172 |
* both nodes to be available in the query.
|
| 173 |
*
|
| 174 |
* @param $alias
|
| 175 |
* What this relationship will be called, and is also the alias
|
| 176 |
* for the table.
|
| 177 |
* @param $join
|
| 178 |
* A views_join object (or derived object) to join the alias in.
|
| 179 |
* @param $base
|
| 180 |
* The name of the 'base' table this relationship represents; this
|
| 181 |
* tells the join search which path to attempt to use when finding
|
| 182 |
* the path to this relationship.
|
| 183 |
* @param $link_point
|
| 184 |
* If this relationship links to something other than the primary
|
| 185 |
* table, specify that table here. For example, a 'track' node
|
| 186 |
* might have a relationship to an 'album' node, which might
|
| 187 |
* have a relationship to an 'artist' node.
|
| 188 |
*/
|
| 189 |
function add_relationship($alias, $join, $base, $link_point = NULL) {
|
| 190 |
if (empty($link_point)) {
|
| 191 |
$link_point = $this->base_table;
|
| 192 |
}
|
| 193 |
else if (!array_key_exists($link_point, $this->relationships)) {
|
| 194 |
return FALSE;
|
| 195 |
}
|
| 196 |
|
| 197 |
// Make sure $alias isn't already used; if it, start adding stuff.
|
| 198 |
$alias_base = $alias;
|
| 199 |
$count = 1;
|
| 200 |
while (!empty($this->relationships[$alias])) {
|
| 201 |
$alias = $alias_base . '_' . $count++;
|
| 202 |
}
|
| 203 |
|
| 204 |
// Make sure this join is adjusted for our relationship.
|
| 205 |
if ($link_point && isset($this->relationships[$link_point])) {
|
| 206 |
$join = $this->adjust_join($join, $link_point);
|
| 207 |
}
|
| 208 |
|
| 209 |
// Add the table directly to the queue to avoid accidentally marking
|
| 210 |
// it.
|
| 211 |
$this->table_queue[$alias] = array(
|
| 212 |
'table' => $join->table,
|
| 213 |
'num' => 1,
|
| 214 |
'alias' => $alias,
|
| 215 |
'join' => $join,
|
| 216 |
'relationship' => $link_point,
|
| 217 |
);
|
| 218 |
|
| 219 |
$this->relationships[$alias] = array(
|
| 220 |
'link' => $link_point,
|
| 221 |
'table' => $join->table,
|
| 222 |
'base' => $base,
|
| 223 |
);
|
| 224 |
return $alias;
|
| 225 |
}
|
| 226 |
|
| 227 |
/**
|
| 228 |
* Add a table to the query, ensuring the path exists.
|
| 229 |
*
|
| 230 |
* This function will test to ensure that the path back to the primary
|
| 231 |
* table is valid and exists; if you do not wish for this testing to
|
| 232 |
* occur, use $query->queue_table() instead.
|
| 233 |
*
|
| 234 |
* @param $table
|
| 235 |
* The name of the table to add. It needs to exist in the global table
|
| 236 |
* array.
|
| 237 |
* @param $relationship
|
| 238 |
* An alias of a table; if this is set, the path back to this table will
|
| 239 |
* be tested prior to adding the table, making sure that all intermediary
|
| 240 |
* tables exist and are properly aliased. If set to NULL the path to
|
| 241 |
* the primary table will be ensured. If the path cannot be made, the
|
| 242 |
* table will NOT be added.
|
| 243 |
* @param $join
|
| 244 |
* In some join configurations this table may actually join back through
|
| 245 |
* a different method; this is most likely to be used when tracing
|
| 246 |
* a hierarchy path. (node->parent->parent2->parent3). This parameter
|
| 247 |
* will specify how this table joins if it is not the default.
|
| 248 |
* @param $alias
|
| 249 |
* A specific alias to use, rather than the default alias.
|
| 250 |
*
|
| 251 |
* @return $alias
|
| 252 |
* The alias of the table; this alias can be used to access information
|
| 253 |
* about the table and should always be used to refer to the table when
|
| 254 |
* adding parts to the query. Or FALSE if the table was not able to be
|
| 255 |
* added.
|
| 256 |
*/
|
| 257 |
function add_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
|
| 258 |
if (!$this->ensure_path($table, $relationship, $join)) {
|
| 259 |
return FALSE;
|
| 260 |
}
|
| 261 |
|
| 262 |
if (!$join) {
|
| 263 |
$join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
|
| 264 |
}
|
| 265 |
|
| 266 |
$join = $this->adjust_join($join, $relationship);
|
| 267 |
|
| 268 |
return $this->queue_table($table, $relationship, $join, $alias);
|
| 269 |
}
|
| 270 |
|
| 271 |
/**
|
| 272 |
* Add a table to the query, without ensuring the path.
|
| 273 |
*
|
| 274 |
* This function will test to ensure that the path back to the primary
|
| 275 |
* table is valid and exists; if you do not wish for this testing to
|
| 276 |
* occur, use $query->queue_table() instead.
|
| 277 |
*
|
| 278 |
* @param $table
|
| 279 |
* The name of the table to add. It needs to exist in the global table
|
| 280 |
* array.
|
| 281 |
* @param $relationship
|
| 282 |
* The primary table alias this table is related to. If not set, the
|
| 283 |
* primary table will be used.
|
| 284 |
* @param $join
|
| 285 |
* In some join configurations this table may actually join back through
|
| 286 |
* a different method; this is most likely to be used when tracing
|
| 287 |
* a hierarchy path. (node->parent->parent2->parent3). This parameter
|
| 288 |
* will specify how this table joins if it is not the default.
|
| 289 |
* @param $alias
|
| 290 |
* A specific alias to use, rather than the default alias.
|
| 291 |
*
|
| 292 |
* @return $alias
|
| 293 |
* The alias of the table; this alias can be used to access information
|
| 294 |
* about the table and should always be used to refer to the table when
|
| 295 |
* adding parts to the query. Or FALSE if the table was not able to be
|
| 296 |
* added.
|
| 297 |
*/
|
| 298 |
function queue_table($table, $relationship = NULL, $join = NULL, $alias = NULL) {
|
| 299 |
// If the alias is set, make sure it doesn't already exist.
|
| 300 |
if (isset($this->table_queue[$alias])) {
|
| 301 |
return $alias;
|
| 302 |
}
|
| 303 |
|
| 304 |
if (empty($relationship)) {
|
| 305 |
$relationship = $this->base_table;
|
| 306 |
}
|
| 307 |
|
| 308 |
if (!array_key_exists($relationship, $this->relationships)) {
|
| 309 |
return FALSE;
|
| 310 |
}
|
| 311 |
|
| 312 |
if (!$alias && $join && $relationship && !empty($join->adjusted) && $table != $join->table) {
|
| 313 |
if ($relationship == $this->base_table) {
|
| 314 |
$alias = $table;
|
| 315 |
}
|
| 316 |
else {
|
| 317 |
$alias = $relationship . '_' . $table;
|
| 318 |
}
|
| 319 |
}
|
| 320 |
|
| 321 |
// Check this again to make sure we don't blow up existing aliases for already
|
| 322 |
// adjusted joins.
|
| 323 |
if (isset($this->table_queue[$alias])) {
|
| 324 |
return $alias;
|
| 325 |
}
|
| 326 |
|
| 327 |
$alias = $this->mark_table($table, $relationship, $alias);
|
| 328 |
|
| 329 |
// If no alias is specified, give it the default.
|
| 330 |
if (!isset($alias)) {
|
| 331 |
$alias = $this->tables[$relationship][$table]['alias'] . $this->tables[$relationship][$table]['count'];
|
| 332 |
}
|
| 333 |
|
| 334 |
// If this is a relationship based table, add a marker with
|
| 335 |
// the relationship as a primary table for the alias.
|
| 336 |
if ($table != $alias) {
|
| 337 |
$this->mark_table($alias, $this->base_table, $alias);
|
| 338 |
}
|
| 339 |
|
| 340 |
// If no join is specified, pull it from the table data.
|
| 341 |
if (!isset($join)) {
|
| 342 |
$join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
|
| 343 |
if (empty($join)) {
|
| 344 |
return FALSE;
|
| 345 |
}
|
| 346 |
|
| 347 |
$join = $this->adjust_join($join, $relationship);
|
| 348 |
}
|
| 349 |
|
| 350 |
$this->table_queue[$alias] = array(
|
| 351 |
'table' => $table,
|
| 352 |
'num' => $this->tables[$relationship][$table]['count'],
|
| 353 |
'alias' => $alias,
|
| 354 |
'join' => $join,
|
| 355 |
'relationship' => $relationship,
|
| 356 |
);
|
| 357 |
|
| 358 |
return $alias;
|
| 359 |
}
|
| 360 |
|
| 361 |
function mark_table($table, $relationship, $alias) {
|
| 362 |
// Mark that this table has been added.
|
| 363 |
if (empty($this->tables[$relationship][$table])) {
|
| 364 |
if (!isset($alias)) {
|
| 365 |
$alias = '';
|
| 366 |
if ($relationship != $this->base_table) {
|
| 367 |
// double underscore will help prevent accidental name
|
| 368 |
// space collisions.
|
| 369 |
$alias = $relationship . '__';
|
| 370 |
}
|
| 371 |
$alias .= $table;
|
| 372 |
}
|
| 373 |
$this->tables[$relationship][$table] = array(
|
| 374 |
'count' => 1,
|
| 375 |
'alias' => $alias,
|
| 376 |
);
|
| 377 |
}
|
| 378 |
else {
|
| 379 |
$this->tables[$relationship][$table]['count']++;
|
| 380 |
}
|
| 381 |
|
| 382 |
return $alias;
|
| 383 |
}
|
| 384 |
|
| 385 |
/**
|
| 386 |
* Ensure a table exists in the queue; if it already exists it won't
|
| 387 |
* do anything, but if it doesn't it will add the table queue. It will ensure
|
| 388 |
* a path leads back to the relationship table.
|
| 389 |
*
|
| 390 |
* @param $table
|
| 391 |
* The unaliased name of the table to ensure.
|
| 392 |
* @param $relationship
|
| 393 |
* The relationship to ensure the table links to. Each relationship will
|
| 394 |
* get a unique instance of the table being added. If not specified,
|
| 395 |
* will be the primary table.
|
| 396 |
* @param $join
|
| 397 |
* A views_join object (or derived object) to join the alias in.
|
| 398 |
*
|
| 399 |
* @return
|
| 400 |
* The alias used to refer to this specific table, or NULL if the table
|
| 401 |
* cannot be ensured.
|
| 402 |
*/
|
| 403 |
function ensure_table($table, $relationship = NULL, $join = NULL) {
|
| 404 |
// ensure a relationship
|
| 405 |
if (empty($relationship)) {
|
| 406 |
$relationship = $this->base_table;
|
| 407 |
}
|
| 408 |
|
| 409 |
// If the relationship is the primary table, this actually be a relationship
|
| 410 |
// link back from an alias. We store all aliases along with the primary table
|
| 411 |
// to detect this state, because eventually it'll hit a table we already
|
| 412 |
// have and that's when we want to stop.
|
| 413 |
if ($relationship == $this->base_table && !empty($this->tables[$relationship][$table])) {
|
| 414 |
return $this->tables[$relationship][$table]['alias'];
|
| 415 |
}
|
| 416 |
|
| 417 |
if (!array_key_exists($relationship, $this->relationships)) {
|
| 418 |
return FALSE;
|
| 419 |
}
|
| 420 |
|
| 421 |
if ($table == $this->relationships[$relationship]['base']) {
|
| 422 |
return $relationship;
|
| 423 |
}
|
| 424 |
|
| 425 |
// If we do not have join info, fetch it.
|
| 426 |
if (!isset($join)) {
|
| 427 |
$join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
|
| 428 |
}
|
| 429 |
|
| 430 |
// If it can't be fetched, this won't work.
|
| 431 |
if (empty($join)) {
|
| 432 |
return;
|
| 433 |
}
|
| 434 |
|
| 435 |
// Adjust this join for the relationship, which will ensure that the 'base'
|
| 436 |
// table it links to is correct. Tables adjoined to a relationship
|
| 437 |
// join to a link point, not the base table.
|
| 438 |
$join = $this->adjust_join($join, $relationship);
|
| 439 |
|
| 440 |
if ($this->ensure_path($table, $relationship, $join)) {
|
| 441 |
// Attempt to eliminate redundant joins. If this table's
|
| 442 |
// relationship and join exactly matches an existing table's
|
| 443 |
// relationship and join, we do not have to join to it again;
|
| 444 |
// just return the existing table's alias. See
|
| 445 |
// http://groups.drupal.org/node/11288 for details.
|
| 446 |
//
|
| 447 |
// This can be done safely here but not lower down in
|
| 448 |
// queue_table(), because queue_table() is also used by
|
| 449 |
// add_table() which requires the ability to intentionally add
|
| 450 |
// the same table with the same join multiple times. For
|
| 451 |
// example, a view that filters on 3 taxonomy terms using AND
|
| 452 |
// needs to join term_data 3 times with the same join.
|
| 453 |
|
| 454 |
// scan through the table queue to see if a matching join and
|
| 455 |
// relationship exists. If so, use it instead of this join.
|
| 456 |
|
| 457 |
// TODO: Scanning through $this->table_queue results in an
|
| 458 |
// O(N^2) algorithm, and this code runs every time the view is
|
| 459 |
// instantiated (Views 2 does not currently cache queries).
|
| 460 |
// There are a couple possible "improvements" but we should do
|
| 461 |
// some performance testing before picking one.
|
| 462 |
foreach ($this->table_queue as $queued_table) {
|
| 463 |
// In PHP 4 and 5, the == operation returns TRUE for two objects
|
| 464 |
// if they are instances of the same class and have the same
|
| 465 |
// attributes and values.
|
| 466 |
if ($queued_table['relationship'] == $relationship && $queued_table['join'] == $join) {
|
| 467 |
return $queued_table['alias'];
|
| 468 |
}
|
| 469 |
}
|
| 470 |
|
| 471 |
return $this->queue_table($table, $relationship, $join);
|
| 472 |
}
|
| 473 |
}
|
| 474 |
|
| 475 |
/**
|
| 476 |
* Make sure that the specified table can be properly linked to the primary
|
| 477 |
* table in the JOINs. This function uses recursion. If the tables
|
| 478 |
* needed to complete the path back to the primary table are not in the
|
| 479 |
* query they will be added, but additional copies will NOT be added
|
| 480 |
* if the table is already there.
|
| 481 |
*/
|
| 482 |
function ensure_path($table, $relationship = NULL, $join = NULL, $traced = array(), $add = array()) {
|
| 483 |
if (!isset($relationship)) {
|
| 484 |
$relationship = $this->base_table;
|
| 485 |
}
|
| 486 |
|
| 487 |
if (!array_key_exists($relationship, $this->relationships)) {
|
| 488 |
return FALSE;
|
| 489 |
}
|
| 490 |
|
| 491 |
// If we do not have join info, fetch it.
|
| 492 |
if (!isset($join)) {
|
| 493 |
$join = $this->get_join_data($table, $this->relationships[$relationship]['base']);
|
| 494 |
}
|
| 495 |
|
| 496 |
// If it can't be fetched, this won't work.
|
| 497 |
if (empty($join)) {
|
| 498 |
return FALSE;
|
| 499 |
}
|
| 500 |
|
| 501 |
// Does a table along this path exist?
|
| 502 |
if (isset($this->tables[$relationship][$table]) ||
|
| 503 |
($join && $join->left_table == $relationship) ||
|
| 504 |
($join && $join->left_table == $this->relationships[$relationship]['table'])) {
|
| 505 |
|
| 506 |
// Make sure that we're linking to the correct table for our relationship.
|
| 507 |
foreach (array_reverse($add) as $table => $path_join) {
|
| 508 |
$this->queue_table($table, $relationship, $this->adjust_join($path_join, $relationship));
|
| 509 |
}
|
| 510 |
return TRUE;
|
| 511 |
}
|
| 512 |
|
| 513 |
// Have we been this way?
|
| 514 |
if (isset($traced[$join->left_table])) {
|
| 515 |
// we looped. Broked.
|
| 516 |
return FALSE;
|
| 517 |
}
|
| 518 |
|
| 519 |
// Do we have to add this table?
|
| 520 |
$left_join = $this->get_join_data($join->left_table, $this->relationships[$relationship]['base']);
|
| 521 |
if (!isset($this->tables[$relationship][$join->left_table])) {
|
| 522 |
$add[$join->left_table] = $left_join;
|
| 523 |
}
|
| 524 |
|
| 525 |
// Keep looking.
|
| 526 |
$traced[$join->left_table] = TRUE;
|
| 527 |
return $this->ensure_path($join->left_table, $relationship, $left_join, $traced, $add);
|
| 528 |
}
|
| 529 |
|
| 530 |
/**
|
| 531 |
* Fix a join to adhere to the proper relationship; the left table can vary
|
| 532 |
* based upon what relationship items are joined in on.
|
| 533 |
*/
|
| 534 |
function adjust_join($join, $relationship) {
|
| 535 |
if (!empty($join->adjusted)) {
|
| 536 |
return $join;
|
| 537 |
}
|
| 538 |
|
| 539 |
if (empty($relationship) || empty($this->relationships[$relationship])) {
|
| 540 |
return $join;
|
| 541 |
}
|
| 542 |
|
| 543 |
// Adjusts the left table for our relationship.
|
| 544 |
if ($relationship != $this->base_table) {
|
| 545 |
// If we're linking to the primary table, the relationship to use will
|
| 546 |
// be the prior relationship. Unless it's a direct link.
|
| 547 |
|
| 548 |
// Safety! Don't modify an original here.
|
| 549 |
$join = drupal_clone($join);
|
| 550 |
|
| 551 |
// Do we need to try to ensure a path?
|
| 552 |
if ($join->left_table != $this->relationships[$relationship]['table'] &&
|
| 553 |
$join->left_table != $this->relationships[$relationship]['base'] &&
|
| 554 |
!isset($this->tables[$relationship][$join->left_table]['alias'])) {
|
| 555 |
$this->ensure_table($join->left_table, $relationship);
|
| 556 |
}
|
| 557 |
|
| 558 |
// First, if this is our link point/anchor table, just use the relationship
|
| 559 |
if ($join->left_table == $this->relationships[$relationship]['table']) {
|
| 560 |
$join->left_table = $relationship;
|
| 561 |
}
|
| 562 |
// then, try the base alias.
|
| 563 |
else if (isset($this->tables[$relationship][$join->left_table]['alias'])) {
|
| 564 |
$join->left_table = $this->tables[$relationship][$join->left_table]['alias'];
|
| 565 |
}
|
| 566 |
// But if we're already looking at an alias, use that instead.
|
| 567 |
else if (isset($this->table_queue[$relationship]['alias'])) {
|
| 568 |
$join->left_table = $this->table_queue[$relationship]['alias'];
|
| 569 |
}
|
| 570 |
}
|
| 571 |
|
| 572 |
$join->adjusted = TRUE;
|
| 573 |
return $join;
|
| 574 |
}
|
| 575 |
|
| 576 |
/**
|
| 577 |
* Retrieve join data from the larger join data cache.
|
| 578 |
*
|
| 579 |
* @param $table
|
| 580 |
* The table to get the join information for.
|
| 581 |
* @param $base_table
|
| 582 |
* The path we're following to get this join.
|
| 583 |
*
|
| 584 |
* @return
|
| 585 |
* A views_join object or child object, if one exists.
|
| 586 |
*/
|
| 587 |
function get_join_data($table, $base_table) {
|
| 588 |
// Check to see if we're linking to a known alias. If so, get the real
|
| 589 |
// table's data instead.
|
| 590 |
if (!empty($this->table_queue[$table])) {
|
| 591 |
$table = $this->table_queue[$table]['table'];
|
| 592 |
}
|
| 593 |
return views_get_table_join($table, $base_table);
|
| 594 |
|
| 595 |
}
|
| 596 |
|
| 597 |
/**
|
| 598 |
* Get the information associated with a table.
|
| 599 |
*
|
| 600 |
* If you need the alias of a table with a particular relationship, use
|
| 601 |
* ensure_table().
|
| 602 |
*/
|
| 603 |
function get_table_info($table) {
|
| 604 |
if (!empty($this->table_queue[$table])) {
|
| 605 |
return $this->table_queue[$table];
|
| 606 |
}
|
| 607 |
|
| 608 |
// In rare cases we might *only* have aliased versions of the table.
|
| 609 |
if (!empty($this->tables[$this->base_table][$table])) {
|
| 610 |
$alias = $this->tables[$this->base_table][$table]['alias'];
|
| 611 |
if (!empty($this->table_queue[$alias])) {
|
| 612 |
return $this->table_queue[$alias];
|
| 613 |
}
|
| 614 |
}
|
| 615 |
}
|
| 616 |
|
| 617 |
/**
|
| 618 |
* Add a field to the query table, possibly with an alias. This will
|
| 619 |
* automatically call ensure_table to make sure the required table
|
| 620 |
* exists, *unless* $table is unset.
|
| 621 |
*
|
| 622 |
* @param $table
|
| 623 |
* The table this field is attached to. If NULL, it is assumed this will
|
| 624 |
* be a formula; otherwise, ensure_table is used to make sure the
|
| 625 |
* table exists.
|
| 626 |
* @param $field
|
| 627 |
* The name of the field to add. This may be a real field or a formula.
|
| 628 |
* @param $alias
|
| 629 |
* The alias to create. If not specified, the alias will be $table_$field
|
| 630 |
* unless $table is NULL. When adding formulae, it is recommended that an
|
| 631 |
* alias be used.
|
| 632 |
*
|
| 633 |
* @return $name
|
| 634 |
* The name that this field can be referred to as. Usually this is the alias.
|
| 635 |
*/
|
| 636 |
function add_field($table, $field, $alias = '', $params = NULL) {
|
| 637 |
// We check for this specifically because it gets a special alias.
|
| 638 |
if ($table == $this->base_table && $field == $this->base_field && empty($alias)) {
|
| 639 |
$alias = $this->base_field;
|
| 640 |
}
|
| 641 |
|
| 642 |
if ($table && empty($this->table_queue[$table])) {
|
| 643 |
$this->ensure_table($table);
|
| 644 |
}
|
| 645 |
|
| 646 |
if (!$alias && $table) {
|
| 647 |
$alias = $table . '_' . $field;
|
| 648 |
}
|
| 649 |
|
| 650 |
$name = $alias ? $alias : $field;
|
| 651 |
|
| 652 |
// @todo FIXME -- $alias, then $name is inconsistent
|
| 653 |
if (empty($this->fields[$alias])) {
|
| 654 |
$this->fields[$name] = array(
|
| 655 |
'field' => $field,
|
| 656 |
'table' => $table,
|
| 657 |
'alias' => $alias,
|
| 658 |
);
|
| 659 |
}
|
| 660 |
|
| 661 |
foreach ((array)$params as $key => $value) {
|
| 662 |
$this->fields[$name][$key] = $value;
|
| 663 |
}
|
| 664 |
|
| 665 |
return $name;
|
| 666 |
}
|
| 667 |
|
| 668 |
/**
|
| 669 |
* Remove all fields that may've been added; primarily used for summary
|
| 670 |
* mode where we're changing the query because we didn't get data we needed.
|
| 671 |
*/
|
| 672 |
function clear_fields() {
|
| 673 |
$this->fields = array();
|
| 674 |
}
|
| 675 |
|
| 676 |
/**
|
| 677 |
* Create a new grouping for the WHERE or HAVING clause.
|
| 678 |
*
|
| 679 |
* @param $type
|
| 680 |
* Either 'AND' or 'OR'. All items within this group will be added
|
| 681 |
* to the WHERE clause with this logical operator.
|
| 682 |
* @param $group
|
| 683 |
* An ID to use for this group. If unspecified, an ID will be generated.
|
| 684 |
* @param $where
|
| 685 |
* 'where' or 'having'.
|
| 686 |
*
|
| 687 |
* @return $group
|
| 688 |
* The group ID generated.
|
| 689 |
*/
|
| 690 |
function set_where_group($type = 'AND', $group = NULL, $where = 'where') {
|
| 691 |
// Set an alias.
|
| 692 |
$groups = &$this->$where;
|
| 693 |
|
| 694 |
if (!isset($group)) {
|
| 695 |
$group = empty($groups) ? 1 : max(array_keys($groups)) + 1;
|
| 696 |
}
|
| 697 |
|
| 698 |
// Create an empty group
|
| 699 |
if (empty($groups[$group])) {
|
| 700 |
$groups[$group] = array('clauses' => array(), 'args' => array());
|
| 701 |
}
|
| 702 |
|
| 703 |
$groups[$group]['type'] = strtoupper($type);
|
| 704 |
return $group;
|
| 705 |
}
|
| 706 |
|
| 707 |
/**
|
| 708 |
* Control how all WHERE and HAVING groups are put together.
|
| 709 |
*
|
| 710 |
* @param $type
|
| 711 |
* Either 'AND' or 'OR'
|
| 712 |
*/
|
| 713 |
function set_group_operator($type = 'AND') {
|
| 714 |
$this->group_operator = strtoupper($type);
|
| 715 |
}
|
| 716 |
|
| 717 |
/**
|
| 718 |
* Add a simple WHERE clause to the query. The caller is responsible for
|
| 719 |
* ensuring that all fields are fully qualified (TABLE.FIELD) and that
|
| 720 |
* the table already exists in the query.
|
| 721 |
*
|
| 722 |
* @param $group
|
| 723 |
* The WHERE group to add these to; groups are used to create AND/OR
|
| 724 |
* sections. Groups cannot be nested. Use 0 as the default group.
|
| 725 |
* If the group does not yet exist it will be created as an AND group.
|
| 726 |
* @param $clause
|
| 727 |
* The actual clause to add. When adding a where clause it is important
|
| 728 |
* that all tables are addressed by the alias provided by add_table or
|
| 729 |
* ensure_table and that all fields are addressed by their alias wehn
|
| 730 |
* possible. Please use %d and %s for arguments.
|
| 731 |
* @param ...
|
| 732 |
* A number of arguments as used in db_query(). May be many args or one
|
| 733 |
* array full of args.
|
| 734 |
*/
|
| 735 |
function add_where($group, $clause) {
|
| 736 |
$args = func_get_args();
|
| 737 |
array_shift($args); // ditch $group
|
| 738 |
array_shift($args); // ditch $clause
|
| 739 |
|
| 740 |
// Expand an array of args if it came in.
|
| 741 |
if (count($args) == 1 && is_array(reset($args))) {
|
| 742 |
$args = current($args);
|
| 743 |
}
|
| 744 |
|
| 745 |
// Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
|
| 746 |
// the default group.
|
| 747 |
if (empty($group)) {
|
| 748 |
$group = 0;
|
| 749 |
}
|
| 750 |
|
| 751 |
// Check for a group.
|
| 752 |
if (!isset($this->where[$group])) {
|
| 753 |
$this->set_where_group('AND', $group);
|
| 754 |
}
|
| 755 |
|
| 756 |
// Add the clause and the args.
|
| 757 |
if (is_array($args)) {
|
| 758 |
$this->where[$group]['clauses'][] = $clause;
|
| 759 |
// we use array_values() here to prevent array_merge errors as keys from multiple
|
| 760 |
// sources occasionally collide.
|
| 761 |
$this->where[$group]['args'] = array_merge($this->where[$group]['args'], array_values($args));
|
| 762 |
}
|
| 763 |
}
|
| 764 |
|
| 765 |
/**
|
| 766 |
* Add a simple HAVING clause to the query. The caller is responsible for
|
| 767 |
* ensuring that all fields are fully qualified (TABLE.FIELD) and that
|
| 768 |
* the table and an appropriate GROUP BY already exist in the query.
|
| 769 |
*
|
| 770 |
* @param $group
|
| 771 |
* The HAVING group to add these to; groups are used to create AND/OR
|
| 772 |
* sections. Groups cannot be nested. Use 0 as the default group.
|
| 773 |
* If the group does not yet exist it will be created as an AND group.
|
| 774 |
* @param $clause
|
| 775 |
* The actual clause to add. When adding a having clause it is important
|
| 776 |
* that all tables are addressed by the alias provided by add_table or
|
| 777 |
* ensure_table and that all fields are addressed by their alias wehn
|
| 778 |
* possible. Please use %d and %s for arguments.
|
| 779 |
* @param ...
|
| 780 |
* A number of arguments as used in db_query(). May be many args or one
|
| 781 |
* array full of args.
|
| 782 |
*/
|
| 783 |
function add_having($group, $clause) {
|
| 784 |
$args = func_get_args();
|
| 785 |
array_shift($args); // ditch $group
|
| 786 |
array_shift($args); // ditch $clause
|
| 787 |
|
| 788 |
// Expand an array of args if it came in.
|
| 789 |
if (count($args) == 1 && is_array(reset($args))) {
|
| 790 |
$args = current($args);
|
| 791 |
}
|
| 792 |
|
| 793 |
// Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
|
| 794 |
// the default group.
|
| 795 |
if (empty($group)) {
|
| 796 |
$group = 0;
|
| 797 |
}
|
| 798 |
|
| 799 |
// Check for a group.
|
| 800 |
if (!isset($this->having[$group])) {
|
| 801 |
$this->set_where_group('AND', $group, 'having');
|
| 802 |
}
|
| 803 |
|
| 804 |
// Add the clause and the args.
|
| 805 |
if (is_array($args)) {
|
| 806 |
$this->having[$group]['clauses'][] = $clause;
|
| 807 |
$this->having[$group]['args'] = array_merge($this->having[$group]['args'], array_values($args));
|
| 808 |
}
|
| 809 |
}
|
| 810 |
|
| 811 |
/**
|
| 812 |
* Add an ORDER BY clause to the query.
|
| 813 |
*
|
| 814 |
* @param $table
|
| 815 |
* The table this field is part of. If a formula, enter NULL.
|
| 816 |
* @param $field
|
| 817 |
* The field or formula to sort on. If already a field, enter NULL
|
| 818 |
* and put in the alias.
|
| 819 |
* @param $order
|
| 820 |
* Either ASC or DESC.
|
| 821 |
* @param $alias
|
| 822 |
* The alias to add the field as. In SQL, all fields in the order by
|
| 823 |
* must also be in the SELECT portion. If an $alias isn't specified
|
| 824 |
* one will be generated for from the $field; however, if the
|
| 825 |
* $field is a formula, this alias will likely fail.
|
| 826 |
*/
|
| 827 |
function add_orderby($table, $field, $order, $alias = '') {
|
| 828 |
if ($table) {
|
| 829 |
$this->ensure_table($table);
|
| 830 |
}
|
| 831 |
|
| 832 |
// Only fill out this aliasing if there is a table;
|
| 833 |
// otherwise we assume it is a formula.
|
| 834 |
if (!$alias && $table) {
|
| 835 |
$as = $table . '_' . $field;
|
| 836 |
}
|
| 837 |
else {
|
| 838 |
$as = $alias;
|
| 839 |
}
|
| 840 |
|
| 841 |
if ($field) {
|
| 842 |
$this->add_field($table, $field, $as);
|
| 843 |
}
|
| 844 |
|
| 845 |
$this->orderby[] = "$as " . strtoupper($order);
|
| 846 |
|
| 847 |
// If grouping, all items in the order by must also be in the
|
| 848 |
// group by clause. Check $table to ensure that this is not a
|
| 849 |
// formula.
|
| 850 |
if ($this->groupby && $table) {
|
| 851 |
$this->add_groupby($as);
|
| 852 |
}
|
| 853 |
}
|
| 854 |
|
| 855 |
/**
|
| 856 |
* Add a simple GROUP BY clause to the query. The caller is responsible
|
| 857 |
* for ensuring that the fields are fully qualified and the table is properly
|
| 858 |
* added.
|
| 859 |
*/
|
| 860 |
function add_groupby($clause) {
|
| 861 |
// Only add it if it's not already in there.
|
| 862 |
if (!in_array($clause, $this->groupby)) {
|
| 863 |
$this->groupby[] = $clause;
|
| 864 |
}
|
| 865 |
}
|
| 866 |
|
| 867 |
/**
|
| 868 |
* Construct the "WHERE" or "HAVING" part of the query.
|
| 869 |
*
|
| 870 |
* @param $where
|
| 871 |
* 'where' or 'having'.
|
| 872 |
*/
|
| 873 |
function condition_sql($where = 'where') {
|
| 874 |
$clauses = array();
|
| 875 |
foreach ($this->$where as $group => $info) {
|
| 876 |
$clause = implode(") " . $info['type'] . " (", $info['clauses']);
|
| 877 |
if (count($info['clauses']) > 1) {
|
| 878 |
$clause = '(' . $clause . ')';
|
| 879 |
}
|
| 880 |
$clauses[] = $clause;
|
| 881 |
}
|
| 882 |
|
| 883 |
if ($clauses) {
|
| 884 |
$keyword = drupal_strtoupper($where);
|
| 885 |
if (count($clauses) > 1) {
|
| 886 |
return "$keyword (" . implode(")\n " . $this->group_operator . ' (', $clauses) . ")\n";
|
| 887 |
}
|
| 888 |
else {
|
| 889 |
return "$keyword " . array_shift($clauses) . "\n";
|
| 890 |
}
|
| 891 |
}
|
| 892 |
return "";
|
| 893 |
}
|
| 894 |
|
| 895 |
/**
|
| 896 |
* Generate a query and a countquery from all of the information supplied
|
| 897 |
* to the object.
|
| 898 |
*
|
| 899 |
* @param $get_count
|
| 900 |
* Provide a countquery if this is true, otherwise provide a normal query.
|
| 901 |
*/
|
| 902 |
function query($get_count = FALSE) {
|
| 903 |
// Check query distinct value.
|
| 904 |
if (empty($this->no_distinct) && $this->distinct && !empty($this->fields)) {
|
| 905 |
if (!empty($this->fields[$this->base_field])) {
|
| 906 |
$this->fields[$this->base_field]['distinct'] = TRUE;
|
| 907 |
$this->add_groupby($this->base_field);
|
| 908 |
}
|
| 909 |
}
|
| 910 |
|
| 911 |
/**
|
| 912 |
* An optimized count query includes just the base field instead of all the fields.
|
| 913 |
* Determine of this query qualifies by checking for a groupby or distinct.
|
| 914 |
*/
|
| 915 |
$fields_array = $this->fields;
|
| 916 |
if ($get_count && !$this->groupby) {
|
| 917 |
foreach ($fields_array as $field) {
|
| 918 |
if (!empty($field['distinct'])) {
|
| 919 |
$get_count_optimized = FALSE;
|
| 920 |
break;
|
| 921 |
}
|
| 922 |
}
|
| 923 |
}
|
| 924 |
else {
|
| 925 |
$get_count_optimized = FALSE;
|
| 926 |
}
|
| 927 |
if (!isset($get_count_optimized)) {
|
| 928 |
$get_count_optimized = TRUE;
|
| 929 |
}
|
| 930 |
|
| 931 |
$joins = $fields = $where = $having = $orderby = $groupby = '';
|
| 932 |
// Add all the tables to the query via joins. We assume all LEFT joins.
|
| 933 |
foreach ($this->table_queue as $table) {
|
| 934 |
if (is_object($table['join'])) {
|
| 935 |
$joins .= $table['join']->join($table, $this) . "\n";
|
| 936 |
}
|
| 937 |
}
|
| 938 |
|
| 939 |
$has_aggregate = FALSE;
|
| 940 |
$non_aggregates = array();
|
| 941 |
|
| 942 |
foreach ($fields_array as $field) {
|
| 943 |
if ($fields) {
|
| 944 |
$fields .= ",\n ";
|
| 945 |
}
|
| 946 |
$string = '';
|
| 947 |
if (!empty($field['table'])) {
|
| 948 |
$string .= $field['table'] . '.';
|
| 949 |
}
|
| 950 |
$string .= $field['field'];
|
| 951 |
|
| 952 |
// store for use with non-aggregates below
|
| 953 |
$fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
|
| 954 |
|
| 955 |
if (!empty($field['distinct'])) {
|
| 956 |
$string = "DISTINCT($string)";
|
| 957 |
}
|
| 958 |
if (!empty($field['count'])) {
|
| 959 |
$string = "COUNT($string)";
|
| 960 |
$has_aggregate = TRUE;
|
| 961 |
}
|
| 962 |
else if (!empty($field['aggregate'])) {
|
| 963 |
$has_aggregate = TRUE;
|
| 964 |
}
|
| 965 |
elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
|
| 966 |
$string = $GLOBALS['db_type'] == 'pgsql' ? "FIRST($string)" : $string;
|
| 967 |
}
|
| 968 |
else {
|
| 969 |
$non_aggregates[] = $fieldname;
|
| 970 |
}
|
| 971 |
if ($field['alias']) {
|
| 972 |
$string .= " AS $field[alias]";
|
| 973 |
}
|
| 974 |
$fields .= $string;
|
| 975 |
|
| 976 |
if ($get_count_optimized) {
|
| 977 |
// We only want the first field in this case.
|
| 978 |
break;
|
| 979 |
}
|
| 980 |
}
|
| 981 |
|
| 982 |
if ($has_aggregate || $this->groupby) {
|
| 983 |
$groupby = "GROUP BY " . implode(', ', array_unique(array_merge($this->groupby, $non_aggregates))) . "\n";
|
| 984 |
if ($this->having) {
|
| 985 |
$having = $this->condition_sql('having');
|
| 986 |
}
|
| 987 |
}
|
| 988 |
|
| 989 |
if (!$get_count_optimized) {
|
| 990 |
// we only add the groupby if we're not counting.
|
| 991 |
if ($this->orderby) {
|
| 992 |
$orderby = "ORDER BY " . implode(', ', $this->orderby) . "\n";
|
| 993 |
}
|
| 994 |
}
|
| 995 |
|
| 996 |
$where = $this->condition_sql();
|
| 997 |
|
| 998 |
$query = "SELECT $fields\n FROM {" . $this->base_table . "} $this->base_table \n$joins $where $groupby $having $orderby";
|
| 999 |
|
| 1000 |
$replace = array('>' => '>', '<' => '<');
|
| 1001 |
$query = strtr($query, $replace);
|
| 1002 |
|
| 1003 |
return $query;
|
| 1004 |
}
|
| 1005 |
|
| 1006 |
/**
|
| 1007 |
* Get the arguments attached to the WHERE and HAVING clauses of this query.
|
| 1008 |
*/
|
| 1009 |
function get_where_args() {
|
| 1010 |
$args = array();
|
| 1011 |
foreach ($this->where as $group => $where) {
|
| 1012 |
$args = array_merge($args, $where['args']);
|
| 1013 |
}
|
| 1014 |
foreach ($this->having as $group => $having) {
|
| 1015 |
$args = array_merge($args, $having['args']);
|
| 1016 |
}
|
| 1017 |
return $args;
|
| 1018 |
}
|
| 1019 |
}
|
| 1020 |
|