Failed Conditions
Push — 186-self-joins-fail-in-relatio... ( ba3e0e...5bcaaa )
by Bas
08:01 queued 40s
created

CompilesColumns::normalizeColumnReferences()   B

Complexity

Conditions 9
Paths 33

Size

Total Lines 39
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 18
c 0
b 0
f 0
nc 33
nop 3
dl 0
loc 39
ccs 18
cts 18
cp 1
crap 9
rs 8.0555
1
<?php
2
3
declare(strict_types=1);
4
5
namespace LaravelFreelancerNL\Aranguent\Query\Concerns;
6
7
use Exception;
8
use Illuminate\Database\Query\Builder as IlluminateQueryBuilder;
9
use Illuminate\Database\Query\Expression;
10
use LaravelFreelancerNL\Aranguent\Query\Builder;
11
12
trait CompilesColumns
13
{
14
    /**
15
     * @param mixed[] $returnAttributes
16
     * @param mixed[] $returnDocs
17
     * @param Builder $query
18
     * @return mixed[]
19
     */
20 389
    public function processEmptyReturnValues(array $returnAttributes, array $returnDocs, Builder $query): array
21
    {
22 389
        if (empty($returnAttributes) && empty($returnDocs)) {
23 304
            $returnDocs[] = (string) $query->getTableAlias($query->from);
24
25 304
            if ($query->joins !== null) {
26 6
                $returnDocs = $this->mergeJoinResults($query, $returnDocs);
27
            }
28
        }
29 389
        return $returnDocs;
30
    }
31
32
    /**
33
     * @param Builder $query
34
     * @param mixed[] $returnDocs
35
     * @param mixed[] $returnAttributes
36
     * @return mixed[]
37
     */
38 389
    public function processAggregateReturnValues(Builder $query, array $returnDocs, array $returnAttributes): array
39
    {
40 389
        if ($query->aggregate !== null && $query->unions === null) {
41 77
            $returnDocs = [];
42 77
            $returnAttributes = ['aggregate' => 'aggregateResult'];
43
        }
44 389
        return [$returnDocs, $returnAttributes];
45
    }
46
47
    /**
48
     * Compile the "select *" portion of the query.
49
     *
50
     * @param IlluminateQueryBuilder $query
51
     * @param array<mixed> $columns
52
     * @return string|null
53
     * @throws Exception
54
     */
55 389
    protected function compileColumns(IlluminateQueryBuilder $query, $columns)
56
    {
57
        assert($query instanceof Builder);
58
59 389
        if (count($columns) === 1  && !is_string($columns[0]) && is_scalar($columns[0])) {
60
            return 'RETURN '.var_export($columns[0], true);
61 389
        }
62
63 389
        $columns = $this->convertJsonFields($columns);
0 ignored issues
show
Bug introduced by
It seems like convertJsonFields() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

63
        /** @scrutinizer ignore-call */ 
64
        $columns = $this->convertJsonFields($columns);
Loading history...
64
65 389
        [$returnAttributes, $returnDocs] = $this->prepareColumns($query, $columns);
66 389
67 4
        $returnValues = $this->determineReturnValues($query, $returnAttributes, $returnDocs);
68
69
        $return = 'RETURN ';
70 389
        if ($query->distinct) {
71
            $return .= 'DISTINCT ';
72
        }
73
74
        return $return . $returnValues;
75
    }
76
77
    /**
78
     * @param IlluminateQueryBuilder $query
79
     * @param array<mixed> $columns
80
     * @return array<mixed>
81 389
     * @throws Exception
82
     *
83
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
84
     */
85 389
    protected function prepareColumns(IlluminateQueryBuilder $query, array $columns)
86 389
    {
87
        assert($query instanceof Builder);
88 389
89
        $returnDocs = [];
90 389
        $returnAttributes = [];
91 21
92 21
        foreach ($columns as $key => $column) {
93
            // Extract complete documents
94 21
            if (is_string($column) && substr($column, strlen($column) - 2)  === '.*') {
95
                $table = substr($column, 0, strlen($column) - 2);
96
                $returnDocs[] = $query->getTableAlias($table);
97
98 389
                continue;
99 10
            }
100
101 10
            // Extract groups
102
            if (is_array($query->groups) && in_array($column, $query->groups)) {
103
                $returnAttributes[$column] = $this->normalizeColumn($query, $column);
104 380
105 151
                continue;
106
            }
107 151
108
            if (is_string($column) && $column != null && $column != '*') {
109
                [$column, $alias] = $this->normalizeStringColumn($query, $key, $column);
110
111
                if (isset($returnAttributes[$alias]) && is_array($column)) {
112
                    $returnAttributes[$alias] = array_merge_recursive(
113
                        $returnAttributes[$alias],
114 151
                        $this->normalizeColumn($query, $column),
115
                    );
116
                    continue;
117
                }
118 389
                $returnAttributes[$alias] = $column;
119 389
            }
120 389
        }
121 389
122
        return [
123
            $returnAttributes,
124
            $returnDocs,
125
        ];
126
    }
127 330
128
    /**
129
     * @throws Exception
130
     */
131 330
    protected function normalizeColumn(IlluminateQueryBuilder $query, mixed $column, ?string $table = null): mixed
132
    {
133
        assert($query instanceof Builder);
134
135
        if ($column instanceof Expression) {
136 330
            return $column;
137 330
        }
138 330
139
        if (
140 10
            is_array($query->groups)
141
            && in_array($column, $query->groups)
142
            && debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT)[1]['function'] !== "compileGroups"
143 330
        ) {
144
            return $this->wrap($query->convertIdToKey($column));
0 ignored issues
show
Bug introduced by
It seems like wrap() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

144
            return $this->/** @scrutinizer ignore-call */ wrap($query->convertIdToKey($column));
Loading history...
145
        }
146
147
        if (is_array($column)) {
148
            $column = $this->convertJsonFields($column);
149
150
            foreach ($column as $key => $value) {
151
                $column[$key] = $this->normalizeColumn($query, $value, $table);
152 330
            }
153 1
            return $column;
154
        }
155
156
        if ($query->isVariable($column)) {
157 330
            return $this->wrap($column);
158 330
        }
159
160
161
        $column = $this->convertJsonFields($column);
162 330
163
        $column = $query->convertIdToKey($column);
164
165
        //We check for an existing alias to determine of the first reference is a table.
166
        // In which case we replace it with the alias.
167
        return $this->wrap($this->normalizeColumnReferences($query, $column, $table));
168
    }
