Completed
Push — 3.x ( 4ede47...f5caca )
by Hari
7s
created

Select   D

Complexity

Total Complexity 81

Size/Duplication

Total Lines 1051
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 98.77%

Importance

Changes 0
Metric Value
wmc 81
lcom 1
cbo 4
dl 0
loc 1051
ccs 241
cts 244
cp 0.9877
rs 4.4263
c 0
b 0
f 0

52 Methods

Rating   Name   Duplication   Size   Complexity  
A getPaging() 0 4 1
A getStatement() 0 8 2
A cols() 0 7 2
A forUpdate() 0 5 1
A distinct() 0 5 1
A removeCol() 0 16 3
A hasCols() 0 4 1
A getCols() 0 4 1
A from() 0 5 1
A fromRaw() 0 5 1
A addFrom() 0 6 1
A fromSubSelect() 0 7 1
A join() 0 9 1
A fixJoinCondition() 0 19 4
A innerJoin() 0 4 1
A leftJoin() 0 4 1
A joinSubSelect() 0 12 1
A addJoin() 0 6 2
A setPaging() 0 8 2
A addCol() 0 9 2
A addColWithAlias() 0 15 4
A addTableRef() 0 16 3
A subSelect() 0 10 2
A groupBy() 0 7 2
A having() 0 5 1
A orHaving() 0 5 1
A page() 0 6 1
A getPage() 0 4 1
A union() 0 6 1
A unionAll() 0 6 1
A getLimit() 0 4 1
A getOffset() 0 4 1
A reset() 0 14 1
A resetCols() 0 5 1
A resetTables() 0 8 1
A resetWhere() 0 5 1
A resetGroupBy() 0 5 1
A resetHaving() 0 5 1
A resetOrderBy() 0 5 1
A resetUnions() 0 5 1
A build() 0 13 1
A buildGroupBy() 0 8 2
A buildHaving() 0 8 2
A buildForUpdate() 0 8 2
A where() 0 5 1
A orWhere() 0 5 1
A orderBy() 0 4 1
A setPagingLimitOffset() 0 9 2
A buildCols() 0 17 4
A buildFrom() 0 15 4
A limit() 0 9 2
A offset() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like Select often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Select, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 *
4
 * This file is part of Aura for PHP.
5
 *
6
 * @license http://opensource.org/licenses/bsd-license.php BSD
7
 *
8
 */
9
namespace Aura\SqlQuery\Common;
10
11
use Aura\SqlQuery\AbstractQuery;
12
use Aura\SqlQuery\Exception;
13
14
/**
15
 *
16
 * An object for SELECT queries.
17
 *
18
 * @package Aura.SqlQuery
19
 *
20
 */
