Simpletest Coverage - includes/database/select.inc

1 <?php
2 // $Id: select.inc,v 1.19 2009/06/06 16:57:52 webchick Exp $
3
4 /**
5 * @ingroup database
6 * @{
7 */
8
9 /**
10 * Interface for extendable query objects.
11 *
12 * "Extenders" follow the "Decorator" OOP design pattern. That is, they wrap
13 * and "decorate" another object. In our case, they implement the same interface
14 * as select queries and wrap a select query, to which they delegate almost all
15 * operations. Subclasses of this class may implement additional methods or
16 * override existing methods as appropriate. Extenders may also wrap other
17 * extender objects, allowing for arbitrarily complex "enhanced" queries.
18 */
19 interface QueryExtendableInterface {
20
21 /**
22 * Enhance this object by wrapping it in an extender object.
23 *
24 * @param $extender_name
25 * The base name of the extending class. The base name will be checked
26 * against the current database connection to allow driver-specific subclasses
27 * as well, using the same logic as the query objects themselves. For example,
28 * PagerDefault_mysql is the MySQL-specific override for PagerDefault.
29 * @return
30 * The extender object, which now contains a reference to this object.
31 */
32 public function extend($extender_name);
33 }
34
35 /**
36 * Interface definition for a Select Query object.
37 */
38 interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableInterface, QueryExtendableInterface {
39
40 /* Alter accessors to expose the query data to alter hooks. */
41
42 /**
43 * Returns a reference to the fields array for this query.
44 *
45 * Because this method returns by reference, alter hooks may edit the fields
46 * array directly to make their changes. If just adding fields, however, the
47 * use of addField() is preferred.
48 *
49 * Note that this method must be called by reference as well:
50 *
51 * @code
52 * $fields =& $query->getFields();
53 * @endcode
54 *
55 * @return
56 * A reference to the fields array structure.
57 */
58 public function &getFields();
59
60 /**
61 * Returns a reference to the expressions array for this query.
62 *
63 * Because this method returns by reference, alter hooks may edit the expressions
64 * array directly to make their changes. If just adding expressions, however, the
65 * use of addExpression() is preferred.
66 *
67 * Note that this method must be called by reference as well:
68 *
69 * @code
70 * $fields =& $query->getExpressions();
71 * @endcode
72 *
73 * @return
74 * A reference to the expression array structure.
75 */
76 public function &getExpressions();
77
78 /**
79 * Returns a reference to the order by array for this query.
80 *
81 * Because this method returns by reference, alter hooks may edit the order-by
82 * array directly to make their changes. If just adding additional ordering
83 * fields, however, the use of orderBy() is preferred.
84 *
85 * Note that this method must be called by reference as well:
86 *
87 * @code
88 * $fields =& $query->getOrderBy();
89 * @endcode
90 *
91 * @return
92 * A reference to the expression array structure.
93 */
94 public function &getOrderBy();
95
96 /**
97 * Returns a reference to the tables array for this query.
98 *
99 * Because this method returns by reference, alter hooks may edit the tables
100 * array directly to make their changes. If just adding tables, however, the
101 * use of the join() methods is preferred.
102 *
103 * Note that this method must be called by reference as well:
104 *
105 * @code
106 * $fields =& $query->getTables();
107 * @endcode
108 *
109 * @return
110 * A reference to the tables array structure.
111 */
112 public function &getTables();
113
114 /**
115 * Compiles and returns an associative array of the arguments for this prepared statement.
116 *
117 * @return
118 * An associative array of all placeholder arguments for this query.
119 */
120 public function getArguments();
121
122 /* Query building operations */
123
124 /**
125 * Sets this query to be DISTINCT.
126 *
127 * @param $distinct
128 * TRUE to flag this query DISTINCT, FALSE to disable it.
129 * @return
130 * The called object.
131 */
132 public function distinct($distinct = TRUE);
133
134 /**
135 * Adds a field to the list to be SELECTed.
136 *
137 * @param $table_alias
138 * The name of the table from which the field comes, as an alias. Generally
139 * you will want to use the return value of join() here to ensure that it is
140 * valid.
141 * @param $field
142 * The name of the field.
143 * @param $alias
144 * The alias for this field. If not specified, one will be generated
145 * automatically based on the $table_alias and $field. The alias will be
146 * checked for uniqueness, so the requested alias may not be the alias
147 * that is assigned in all cases.
148 * @return
149 * The unique alias that was assigned for this field.
150 */
151 public function addField($table_alias, $field, $alias = NULL);
152
153 /**
154 * Add multiple fields from the same table to be SELECTed.
155 *
156 * This method does not return the aliases set for the passed fields. In the
157 * majority of cases that is not a problem, as the alias will be the field
158 * name. However, if you do need to know the alias you can call getFields()
159 * and examine the result to determine what alias was created. Alternatively,
160 * simply use addField() for the few fields you care about and this method for
161 * the rest.
162 *
163 * @param $table_alias
164 * The name of the table from which the field comes, as an alias. Generally
165 * you will want to use the return value of join() here to ensure that it is
166 * valid.
167 * @param $fields
168 * An indexed array of fields present in the specified table that should be
169 * included in this query. If not specified, $table_alias.* will be generated
170 * without any aliases.
171 * @return
172 * The called object.
173 */
174 public function fields($table_alias, array $fields = array());
175
176 /**
177 * Adds an expression to the list of "fields" to be SELECTed.
178 *
179 * An expression can be any arbitrary string that is valid SQL. That includes
180 * various functions, which may in some cases be database-dependent. This
181 * method makes no effort to correct for database-specific functions.
182 *
183 * @param $expression
184 * The expression string. May contain placeholders.
185 * @param $alias
186 * The alias for this expression. If not specified, one will be generated
187 * automatically in the form "expression_#". The alias will be checked for
188 * uniqueness, so the requested alias may not be the alias that is assigned
189 * in all cases.
190 * @param $arguments
191 * Any placeholder arguments needed for this expression.
192 * @return
193 * The unique alias that was assigned for this expression.
194 */
195 public function addExpression($expression, $alias = NULL, $arguments = array());
196
197 /**
198 * Default Join against another table in the database.
199 *
200 * This method is a convenience method for innerJoin().
201 *
202 * @param $table
203 * The table against which to join.
204 * @param $alias
205 * The alias for the table. In most cases this should be the first letter
206 * of the table, or the first letter of each "word" in the table.
207 * @param $condition
208 * The condition on which to join this table. If the join requires values,
209 * this clause should use a named placeholder and the value or values to
210 * insert should be passed in the 4th parameter. For the first table joined
211 * on a query, this value is ignored as the first table is taken as the base
212 * table.
213 * @param $arguments
214 * An array of arguments to replace into the $condition of this join.
215 * @return
216 * The unique alias that was assigned for this table.
217 */
218 public function join($table, $alias = NULL, $condition = NULL, $arguments = array());
219
220 /**
221 * Inner Join against another table in the database.
222 *
223 * @param $table
224 * The table against which to join.
225 * @param $alias
226 * The alias for the table. In most cases this should be the first letter
227 * of the table, or the first letter of each "word" in the table.
228 * @param $condition
229 * The condition on which to join this table. If the join requires values,
230 * this clause should use a named placeholder and the value or values to
231 * insert should be passed in the 4th parameter. For the first table joined
232 * on a query, this value is ignored as the first table is taken as the base
233 * table.
234 * @param $arguments
235 * An array of arguments to replace into the $condition of this join.
236 * @return
237 * The unique alias that was assigned for this table.
238 */
239 public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array());
240
241 /**
242 * Left Outer Join against another table in the database.
243 *
244 * @param $table
245 * The table against which to join.
246 * @param $alias
247 * The alias for the table. In most cases this should be the first letter
248 * of the table, or the first letter of each "word" in the table.
249 * @param $condition
250 * The condition on which to join this table. If the join requires values,
251 * this clause should use a named placeholder and the value or values to
252 * insert should be passed in the 4th parameter. For the first table joined
253 * on a query, this value is ignored as the first table is taken as the base
254 * table.
255 * @param $arguments
256 * An array of arguments to replace into the $condition of this join.
257 * @return
258 * The unique alias that was assigned for this table.
259 */
260 public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array());
261
262 /**
263 * Right Outer Join against another table in the database.
264 *
265 * @param $table
266 * The table against which to join.
267 * @param $alias
268 * The alias for the table. In most cases this should be the first letter
269 * of the table, or the first letter of each "word" in the table.
270 * @param $condition
271 * The condition on which to join this table. If the join requires values,
272 * this clause should use a named placeholder and the value or values to
273 * insert should be passed in the 4th parameter. For the first table joined
274 * on a query, this value is ignored as the first table is taken as the base
275 * table.
276 * @param $arguments
277 * An array of arguments to replace into the $condition of this join.
278 * @return
279 * The unique alias that was assigned for this table.
280 */
281 public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array());
282
283 /**
284 * Join against another table in the database.
285 *
286 * This method does the "hard" work of queuing up a table to be joined against.
287 * In some cases, that may include dipping into the Schema API to find the necessary
288 * fields on which to join.
289 *
290 * @param $type
291 * The type of join. Typically one one of INNER, LEFT OUTER, and RIGHT OUTER.
292 * @param $table
293 * The table against which to join. May be a string or another SelectQuery
294 * object. If a query object is passed, it will be used as a subselect.
295 * @param $alias
296 * The alias for the table. In most cases this should be the first letter
297 * of the table, or the first letter of each "word" in the table. If omitted,
298 * one will be dynamically generated.
299 * @param $condition
300 * The condition on which to join this table. If the join requires values,
301 * this clause should use a named placeholder and the value or values to
302 * insert should be passed in the 4th parameter. For the first table joined
303 * on a query, this value is ignored as the first table is taken as the base
304 * table.
305 * @param $arguments
306 * An array of arguments to replace into the $condition of this join.
307 * @return
308 * The unique alias that was assigned for this table.
309 */
310 public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array());
311
312 /**
313 * Orders the result set by a given field.
314 *
315 * If called multiple times, the query will order by each specified field in the
316 * order this method is called.
317 *
318 * @param $field
319 * The field on which to order.
320 * @param $direction
321 * The direction to sort. Legal values are "ASC" and "DESC".
322 * @return
323 * The called object.
324 */
325 public function orderBy($field, $direction = 'ASC');
326
327 /**
328 * Restricts a query to a given range in the result set.
329 *
330 * If this method is called with no parameters, will remove any range
331 * directives that have been set.
332 *
333 * @param $start
334 * The first record from the result set to return. If NULL, removes any
335 * range directives that are set.
336 * @param $limit
337 * The number of records to return from the result set.
338 * @return
339 * The called object.
340 */
341 public function range($start = NULL, $length = NULL);
342
343 /**
344 * Groups the result set by the specified field.
345 *
346 * @param $field
347 * The field on which to group. This should be the field as aliased.
348 * @return
349 * The called object.
350 */
351 public function groupBy($field);
352
353 /**
354 * Get the equivalent COUNT query of this query as a new query object.
355 *
356 * @return
357 * A new SelectQuery object with no fields or expressions besides COUNT(*).
358 */
359 public function countQuery();
360
361 /**
362 * Clone magic method.
363 *
364 * Select queries have dependent objects that must be deep-cloned. The
365 * connection object itself, however, should not be cloned as that would
366 * duplicate the connection itself.
367 */
368 public function __clone();
369 }
370
371 /**
372 * The base extender class for Select queries.
373 */
374 class SelectQueryExtender implements SelectQueryInterface {
375
376 /**
377 * The SelectQuery object we are extending/decorating.
378 *
379 * @var SelectQueryInterface
380 */
381 protected $query;
382
383 /**
384 * The connection object on which to run this query.
385 *
386 * @var DatabaseConnection
387 */
388 protected $connection;
389
390
391 public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) {
392 $this->query = $query;
393 $this->connection = $connection;
394 }
395
396 /* Implementations of QueryAlterableInterface. */
397
398 public function addTag($tag) {
399 $this->query->addTag($tag);
400 return $this;
401 }
402
403 public function hasTag($tag) {
404 return $this->query->hasTag($tag);
405 }
406
407 public function hasAllTags() {
408 return call_user_func_array(array($this->query, 'hasAllTags', func_get_args()));
409 }
410
411 public function hasAnyTag() {
412 return call_user_func_array(array($this->query, 'hasAnyTags', func_get_args()));
413 }
414
415 public function addMetaData($key, $object) {
416 $this->query->addMetaData($key, $object);
417 return $this;
418 }
419
420 public function getMetaData($key) {
421 return $this->query->getMetaData($key);
422 }
423
424 /* Implementations of QueryConditionInterface for the WHERE clause. */
425
426 public function condition($field, $value = NULL, $operator = NULL) {
427 $this->query->condition($field, $value, $operator);
428 return $this;
429 }
430
431 public function &conditions() {
432 return $this->query->conditions();
433 }
434
435 public function arguments() {
436 return $this->query->arguments();
437 }
438
439 public function where($snippet, $args = array()) {
440 $this->query->where($snippet, $args);
441 return $this;
442 }
443
444 public function compile(DatabaseConnection $connection) {
445 return $this->query->compile($connection);
446 }
447
448 /* Implmeentations of QueryConditionInterface for the HAVING clause. */
449
450 public function havingCondition($field, $value = NULL, $operator = '=') {
451 $this->query->condition($field, $value, $operator, $num_args);
452 return $this;
453 }
454
455 public function &havingConditions() {
456 return $this->having->conditions();
457 }
458
459 public function havingArguments() {
460 return $this->having->arguments();
461 }
462
463 public function having($snippet, $args = array()) {
464 $this->query->having($snippet, $args);
465 return $this;
466 }
467
468 public function havingCompile(DatabaseConnection $connection) {
469 return $this->query->havingCompile($connection);
470 }
471
472 /* Implementations of QueryExtendableInterface. */
473
474 public function extend($extender_name) {
475 $override_class = $this->connection->driver();
476 if (class_exists($override_class)) {
477 $extender_name = $override_class;
478 }
479 return new $extender_name($this, $this->connection);
480 }
481
482 /* Alter accessors to expose the query data to alter hooks. */
483
484 public function &getFields() {
485 return $this->query->getFields();
486 }
487
488 public function &getExpressions() {
489 return $this->query->getExpressions();
490 }
491
492 public function &getOrderBy() {
493 return $this->query->getOrderBy();
494 }
495
496 public function &getTables() {
497 return $this->query->getTables();
498 }
499
500 public function getArguments() {
501 return $this->query->getArguments();
502 }
503
504 public function execute() {
505 return $this->query->execute();
506 }
507
508 public function distinct($distinct = TRUE) {
509 $this->query->distinct($distinct);
510 return $this;
511 }
512
513 public function addField($table_alias, $field, $alias = NULL) {
514 return $this->query->addField($table_alias, $field, $alias);
515 }
516
517 public function fields($table_alias, array $fields = array()) {
518 $this->query->fields($table_alias, $fields);
519 return $this;
520 }
521
522 public function addExpression($expression, $alias = NULL, $arguments = array()) {
523 return $this->query->addExpression($expression, $alias, $arguments);
524 }
525
526 public function join($table, $alias = NULL, $condition = NULL, $arguments = array()) {
527 return $this->query->join($table, $alias, $condition, $arguments);
528 }
529
530 public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
531 return $this->query->innerJoin($table, $alias, $condition, $arguments);
532 }
533
534 public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
535 return $this->query->leftJoin($table, $alias, $condition, $arguments);
536 }
537
538 public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
539 return $this->query->rightJoin($table, $alias, $condition, $arguments);
540 }
541
542 public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) {
543 return $this->query->addJoin($type, $table, $alias, $condition, $arguments);
544 }
545
546 public function orderBy($field, $direction = 'ASC') {
547 $this->query->orderBy($field, $direction);
548 return $this;
549 }
550
551 public function range($start = NULL, $length = NULL) {
552 $this->query->range($start, $length);
553 return $this;
554 }
555
556 public function groupBy($field) {
557 $this->query->groupBy($field);
558 return $this;
559 }
560
561 public function countQuery() {
562 // Create our new query object that we will mutate into a count query.
563 $count = clone($this);
564
565 // Zero-out existing fields and expressions.
566 $fields =& $count->getFields();
567 $fields = array();
568 $expressions =& $count->getExpressions();
569 $expressions = array();
570
571 // Also remove 'all_fields' statements, which are expanded into tablename.*
572 // when the query is executed.
573 $tables = &$count->getTables();
574 foreach ($tables as $alias => &$table) {
575 unset($table['all_fields']);
576 }
577
578 // Ordering a count query is a waste of cycles, and breaks on some
579 // databases anyway.
580 $orders = &$count->getOrderBy();
581 $orders = array();
582
583 // COUNT() is an expression, so we add that back in.
584 $count->addExpression('COUNT(*)');
585
586 return $count;
587 }
588
589 function isNull($field) {
590 $this->query->isNull($field);
591 return $this;
592 }
593
594 function isNotNull($field) {
595 $this->query->isNotNull($field);
596 return $this;
597 }
598
599 public function __toString() {
600 return (string)$this->query;
601 }
602
603 public function __clone() {
604 // We need to deep-clone the query we're wrapping, which in turn may
605 // deep-clone other objects. Exciting!
606 $this->query = clone($this->query);
607 }
608
609 /**
610 * Magic override for undefined methods.
611 *
612 * If one extender extends another extender, then methods in the inner extender
613 * will not be exposed on the outer extender. That's because we cannot know
614 * in advance what those methods will be, so we cannot provide wrapping
615 * implementations as we do above. Instead, we use this slower catch-all method
616 * to handle any additional methods.
617 */
618 public function __call($method, $args) {
619 $return = call_user_func_array(array($this->query, $method), $args);
620
621 // Some methods will return the called object as part of a fluent interface.
622 // Others will return some useful value. If it's a value, then the caller
623 // probably wants that value. If it's the called object, then we instead
624 // return this object. That way we don't "lose" an extender layer when
625 // chaining methods together.
626 if ($return instanceof SelectQueryInterface) {
627 return $this;
628 }
629 else {
630 return $return;
631 }
632 }
633 }
634
635 /**
636 * Query builder for SELECT statements.
637 */
638 class SelectQuery extends Query implements SelectQueryInterface {
639
640 /**
641 * The fields to SELECT.
642 *
643 * @var array
644 */
645 protected $fields = array();
646
647 /**
648 * The expressions to SELECT as virtual fields.
649 *
650 * @var array
651 */
652 protected $expressions = array();
653
654 /**
655 * The tables against which to JOIN.
656 *
657 * This property is a nested array. Each entry is an array representing
658 * a single table against which to join. The structure of each entry is:
659 *
660 * array(
661 * 'type' => $join_type (one of INNER, LEFT OUTER, RIGHT OUTER),
662 * 'table' => $table,
663 * 'alias' => $alias_of_the_table,
664 * 'condition' => $condition_clause_on_which_to_join,
665 * 'arguments' => $array_of_arguments_for_placeholders_in_the condition.
666 * 'all_fields' => TRUE to SELECT $alias.*, FALSE or NULL otherwise.
667 * )
668 *
669 * If $table is a string, it is taken as the name of a table. If it is
670 * a SelectQuery object, it is taken as a subquery.
671 *
672 * @var array
673 */
674 protected $tables = array();
675
676 /**
677 * The fields by which to order this query.
678 *
679 * This is an associative array. The keys are the fields to order, and the value
680 * is the direction to order, either ASC or DESC.
681 *
682 * @var array
683 */
684 protected $order = array();
685
686 /**
687 * The fields by which to group.
688 *
689 * @var array
690 */
691 protected $group = array();
692
693 /**
694 * The conditional object for the WHERE clause.
695 *
696 * @var DatabaseCondition
697 */
698 protected $where;
699
700 /**
701 * The conditional object for the HAVING clause.
702 *
703 * @var DatabaseCondition
704 */
705 protected $having;
706
707 /**
708 * Whether or not this query should be DISTINCT
709 *
710 * @var boolean
711 */
712 protected $distinct = FALSE;
713
714 /**
715 * The range limiters for this query.
716 *
717 * @var array
718 */
719 protected $range;
720
721 public function __construct($table, $alias = NULL, DatabaseConnection $connection, $options = array()) {
722 $options['return'] = Database::RETURN_STATEMENT;
723 parent::__construct($connection, $options);
724 $this->where = new DatabaseCondition('AND');
725 $this->having = new DatabaseCondition('AND');
726 $this->addJoin(NULL, $table, $alias);
727 }
728
729 /* Implementations of QueryAlterableInterface. */
730
731 public function addTag($tag) {
732 $this->alterTags[$tag] = 1;
733 return $this;
734 }
735
736 public function hasTag($tag) {
737 return isset($this->alterTags[$tag]);
738 }
739
740 public function hasAllTags() {
741 return !(boolean)array_diff(func_get_args(), array_keys($this->alterTags));
742 }
743
744 public function hasAnyTag() {
745 return (boolean)array_intersect(func_get_args(), array_keys($this->alterTags));
746 }
747
748 public function addMetaData($key, $object) {
749 $this->alterMetaData[$key] = $object;
750 return $this;
751 }
752
753 public function getMetaData($key) {
754 return isset($this->alterMetaData[$key]) ? $this->alterMetaData[$key] : NULL;
755 }
756
757 /* Implementations of QueryConditionInterface for the WHERE clause. */
758
759 public function condition($field, $value = NULL, $operator = '=') {
760 if (!isset($num_args)) {
761 $num_args = func_num_args();
762 }
763 $this->where->condition($field, $value, $operator, $num_args);
764 return $this;
765 }
766
767 public function &conditions() {
768 return $this->where->conditions();
769 }
770
771 public function arguments() {
772 return $this->where->arguments();
773 }
774
775 public function where($snippet, $args = array()) {
776 $this->where->where($snippet, $args);
777 return $this;
778 }
779
780 public function isNull($field) {
781 $this->where->isNull($field);
782 return $this;
783 }
784
785 public function isNotNull($field) {
786 $this->where->isNotNull($field);
787 return $this;
788 }
789
790
791 public function compile(DatabaseConnection $connection) {
792 return $this->where->compile($connection);
793 }
794
795 /* Implmeentations of QueryConditionInterface for the HAVING clause. */
796
797 public function havingCondition($field, $value = NULL, $operator = '=') {
798 if (!isset($num_args)) {
799 $num_args = func_num_args();
800 }
801 $this->having->condition($field, $value, $operator, $num_args);
802 return $this;
803 }
804
805 public function &havingConditions() {
806 return $this->having->conditions();
807 }
808
809 public function havingArguments() {
810 return $this->having->arguments();
811 }
812
813 public function having($snippet, $args = array()) {
814 $this->having->where($snippet, $args);
815 return $this;
816 }
817
818 public function havingCompile(DatabaseConnection $connection) {
819 return $this->having->compile($connection);
820 }
821
822 /* Implementations of QueryExtendableInterface. */
823
824 public function extend($extender_name) {
825 $override_class = __CLASS__ . $this->connection->driver();
826 if (class_exists($override_class)) {
827 $extender_name = $override_class;
828 }
829 return new $extender_name($this, $this->connection);
830 }
831
832 public function havingIsNull($field) {
833 $this->having->isNull($field);
834 return $this;
835 }
836
837 public function havingIsNotNull($field) {
838 $this->having->isNotNull($field);
839 return $this;
840 }
841
842
843 /* Alter accessors to expose the query data to alter hooks. */
844
845 public function &getFields() {
846 return $this->fields;
847 }
848
849 public function &getExpressions() {
850 return $this->expressions;
851 }
852
853 public function &getOrderBy() {
854 return $this->order;
855 }
856
857 public function &getTables() {
858 return $this->tables;
859 }
860
861 public function getArguments() {
862 $this->where->compile($this->connection);
863 $this->having->compile($this->connection);
864 $args = $this->where->arguments() + $this->having->arguments();
865 foreach ($this->tables as $table) {
866 if ($table['arguments']) {
867 $args += $table['arguments'];
868 }
869 // If this table is a subquery, grab its arguments recursively.
870 if ($table['table'] instanceof SelectQueryInterface) {
871 $args += $table['table']->getArguments();
872 }
873 }
874 foreach ($this->expressions as $expression) {
875 if ($expression['arguments']) {
876 $args += $expression['arguments'];
877 }
878 }
879
880 return $args;
881 }
882
883 public function execute() {
884 // Modules may alter all queries or only those having a particular tag.
885 drupal_alter('query', $this);
886 if (isset($this->alterTags)) {
887 foreach ($this->alterTags as $tag => $value) {
888 drupal_alter("query_$tag", $this);
889 }
890 }
891
892 $args = $this->getArguments();
893
894 if (!empty($this->range)) {
895 return $this->connection->queryRange((string)$this, $args, $this->range['start'], $this->range['length'], $this->queryOptions);
896 }
897 return $this->connection->query((string)$this, $args, $this->queryOptions);
898 }
899
900 public function distinct($distinct = TRUE) {
901 $this->distinct = $distinct;
902 return $this;
903 }
904
905 public function addField($table_alias, $field, $alias = NULL) {
906 // If no alias is specified, first try the field name itself.
907 if (empty($alias)) {
908 $alias = $field;
909 }
910
911 // If that's already in use, try the table name and field name.
912 if (!empty($this->tables[$alias])) {
913 $alias = $table_alias . '_' . $field;
914 }
915
916 // If that is already used, just add a counter until we find an unused alias.
917 $alias_candidate = $alias;
918 $count = 2;
919 while (!empty($this->tables[$alias_candidate])) {
920 $alias_candidate = $alias . '_' . $count++;
921 }
922 $alias = $alias_candidate;
923
924 $this->fields[$alias] = array(
925 'field' => $field,
926 'table' => $table_alias,
927 'alias' => $alias,
928 );
929
930 return $alias;
931 }
932
933 public function fields($table_alias, array $fields = array()) {
934
935 if ($fields) {
936 foreach ($fields as $field) {
937 // We don't care what alias was assigned.
938 $this->addField($table_alias, $field);
939 }
940 }
941 else {
942 // We want all fields from this table.
943 $this->tables[$table_alias]['all_fields'] = TRUE;
944 }
945
946 return $this;
947 }
948
949 public function addExpression($expression, $alias = NULL, $arguments = array()) {
950 if (empty($alias)) {
951 $alias = 'expression';
952 }
953
954 $alias_candidate = $alias;
955 $count = 2;
956 while (!empty($this->expressions[$alias_candidate])) {
957 $alias_candidate = $alias . '_' . $count++;
958 }
959 $alias = $alias_candidate;
960
961 $this->expressions[$alias] = array(
962 'expression' => $expression,
963 'alias' => $alias,
964 'arguments' => $arguments,
965 );
966
967 return $alias;
968 }
969
970 public function join($table, $alias = NULL, $condition = NULL, $arguments = array()) {
971 return $this->addJoin('INNER', $table, $alias, $condition, $arguments);
972 }
973
974 public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
975 return $this->addJoin('INNER', $table, $alias, $condition, $arguments);
976 }
977
978 public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
979 return $this->addJoin('LEFT OUTER', $table, $alias, $condition, $arguments);
980 }
981
982 public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
983 return $this->addJoin('RIGHT OUTER', $table, $alias, $condition, $arguments);
984 }
985
986 public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) {
987
988 if (empty($alias)) {
989 if ($table instanceof SelectQueryInterface) {
990 $alias = 'subquery';
991 }
992 else {
993 $alias = $table;
994 }
995 }
996
997 $alias_candidate = $alias;
998 $count = 2;
999 while (!empty($this->tables[$alias_candidate])) {
1000 $alias_candidate = $alias . '_' . $count++;
1001 }
1002 $alias = $alias_candidate;
1003
1004 $this->tables[$alias] = array(
1005 'join type' => $type,
1006 'table' => $table,
1007 'alias' => $alias,
1008 'condition' => $condition,
1009 'arguments' => $arguments,
1010 );
1011
1012 return $alias;
1013 }
1014
1015 public function orderBy($field, $direction = 'ASC') {
1016 $this->order[$field] = $direction;
1017 return $this;
1018 }
1019
1020 public function range($start = NULL, $length = NULL) {
1021 $this->range = func_num_args() ? array('start' => $start, 'length' => $length) : array();
1022 return $this;
1023 }
1024
1025 public function groupBy($field) {
1026 $this->group[] = $field;
1027 return $this;
1028 }
1029
1030 public function countQuery() {
1031 // Create our new query object that we will mutate into a count query.
1032 $count = clone($this);
1033
1034 // Zero-out existing fields and expressions.
1035 $fields =& $count->getFields();
1036 $fields = array();
1037 $expressions =& $count->getExpressions();
1038 $expressions = array();
1039
1040
1041 // Also remove 'all_fields' statements, which are expanded into tablename.*
1042 // when the query is executed.
1043 foreach ($count->tables as $alias => &$table) {
1044 unset($table['all_fields']);
1045 }
1046
1047 // Ordering a count query is a waste of cycles, and breaks on some
1048 // databases anyway.
1049 $orders = &$count->getOrderBy();
1050 $orders = array();
1051
1052 // COUNT() is an expression, so we add that back in.
1053 $count->addExpression('COUNT(*)');
1054
1055 return $count;
1056 }
1057
1058 public function __toString() {
1059
1060 // SELECT
1061 $query = 'SELECT ';
1062 if ($this->distinct) {
1063 $query .= 'DISTINCT ';
1064 }
1065
1066 // FIELDS and EXPRESSIONS
1067 $fields = array();
1068 foreach ($this->fields as $alias => $field) {
1069 // Always use the AS keyword for field aliases, as some
1070 // databases require it (e.g., PostgreSQL).
1071 $fields[] = (isset($field['table']) ? $field['table'] . '.' : '') . $field['field'] . ' AS ' . $field['alias'];
1072 }
1073 foreach ($this->expressions as $alias => $expression) {
1074 $fields[] = $expression['expression'] . ' AS ' . $expression['alias'];
1075 }
1076 foreach ($this->tables as $alias => $table) {
1077 if (!empty($table['all_fields'])) {
1078 $fields[] = $alias . '.*';
1079 }
1080 }
1081 $query .= implode(', ', $fields);
1082
1083
1084 // FROM - We presume all queries have a FROM, as any query that doesn't won't need the query builder anyway.
1085 $query .= "\nFROM ";
1086 foreach ($this->tables as $alias => $table) {
1087 $query .= "\n";
1088 if (isset($table['join type'])) {
1089 $query .= $table['join type'] . ' JOIN ';
1090 }
1091
1092 // If the table is a subquery, compile it and integrate it into this query.
1093 if ($table['table'] instanceof SelectQueryInterface) {
1094 $table_string = '(' . (string)$table['table'] . ')';
1095 }
1096 else {
1097 $table_string = '{' . $this->connection->escapeTable($table['table']) . '}';
1098 }
1099
1100 // Don't use the AS keyword for table aliases, as some
1101 // databases don't support it (e.g., Oracle).
1102 $query .= $table_string . ' ' . $table['alias'];
1103
1104 if (!empty($table['condition'])) {
1105 $query .= ' ON ' . $table['condition'];
1106 }
1107 }
1108
1109 // WHERE
1110 if (count($this->where)) {
1111 $this->where->compile($this->connection);
1112 // There is an implicit string cast on $this->condition.
1113 $query .= "\nWHERE " . $this->where;
1114 }
1115
1116 // GROUP BY
1117 if ($this->group) {
1118 $query .= "\nGROUP BY " . implode(', ', $this->group);
1119 }
1120
1121 // HAVING
1122 if (count($this->having)) {
1123 $this->having->compile($this->connection);
1124 // There is an implicit string cast on $this->having.
1125 $query .= "\nHAVING " . $this->having;
1126 }
1127
1128 // ORDER BY
1129 if ($this->order) {
1130 $query .= "\nORDER BY ";
1131 $fields = array();
1132 foreach ($this->order as $field => $direction) {
1133 $fields[] = $field . ' ' . $direction;
1134 }
1135 $query .= implode(', ', $fields);
1136 }
1137
1138 // RANGE is database specific, so we can't do it here.
1139
1140 return $query;
1141 }
1142
1143 public function __clone() {
1144 // On cloning, also clone the conditional objects. However, we do not
1145 // want to clone the database connection object as that would duplicate the
1146 // connection itself.
1147
1148 $this->where = clone($this->where);
1149 $this->having = clone($this->having);
1150 }
1151 }
1152
1153 /**
1154 * @} End of "ingroup database".
1155 */
1156

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.