169
170
    /**
171
     * @param Builder $query
172
     * @param int|string $key
173 151
     * @param string $column
174
     * @param string|null $table
175 151
     * @return array<mixed>
176
     * @throws Exception
177 151
     */
178
    protected function normalizeStringColumn(Builder $query, int|string $key, string $column, ?string $table = null): array
179 151
    {
180
        [$column, $alias] = $query->extractAlias($column, $key);
181
182 151
        $column = $query->convertIdToKey($column);
183
184 151
        $column = $this->wrap($this->normalizeColumnReferences($query, $column, $table));
185
186
        /** @phpstan-ignore-next-line */
187
        $alias = $this->cleanAlias($query, $alias);
188
189
        return [$column, $alias];
190
    }
191
192
193
    /**
194 353
     * @param IlluminateQueryBuilder $query
195
     * @param string $column
196
     * @param string|null $table
197
     * @return string
198 353
     */
199 121
    protected function normalizeColumnReferences(IlluminateQueryBuilder $query, string $column, ?string $table = null): string
200
    {
201
        assert($query instanceof Builder);
202 322
203 322
        // if the given column is a table alias or variable return it.
204
        if ($query->isReference($column)) {
205
            return $column;
206 322
        }
207
208 322
        if ($table == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $table of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
209
            $table = (string) $this->getValue($query->from);
0 ignored issues
show
Bug introduced by
It seems like getValue() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

209
            $table = (string) $this->/** @scrutinizer ignore-call */ getValue($query->from);
Loading history...
210 322
        }
211 50
212
        $references = explode('.', $column);
213
214 322
        // If an alias exists for the first element, replace it by the alias.
215 1
        $tableAlias = $query->getTableAlias($references[0]);
216 1
217
        if (isset($tableAlias)) {
218
            $references[0] = $tableAlias;
219
        }
220 322
221 310
        if (array_key_exists('groupsVariable', $query->tableAliases)) {
222
            $tableAlias = 'groupsVariable';
223
            array_unshift($references, $tableAlias);
224 322
        }
225 3
226 3
        // geen tableAlias, table is parent...waarom geen tableAlias?
227
        if ($tableAlias === null  && array_key_exists($table, $query->tableAliases)) {
228
            array_unshift($references, $query->tableAliases[$table]);
229
        }
230 322
231
        if ($tableAlias === null && !$query->isReference($references[0])) {
232
            $tableAlias = $query->generateTableAlias($table);
233 151
            array_unshift($references, $tableAlias);
234
        }
235
236
        /** @phpstan-ignore-next-line */
237 151
        return implode('.', $references);
238
    }
