Query   B
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 83.02%

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 8
dl 0
loc 232
ccs 88
cts 106
cp 0.8302
rs 8.8
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getDb() 0 4 1
A getPivot() 0 4 1
A queryDistinct() 0 11 3
A query() 0 10 2
D queryData() 0 44 9
C queryTotalsResult() 0 48 9
A formatResultValues() 0 11 4
A queryDetailsResult() 0 20 4
A sqlFrom() 0 13 2
1
<?php
2
declare(strict_types = 1);
3
namespace EngineWorks\Pivot;
4
5
use EngineWorks\DBAL\DBAL;
6
7
class Query
8
{
9
    /**
10
     * Database Abstraction Layer Object
11
     * @var DBAL
12
     */
13
    private $db;
14
15
    /**
16
     * @var Pivot
17
     */
18
    private $pivot;
19
20
    /**
21
     * Query constructor.
22
     * @param DBAL $db
23
     * @param Pivot $pivot
24
     */
25 10
    public function __construct(DBAL $db, Pivot $pivot)
26
    {
27 10
        $this->db = $db;
28 10
        $this->pivot = $pivot;
29 10
    }
30
31
    /**
32
     * @return DBAL
33
     */
34
    public function getDb(): DBAL
35
    {
36
        return $this->db;
37
    }
38
39
    /**
40
     * @return Pivot
41
     */
42
    public function getPivot(): Pivot
43
    {
44
        return $this->pivot;
45
    }
46
47
    /**
48
     * Perform a query to obtain the distinct values of a fieldname
49
     *
50
     * @param string $fieldname
51
     * @return array
52
     * @throws PivotException if the fieldname does not exists in the source fields
53
     */
54
    public function queryDistinct(string $fieldname) : array
55
    {
56
        if (! $this->pivot->getFieldsCollection()->exists($fieldname)) {
57
            throw new PivotException("Field $fieldname does not exists in the source fields");
58
        }
59
        $sql = 'SELECT DISTINCT ' . $this->db->sqlFieldEscape($fieldname)
60
            . ' FROM ' . $this->sqlFrom()
61
            . ' ORDER BY ' . $this->db->sqlFieldEscape($fieldname)
62
            . ';';
63
        return $this->db->queryArrayOne($sql) ? : [];
64
    }
65
66
    /**
67
     * @return QueryResult
68
     */
69 10
    public function query() : QueryResult
70
    {
71 10
        return new QueryResult(
72 10
            $this->queryTotalsResult(true),
73 9
            ($this->pivot->hasColumns()) ? $this->queryDetailsResult() : null,
74 9
            $this->pivot->getCurrentRows(),
75 9
            $this->pivot->getCurrentColumns(),
76 9
            $this->pivot->getCurrentAggregates()
77
        );
78
    }
79
80
    /**
81
     * @return array
82
     * @throws PivotException
83
     * @throws PivotExceptionQuery
84
     */
85 10
    private function queryData() : array
86
    {
87 10
        $sqlSelects = [];
88
        // SQL: Select group fields
89 10
        foreach ($this->pivot->getCurrentRows() as $currentRow) {
90 8
            $sqlSelects[] = $this->db->sqlFieldEscape($currentRow['fieldname']);
91
        }
92
        // SQL: Select aggregator fields
93 10
        $sqlAggregates = [];
94 10
        foreach ($this->pivot->getAggregatesCollection() as $aggregate) {
95 9
            $sqlAggregates[] = $aggregate->getSQL($this->db);
96
        }
97
        // SQL: Select group fields and aggregates
98 10
        $sqlAllFields = array_merge($sqlSelects, $sqlAggregates);
99 10
        $countFields = count($sqlAllFields);
100 10
        if (! $countFields) { // nothing to select
101 1
            throw new PivotException('Nothing to query');
102
        }
103
104
        // get all elements from filter
105 9
        $sqlWheres = [];
106 9
        foreach ($this->pivot->getFiltersCollection() as $filter) {
107 3
            $sqlWheres[] = '('
108 3
                . $filter->getSQL($this->db, $this->pivot->getFieldElement($filter->getFieldname())->toDBAL())
109 3
                . ')';
110
        }
111
        // SQL Creation
112 9
        $sql = 'SELECT ' . implode(', ', $sqlAllFields)
113 9
            . ' FROM ' . $this->sqlFrom()
114 9
            . ((count($sqlWheres)) ? ' WHERE ' . implode(' AND ', $sqlWheres) : '')
115 9
            . ((count($sqlSelects)) ? ' GROUP BY ' . implode(', ', $sqlSelects) . ' WITH ROLLUP' : '')
116 9
            . ';';
117
118
        // retrieve and return the data
119
        try {
120 9
            $data = $this->db->queryArrayValues($sql);
121
        } catch (\Throwable $ex) {
122
            throw new PivotExceptionQuery('Query error: ' . $ex->getMessage());
123
        }
124 9
        if (! is_array($data)) {
125
            throw new PivotExceptionQuery('Query error:' . $sql);
126
        }
127 9
        return $data;
128
    }
129
130
    /**
131
     * @param bool $sortValues
132
     * @return Result
133
     * @throws PivotException
134
     */
135 10
    private function queryTotalsResult(bool $sortValues) : Result
136
    {
137
        // variable to return
138 10
        $result = new Result('', '');
139
140
        // now all the data including rollup cells are in $data
141
        // we have to generate the output
142 10
        $data = $this->queryData();
143 9
        $dataCount = count($data);
144 9
        $aggregates = $this->pivot->getAggregatesCollection();
145 9
        for ($i = 0; $i < $dataCount; $i++) {
146
            // create the values content
147 9
            $values = [];
148 9
            foreach ($aggregates as $aggregate) {
149 9
                $aggregateName = $aggregate->getAsname();
150 9
                $values[$aggregateName] = $data[$i][$aggregateName];
151
            }
152
            // ahora especificar donde va el resultado
153 9
            $ch = $result;
154 9
            foreach ($this->pivot->getCurrentRows() as $row) {
155 8
                $rowvalue = $data[$i][$row['fieldname']];
156 8
                if (is_null($rowvalue)) {
157 8
                    break;
158
                } else {
159 8
                    if (! $ch->children->exists($rowvalue)) {
160
                        // insert without value ($row is the $fieldname and $rowvalue is the $caption)
161 8
                        $ch->children->addItem(new Result($row['fieldname'], $rowvalue), $rowvalue);
162
                    }
163 8
                    $ch = $ch->children->value($rowvalue);
164
                }
165
            }
166
            // asignar los valores al nodo seleccionado
167 9
            if (! is_null($ch->values)) {
168
                throw new PivotException('Duplicated values when filling the results');
169
            }
170 9
            $ch->values = $values;
171
        }
172
        // do sort
173 9
        if ($sortValues) {
174 9
            $ordering = new ResultOrdering($aggregates->getOrderArray());
175 9
            if ($ordering->isRequired()) {
176 4
                $result->orderBy($ordering);
177
            }
178
        }
179
        // format aggregate values after sort
180 9
        $this->formatResultValues($aggregates, $result);
181 9
        return $result;
182
    }
183
184 9
    private function formatResultValues(Aggregates $aggregates, Result $result)
185
    {
186 9
        foreach ($aggregates as $aggregatorName => $aggregate) {
187 9
            if (null !== $value = $result->getCurrentValue($aggregatorName)) {
188 9
                $result->setCurrentValue($aggregatorName, number_format($value, $aggregate->getDecimals()));
189
            }
190
        }
191 9
        foreach ($result->children as $child) {
192 8
            $this->formatResultValues($aggregates, $child);
193
        }
194 9
    }
195
196
    /**
197
     * @return Result Null if no columns are set
198
     * @throws PivotException If no columns are defined
199
     */
200 5
    private function queryDetailsResult() : Result
201
    {
202 5
        $columns = $this->pivot->getCurrentColumns();
203 5
        if (! count($columns)) {
204
            throw new PivotException('Cannot get details if no columns are defined');
205
        }
206 5
        $pivotDetails = clone $this->pivot;
207 5
        $pivotDetails->clearSelectorRows();
208 5
        $pivotDetails->clearSelectorColumns();
209 5
        foreach ($columns as $column) {
210 5
            $pivotDetails->addRow($column['fieldname']);
211
        }
212 5
        foreach ($this->pivot->getCurrentRows() as $row) {
213 4
            $pivotDetails->addRow($row['fieldname']);
214
        }
215 5
        $queryClone = new self($this->db, $pivotDetails);
216 5
        $result = $queryClone->queryTotalsResult(false);
217 5
        $result->setAsNotRow(count($columns));
218 5
        return $result;
219
    }
220
221
    /**
222
     * Return the FROM part of the SQL statement
223
     * @return string
224
     */
225 9
    private function sqlFrom() : string
226
    {
227
        // do trim including ';' at the right part
228 9
        $sqlSource = rtrim(ltrim($this->pivot->getSource()), "; \t\n\r\x0B\0");
229
        // check if begins with select, if so then insert into a subquery
230 9
        $pos = strpos(strtoupper($sqlSource), 'SELECT');
231 9
        if ($pos === 0) {
232
            $sqlSource = "($sqlSource) AS __pivot__";
233
        } else {
234 9
            $sqlSource = $this->db->sqlTableEscape($sqlSource);
235
        }
236 9
        return $sqlSource;
237
    }
238
}
239