21
class Select extends AbstractQuery implements SelectInterface, SubselectInterface
22
{
23
    /**
24
     *
25
     * An array of union SELECT statements.
26
     *
27
     * @var array
28
     *
29
     */
30
    protected $union = array();
31
32
    /**
33
     *
34
     * Is this a SELECT FOR UPDATE?
35
     *
36
     * @var
37
     *
38
     */
39
    protected $for_update = false;
40
41
    /**
42
     *
43
     * The columns to be selected.
44
     *
45
     * @var array
46
     *
47
     */
48
    protected $cols = array();
49
50
    /**
51
     *
52
     * Select from these tables; includes JOIN clauses.
53
     *
54
     * @var array
55
     *
56
     */
57
    protected $from = array();
58
59
    /**
60
     *
61
     * The current key in the `$from` array.
62
     *
63
     * @var int
64
     *
65
     */
66
    protected $from_key = -1;
67
68
    /**
69
     *
70
     * Tracks which JOIN clauses are attached to which FROM tables.
71
     *
72
     * @var array
73
     *
74
     */
75
    protected $join = array();
76
77
    /**
78
     *
79
     * GROUP BY these columns.
80
     *
81
     * @var array
82
     *
83
     */
84
    protected $group_by = array();
85
86
    /**
87
     *
88
     * The list of HAVING conditions.
89
     *
90
     * @var array
91
     *
92
     */
93
    protected $having = array();
94
95
    /**
96
     *
97
     * The page number to select.
98
     *
99
     * @var int
100
     *
101
     */
102
    protected $page = 0;
103
104
    /**
105
     *
106
     * The number of rows per page.
107
     *
108
     * @var int
109
     *
110
     */
111
    protected $paging = 10;
112
113
    /**
114
     *
115
     * Tracks table references to avoid duplicate identifiers.
116
     *
117
     * @var array
118
     *
119
     */
120
    protected $table_refs = array();
121
122
    /**
123
     *
124
     * Returns this query object as an SQL statement string.
125
     *
126
     * @return string An SQL statement string.
127
     *
128
     */
129 184
    public function getStatement()
130
    {
131 184
        $union = '';
132 184
        if (! empty($this->union)) {
133 15
            $union = implode(PHP_EOL, $this->union) . PHP_EOL;
134
        }
135 184
        return $union . $this->build();
136
    }
137
138
    /**
139
     *
140
     * Sets the number of rows per page.
141
     *
142
     * @param int $paging The number of rows to page at.
143
     *
144
     * @return $this
145
     *
146
     */
147 10
    public function setPaging($paging)
148
    {
149 10
        $this->paging = (int) $paging;
150 10
        if ($this->page) {
151 5
            $this->setPagingLimitOffset();
152
        }
153 10
        return $this;
154
    }
155
156
    /**
157
     *
158
     * Gets the number of rows per page.
159
     *
160
     * @return int The number of rows per page.
161
     *
162
     */
163 10
    public function getPaging()
164
    {
165 10
        return $this->paging;
166
    }
167
168
    /**
169
     *
170
     * Makes the select FOR UPDATE (or not).
171
     *
172
     * @param bool $enable Whether or not the SELECT is FOR UPDATE (default
173
     * true).
174
     *
175
     * @return $this
176
     *
177
     */
178 20
    public function forUpdate($enable = true)
179
    {
180 20
        $this->for_update = (bool) $enable;
181 20
        return $this;
182
    }
183
184
    /**
185
     *
186
     * Makes the select DISTINCT (or not).
187
     *
188
     * @param bool $enable Whether or not the SELECT is DISTINCT (default
189
     * true).
190
     *
191
     * @return $this
192
     *
193
     */
194 16
    public function distinct($enable = true)
195
    {
196 16
        $this->setFlag('DISTINCT', $enable);
197 16
        return $this;
198
    }
199
200
    /**
201
     *
202
     * Adds columns to the query.
203
     *
204
     * Multiple calls to cols() will append to the list of columns, not
205
     * overwrite the previous columns.
206
     *
207
     * @param array $cols The column(s) to add to the query. The elements can be
208
     * any mix of these: `array("col", "col AS alias", "col" => "alias")`
209
     *
210
     * @return $this
211
     *
212
     */
213 224
    public function cols(array $cols)
214
    {
215 224
        foreach ($cols as $key => $val) {
216 224
            $this->addCol($key, $val);
217
        }
218 224
        return $this;
219
    }
220
221
    /**
222
     *
223
     * Adds a column and alias to the columns to be selected.
224
     *
225
     * @param mixed $key If an integer, ignored. Otherwise, the column to be
226
     * added.
227
     *
228
     * @param mixed $val If $key was an integer, the column to be added;
229
     * otherwise, the column alias.
230
     *
231
     * @return null
232
     *
233
     */
234 224
    protected function addCol($key, $val)
235
    {
236 224
        if (is_string($key)) {
237
            // [col => alias]
238 25
            $this->cols[$val] = $key;
239
        } else {
240 214
            $this->addColWithAlias($val);
241
        }
242 224
    }
243
244
    /**
245
     *
246
     * Adds a column with an alias to the columns to be selected.
247
     *
248
     * @param string $spec The column specification: "col alias",
249
     * "col AS alias", or something else entirely.
250
     *
251
     * @return null
252
     *
253
     */
254 214
    protected function addColWithAlias($spec)
255
    {
256 214
        $parts = explode(' ', $spec);
257 214
        $count = count($parts);
258 214
        if ($count == 2) {
259
            // "col alias"
260 5
            $this->cols[$parts[1]] = $parts[0];
261 214
        } elseif ($count == 3 && strtoupper($parts[1]) == 'AS') {
262
            // "col AS alias"
263 5
            $this->cols[$parts[2]] = $parts[0];
264
        } else {
265
            // no recognized alias
266 214
            $this->cols[] = $spec;
267
        }
268 214
    }
269
270
    /**
271
     *
272
     * Remove a column via its alias.
273
     *
274
     * @param string $alias The column to remove
275
     *
276
     * @return bool
277
     *
278
     */
279 15
    public function removeCol($alias)
280
    {
281 15
        if (isset($this->cols[$alias])) {
282 5
            unset($this->cols[$alias]);
283
284 5
            return true;
285
        }
286
287 10
        $index = array_search($alias, $this->cols);
288 10
        if ($index !== false) {
289 5
            unset($this->cols[$index]);
290 5
            return true;
291
        }
292
293 5
        return false;
294
    }
295
296
    /**
297
     *
298
     * Does the query have any columns in it?
299
     *
300
     * @return bool
301
     *
302
     */
303 5
    public function hasCols()
304
    {
305 5
        return (bool) $this->cols;
306
    }
307
308
    /**
309
     *
310
     * Returns a list of columns.
311
     *
312
     * @return array
313
     *
314
     */
315 15
    public function getCols()
316
    {
317 15
        return $this->cols;
318
    }
319
320
    /**
321
     *
322
     * Tracks table references.
323
     *
324
     * @param string $type FROM, JOIN, etc.
325
     *
326
     * @param string $spec The table and alias name.
327
     *
328
     * @return null
329
     *
330
     * @throws Exception when the reference has already been used.
331
     *
332
     */
333 149
    protected function addTableRef($type, $spec)
334
    {
335 149
        $name = $spec;
336
337 149
        $pos = strripos($name, ' AS ');
338 149
        if ($pos !== false) {
339 35
            $name = trim(substr($name, $pos + 4));
340
        }
341
342 149
        if (isset($this->table_refs[$name])) {
343 25
            $used = $this->table_refs[$name];
344 25
            throw new Exception("Cannot reference '$type $spec' after '$used'");
345
        }
346
347 149
        $this->table_refs[$name] = "$type $spec";
348 149
    }
349
350
    /**
351
     *
352
     * Adds a FROM element to the query; quotes the table name automatically.
353
     *
354
     * @param string $spec The table specification; "foo" or "foo AS bar".
355
     *
356
     * @return $this
357
     *
358
     */
359 139
    public function from($spec)
360
    {
361 139
        $this->addTableRef('FROM', $spec);
362 139
        return $this->addFrom($this->quoter->quoteName($spec));
363
    }
364
365
    /**
366
     *
367
     * Adds a raw unquoted FROM element to the query; useful for adding FROM
368
     * elements that are functions.
369
     *
370
     * @param string $spec The table specification, e.g. "function_name()".
371
     *
372
     * @return $this
373
     *
374
     */
375 5
    public function fromRaw($spec)
376
    {
377 5
        $this->addTableRef('FROM', $spec);
378 5
        return $this->addFrom($spec);
379
    }
380
381
    /**
382
     *
383
     * Adds to the $from property and increments the key count.
384
     *
385
     * @param string $spec The table specification.
386
     *
387
     * @return $this
388
     *
389
     */
390 149
    protected function addFrom($spec)
391
    {
392 149
        $this->from[] = array($spec);
393 149
        $this->from_key ++;
394 149
        return $this;
395
    }
396
397
    /**
398
     *
399
     * Adds an aliased sub-select to the query.
400
     *
401
     * @param string|Select $spec If a Select object, use as the sub-select;
402
     * if a string, the sub-select string.
403
     *
404
     * @param string $name The alias name for the sub-select.
405
     *
406
     * @return $this
407
     *
408
     */
409 15
    public function fromSubSelect($spec, $name)
410
    {
411 15
        $this->addTableRef('FROM (SELECT ...) AS', $name);
412 10
        $spec = $this->subSelect($spec, '        ');
413 10
        $name = $this->quoter->quoteName($name);
414 10
        return $this->addFrom("({$spec}    ) AS $name");
415
    }
416
417
    /**
418
     *
419
     * Formats a sub-SELECT statement, binding values from a Select object as
420
     * needed.
421
     *
422
     * @param string|SelectInterface $spec A sub-SELECT specification.
423
     *
424
     * @param string $indent Indent each line with this string.
425
     *
426
     * @return string The sub-SELECT string.
427
     *
428
     */
429 25
    protected function subSelect($spec, $indent)
430
    {
431 25
        if ($spec instanceof SelectInterface) {
432 10
            $this->bindValues($spec->getBindValues());
433
        }
434
435 25
        return PHP_EOL . $indent
436 25
            . ltrim(preg_replace('/^/m', $indent, (string) $spec))
437 25
            . PHP_EOL;
438
    }
439
440
    /**
441
     *
442
     * Adds a JOIN table and columns to the query.
443
     *
444
     * @param string $join The join type: inner, left, natural, etc.
445
     *
446
     * @param string $spec The table specification; "foo" or "foo AS bar".
447
     *
448
     * @param string $cond Join on this condition.
449
     *
450
     * @param array $bind Values to bind to ?-placeholders in the condition.
451
     *
452
     * @return $this
453
     *
454
     * @throws Exception
455
     *
456
     */
457 45
    public function join($join, $spec, $cond = null, array $bind = array())
458
    {
459 45
        $join = strtoupper(ltrim("$join JOIN"));
460 45
        $this->addTableRef($join, $spec);
461
462 40
        $spec = $this->quoter->quoteName($spec);
463 40
        $cond = $this->fixJoinCondition($cond, $bind);
464 40
        return $this->addJoin(rtrim("$join $spec $cond"));
465
    }
466
467
    /**
468
     *
469
     * Fixes a JOIN condition to quote names in the condition and prefix it
470
     * with a condition type ('ON' is the default and 'USING' is recognized).
471
     *
472
     * @param string $cond Join on this condition.
473
     *
474
     * @param array $bind Values to bind to ?-placeholders in the condition.
475
     *
476
     * @return string
477
     *
478
     */
479 55
    protected function fixJoinCondition($cond, array $bind)
480
    {
481 55
        if (! $cond) {
482 25
            return '';
483
        }
484
485 55
        $cond = $this->quoter->quoteNamesIn($cond);
486 55
        $cond = $this->rebuildCondAndBindValues($cond, $bind);
487
488 55
        if (strtoupper(substr(ltrim($cond), 0, 3)) == 'ON ') {
489 5
            return $cond;
490
        }
491
492 55
        if (strtoupper(substr(ltrim($cond), 0, 6)) == 'USING ') {
493 5
            return $cond;
494
        }
495
496 50
        return 'ON ' . $cond;
497
    }
498
499
    /**
500
     *
501
     * Adds a INNER JOIN table and columns to the query.
502
     *
503
     * @param string $spec The table specification; "foo" or "foo AS bar".
504
     *
505
     * @param string $cond Join on this condition.
506
     *
507
     * @param array $bind Values to bind to ?-placeholders in the condition.
508
     *
509
     * @return $this
510
     *
511
     * @throws Exception
512
     *
513
     */
514 10
    public function innerJoin($spec, $cond = null, array $bind = array())
515
    {
516 10
        return $this->join('INNER', $spec, $cond, $bind);
517
    }
518
519
    /**
520
     *
521
     * Adds a LEFT JOIN table and columns to the query.
522
     *
523
     * @param string $spec The table specification; "foo" or "foo AS bar".
524
     *
525
     * @param string $cond Join on this condition.
526
     *
527
     * @param array $bind Values to bind to ?-placeholders in the condition.
528
     *
529
     * @return $this
530
     *
531
     * @throws Exception
532
     *
533
     */
534 10
    public function leftJoin($spec, $cond = null, array $bind = array())
535
    {
536 10
        return $this->join('LEFT', $spec, $cond, $bind);
537
    }
538
539
    /**
540
     *
541
     * Adds a JOIN to an aliased subselect and columns to the query.
542
     *
543
     * @param string $join The join type: inner, left, natural, etc.
544
     *
545
     * @param string|Select $spec If a Select
546
     * object, use as the sub-select; if a string, the sub-select
547
     * command string.
548
     *
549
     * @param string $name The alias name for the sub-select.
550
     *
551
     * @param string $cond Join on this condition.
552
     *
553
     * @param array $bind Values to bind to ?-placeholders in the condition.
554
     *
555
     * @return $this
556
     *
557
     * @throws Exception
558
     *
559
     */
560 20
    public function joinSubSelect($join, $spec, $name, $cond = null, array $bind = array())
561
    {
562 20
        $join = strtoupper(ltrim("$join JOIN"));
563 20
        $this->addTableRef("$join (SELECT ...) AS", $name);
564
565 15
        $spec = $this->subSelect($spec, '            ');
566 15
        $name = $this->quoter->quoteName($name);
567 15
        $cond = $this->fixJoinCondition($cond, $bind);
568
569 15
        $text = rtrim("$join ($spec        ) AS $name $cond");
570 15
        return $this->addJoin('        ' . $text);
571
    }
572
573
    /**
574
     *
575
     * Adds the JOIN to the right place, given whether or not a FROM has been
576
     * specified yet.
577
     *
578
     * @param string $spec The JOIN clause.
579
     *
580
     * @return $this
581
     *
582
     */
583 55
    protected function addJoin($spec)
584
    {
585 55
        $from_key = ($this->from_key == -1) ? 0 : $this->from_key;
586 55
        $this->join[$from_key][] = $spec;
587 55
        return $this;
588
    }
589
590
    /**
591
     *
592
     * Adds grouping to the query.
593
     *
594
     * @param array $spec The column(s) to group by.
595
     *
596
     * @return $this
597
     *
598
     */
599 5
    public function groupBy(array $spec)
600
    {
601 5
        foreach ($spec as $col) {
602 5
            $this->group_by[] = $this->quoter->quoteNamesIn($col);
603
        }
604 5
        return $this;
605
    }
606
607
    /**
608
     *
609
     * Adds a HAVING condition to the query by AND. If the condition has
610
     * ?-placeholders, additional arguments to the method will be bound to
611
     * those placeholders sequentially.
612
     *
613
     * @param string $cond The HAVING condition.
614
     *
615
     * @param array ...$bind arguments to bind to placeholders
616
     *
617
     * @return $this
618
     *
619
     */
620 10
    public function having($cond, ...$bind)
621
    {
622 10
        $this->addClauseCondWithBind('having', 'AND', $cond, $bind);
623 10
        return $this;
624
    }
625
626
    /**
627
     *
628
     * Adds a HAVING condition to the query by AND. If the condition has
629
     * ?-placeholders, additional arguments to the method will be bound to
630
     * those placeholders sequentially.
631
     *
632
     * @param string $cond The HAVING condition.
633
     *
634
     * @param array ...$bind arguments to bind to placeholders
635
     *
636
     * @return $this
637
     *
638
     * @see having()
639
     *
640
     */
641 5
    public function orHaving($cond, ...$bind)
642
    {
643 5
        $this->addClauseCondWithBind('having', 'OR', $cond, $bind);
644 5
        return $this;
645
    }
646
647
    /**
648
     *
649
     * Sets the limit and count by page number.
650
     *
651
     * @param int $page Limit results to this page number.
652
     *
653
     * @return $this
654
     *
655
     */
656 25
    public function page($page)
657
    {
658 25
        $this->page = (int) $page;
659 25
        $this->setPagingLimitOffset();
660 25
        return $this;
661
    }
662
663
    /**
664
     *
665
     * Updates the limit and offset values when changing pagination.
666
     *
667
     * @return null
668
     *
669
     */
670 25
    protected function setPagingLimitOffset()
671
    {
672 25
        $this->limit  = 0;
673 25
        $this->offset = 0;
674 25
        if ($this->page) {
675 10
            $this->limit  = $this->paging;
676 10
            $this->offset = $this->paging * ($this->page - 1);
677
        }
678 25
    }
679
680
    /**
681
     *
682
     * Returns the page number being selected.
683
     *
684
     * @return int
685
     *
686
     */
687 5
    public function getPage()
688
    {
689 5
        return $this->page;
690
    }
691
692
    /**
693
     *
694
     * Takes the current select properties and retains them, then sets
695
     * UNION for the next set of properties.
696
     *
697
     * @return $this
698
     *
699
     */
700 10
    public function union()
701
    {
702 10
        $this->union[] = $this->build() . PHP_EOL . 'UNION';
703 10
        $this->reset();
704 10
        return $this;
705
    }
706
707
    /**
708
     *
709
     * Takes the current select properties and retains them, then sets
710
     * UNION ALL for the next set of properties.
711
     *
712
     * @return $this
713
     *
714
     */
715 5
    public function unionAll()
716
    {
717 5
        $this->union[] = $this->build() . PHP_EOL . 'UNION ALL';
718 5
        $this->reset();
719 5
        return $this;
720
    }
721
722
    /**
723
     *
724
     * Returns the LIMIT value.
725
     *
726
     * @return int
727
     *
728
     */
729 10
    public function getLimit()
730
    {
731 10
        return $this->limit;
732
    }
733
734
    /**
735
     *
736
     * Returns the OFFSET value.
737
     *
738
     * @return int
739
     *
740
     */
741 10
    public function getOffset()
742
    {
743 10
        return $this->offset;
744
    }
745
746
    /**
747
     *
748
     * Clears the current select properties; generally used after adding a
749
     * union.
750
     *
751
     * @return null
752
     *
753
     */
754 15
    protected function reset()
755
    {
756 15
        $this->resetFlags();
757 15
        $this->resetCols();
758 15
        $this->resetTables();
759 15
        $this->resetWhere();
760 15
        $this->resetGroupBy();
761 15
        $this->resetHaving();
762 15
        $this->resetOrderBy();
763 15
        $this->limit(0);
764 15
        $this->offset(0);
765 15
        $this->page(0);
766 15
        $this->forUpdate(false);
767 15
    }
768
769
    /**
770
     *
771
     * Resets the columns on the SELECT.
772
     *
773
     * @return $this
774
     *
775
     */
776 15
    public function resetCols()
777
    {
778 15
        $this->cols = array();
779 15
        return $this;
780
    }
781
782
    /**
783
     *
784
     * Resets the FROM and JOIN clauses on the SELECT.
785
     *
786
     * @return $this
787
     *
788
     */
789 15
    public function resetTables()
790
    {
791 15
        $this->from = array();
792 15
        $this->from_key = -1;
793 15
        $this->join = array();
794 15
        $this->table_refs = array();
795 15
        return $this;
796
    }
797
798
    /**
799
     *
800
     * Resets the WHERE clause on the SELECT.
801
     *
802
     * @return $this
803
     *
804
     */
805 15
    public function resetWhere()
806
    {
807 15
        $this->where = array();
808 15
        return $this;
809
    }
810
811
    /**
812
     *
813
     * Resets the GROUP BY clause on the SELECT.
814
     *
815
     * @return $this
816
     *
817
     */
818 15
    public function resetGroupBy()
819
    {
820 15
        $this->group_by = array();
821 15
        return $this;
822
    }
823
824
    /**
825
     *
826
     * Resets the HAVING clause on the SELECT.
827
     *
828
     * @return $this
829
     *
830
     */
831 15
    public function resetHaving()
832
    {
833 15
        $this->having = array();
834 15
        return $this;
835
    }
836
837
    /**
838
     *
839
     * Resets the ORDER BY clause on the SELECT.
840
     *
841
     * @return $this
842
     *
843
     */
844 15
    public function resetOrderBy()
845
    {
846 15
        $this->order_by = array();
847 15
        return $this;
848
    }
849
850
    /**
851
     *
852
     * Resets the UNION and UNION ALL clauses on the SELECT.
853
     *
854
     * @return $this
855
     *
856
     */
857
    public function resetUnions()
858
    {
859
        $this->union = array();
860
        return $this;
861
    }
862
863
    /**
864
     *
865
     * Builds this query object into a string.
866
     *
867
     * @return string
868
     *
869
     */
870 184
    protected function build()
871
    {
872
        return 'SELECT'
873 184
            . $this->buildFlags()
874 184
            . $this->buildCols()
875 179
            . $this->buildFrom() // includes JOIN
876 179
            . $this->buildWhere()
877 179
            . $this->buildGroupBy()
878 179
            . $this->buildHaving()
879 179
            . $this->buildOrderBy()
880 179
            . $this->buildLimit()
881 179
            . $this->buildForUpdate();
882
    }
883
884
    /**
885
     *
886
     * Builds the columns clause.
887
     *
888
     * @return string
889
     *
890
     * @throws Exception when there are no columns in the SELECT.
891
     *
892
     */
893 184
    protected function buildCols()
894
    {
895 184
        if (empty($this->cols)) {
896 5
            throw new Exception('No columns in the SELECT.');
897
        }
898
899 179
        $cols = array();
900 179
        foreach ($this->cols as $key => $val) {
901 179
            if (is_int($key)) {
902 179
                $cols[] = $this->quoter->quoteNamesIn($val);
903
            } else {
904 179
                $cols[] = $this->quoter->quoteNamesIn("$val AS $key");
905
            }
906
        }
907
908 179
        return $this->indentCsv($cols);
909
    }
910
911
    /**
912
     *
913
     * Builds the FROM clause.
914
     *
915
     * @return string
916
     *
917
     */
918 179
    protected function buildFrom()
919
    {
920 179
        if (empty($this->from)) {
921 60
            return ''; // not applicable
922
        }
923
924 119
        $refs = array();
925 119
        foreach ($this->from as $from_key => $from) {
926 119
            if (isset($this->join[$from_key])) {
927 55
                $from = array_merge($from, $this->join[$from_key]);
928
            }
929 119
            $refs[] = implode(PHP_EOL, $from);
930
        }
931 119
        return PHP_EOL . 'FROM' . $this->indentCsv($refs);
932
    }
933
934
    /**
935
     *
936
     * Builds the GROUP BY clause.
937
     *
938
     * @return string
939
     *
940
     */
941 179
    protected function buildGroupBy()
942
    {
943 179
        if (empty($this->group_by)) {
944 174
            return ''; // not applicable
945
        }
946
947 5
        return PHP_EOL . 'GROUP BY' . $this->indentCsv($this->group_by);
948
    }
949
950
    /**
951
     *
952
     * Builds the HAVING clause.
953
     *
954
     * @return string
955
     *
956
     */
957 179
    protected function buildHaving()
958
    {
959 179
        if (empty($this->having)) {
960 164
            return ''; // not applicable
961
        }
962
963 15
        return PHP_EOL . 'HAVING' . $this->indent($this->having);
964
    }
965
966
    /**
967
     *
968
     * Builds the FOR UPDATE clause.
969
     *
970
     * @return string
971
     *
972
     */
973 179
    protected function buildForUpdate()
974
    {
975 179
        if (! $this->for_update) {
976 174
            return ''; // not applicable
977
        }
978
979 5
        return PHP_EOL . 'FOR UPDATE';
980
    }
981
982
    /**
983
     *
984
     * Adds a WHERE condition to the query by AND. If the condition has
985
     * ?-placeholders, additional arguments to the method will be bound to
986
     * those placeholders sequentially.
987
     *
988
     * @param string $cond The WHERE condition.
989
     * @param mixed ...$bind arguments to bind to placeholders
990
     *
991
     * @return $this
992
     *
993
     */
994 30
    public function where($cond, ...$bind)
995
    {
996 30
        $this->addWhere('AND', $cond, ...$bind);
997 30
        return $this;
998
    }
999
1000
    /**
1001
     *
1002
     * Adds a WHERE condition to the query by OR. If the condition has
1003
     * ?-placeholders, additional arguments to the method will be bound to
1004
     * those placeholders sequentially.
1005
     *
1006
     * @param string $cond The WHERE condition.
1007
     * @param mixed ...$bind arguments to bind to placeholders
1008
     *
1009
     * @return $this
1010
     *
1011
     * @see where()
1012
     *
1013
     */
1014 5
    public function orWhere($cond, ...$bind)
1015
    {
1016 5
        $this->addWhere('OR', $cond, ...$bind);
1017 5
        return $this;
1018
    }
1019
1020
    /**
1021
     *
1022
     * Sets a limit count on the query.
1023
     *
1024
     * @param int $limit The number of rows to select.
1025
     *
1026
     * @return $this
1027
     *
1028
     */
1029 30
    public function limit($limit)
1030
    {
1031 30
        $this->limit = (int) $limit;
1032 30
        if ($this->page) {
1033 5
            $this->page = 0;
1034 5
            $this->offset = 0;
1035
        }
1036 30
        return $this;
1037
    }
1038
1039
    /**
1040
     *
1041
     * Sets a limit offset on the query.
1042
     *
1043
     * @param int $offset Start returning after this many rows.
1044
     *
1045
     * @return $this
1046
     *
1047
     */
1048 30
    public function offset($offset)
1049
    {
1050 30
        $this->offset = (int) $offset;
1051 30
        if ($this->page) {
1052 5
            $this->page = 0;
1053 5
            $this->limit = 0;
1054
        }
1055 30
        return $this;
1056
    }
1057
1058
    /**
1059
     *
1060
     * Adds a column order to the query.
1061
     *
1062
     * @param array $spec The columns and direction to order by.
1063
     *
1064
     * @return $this
1065
     *
1066
     */
1067 5
    public function orderBy(array $spec)
1068
    {
1069 5
        return $this->addOrderBy($spec);
1070
    }
1071
}
1072