239
240
    /**
241 151
     * Remove any table, tableAlias or variable prefixes from the alias.
242 149
     *
243
     * @param IlluminateQueryBuilder $query
244
     * @param int|string|null $alias
245 8
     * @return int|string|null
246
     */
247
    protected function cleanAlias(IlluminateQueryBuilder $query, int|null|string $alias): int|string|null
248 8
    {
249 8
        assert($query instanceof Builder);
250
251 6
        if (!is_string($alias)) {
252
            return $alias;
253
        }
254 2
255
        if (!str_contains($alias, '.')) {
256 2
            return $alias;
257
        }
258
259
        $elements = explode('.', $alias);
260
261
        if (
262
            !$query->isTable($elements[0])
263
            && !$query->isReference($elements[0])
264
        ) {
265
            return $alias;
266 389
        }
267
268
        array_shift($elements);
269
270
        return implode($elements);
271 389
    }
272
273
274 389
    /**
275
     * @param IlluminateQueryBuilder $query
276
     * @param array<string> $returnAttributes
277
     * @param array<string>  $returnDocs
278 389
     * @return string
279 389
     */
280 389
    protected function determineReturnValues(IlluminateQueryBuilder $query, $returnAttributes = [], $returnDocs = []): string
281
    {
282 3
        assert($query instanceof Builder);
283
284
        // If nothing was specifically requested, we return everything.
285 389
        $returnDocs = $this->processEmptyReturnValues($returnAttributes, $returnDocs, $query);
286 165
287
        // Aggregate functions only return the aggregate, so we can clear out everything else.
288
        list($returnDocs, $returnAttributes) = $this->processAggregateReturnValues($query, $returnDocs, $returnAttributes);
289 389
290
        // Return a single value for certain subqueries
291 389
        if (
292
            $query->returnSingleValue === true
293
            && count($returnAttributes) === 1
294
            && empty($returnDocs)
295
        ) {
296
            return reset($returnAttributes);
297
        }
298 389
299
        if (!empty($returnAttributes)) {
300 389
            $returnDocs[] = $this->generateAqlObject($returnAttributes);
0 ignored issues
show
Bug introduced by
It seems like generateAqlObject() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

300
            /** @scrutinizer ignore-call */ 
301
            $returnDocs[] = $this->generateAqlObject($returnAttributes);
Loading history...
301
        }
302 389
303 27
        $values = $this->mergeReturnDocs($returnDocs);
304
305
        return $values;
306 385
    }
307
308
    /**
309
     * @param array<string> $returnDocs
310
     * @return string
311
     */
312
    protected function mergeReturnDocs($returnDocs)
313
    {
314 6
        $returnDocs = array_filter($returnDocs);
315
316
        if (sizeOf($returnDocs) > 1) {
317
            return 'MERGE(' . implode(', ', $returnDocs) . ')';
318 6
        }
319
320
        if (empty($returnDocs)) {
321
            return '';
322 6
        }
323 6
324
        return reset($returnDocs);
325 6
    }
326
327
    /**
328 6
     * @param IlluminateQueryBuilder $query
329
     * @param array<string> $returnDocs
330
     * @return array<string>
331 6
     */
332
    protected function mergeJoinResults(IlluminateQueryBuilder $query, $returnDocs = []): array
333
    {
334
        assert($query instanceof Builder);
335
336
        if (!is_array($query->joins)) {
337
            return $returnDocs;
338
        }
339
340
        foreach ($query->joins as $join) {
341
            $tableAlias = $query->getTableAlias($join->table);
342
343
            if (!isset($tableAlias)) {
344
                $tableAlias = $query->generateTableAlias($join->table);
345
            }
346
            $returnDocs[] = (string) $tableAlias;
347
        }
348
349
        return $returnDocs;
350
    }
351
}
352