CompilerCache::hashSelectQuery()   F
last analyzed

Complexity

Conditions 19
Paths 6144

Size

Total Lines 82
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 19.0036

Importance

Changes 1
Bugs 1 Features 1
Metric Value
cc 19
eloc 48
c 1
b 1
f 1
nc 6144
nop 2
dl 0
loc 82
ccs 45
cts 46
cp 0.9783
crap 19.0036
rs 0.3499

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Driver;
13
14
use Cycle\Database\Injection\Expression;
15
use Cycle\Database\Injection\Fragment;
16
use Cycle\Database\Injection\FragmentInterface;
17
use Cycle\Database\Injection\JsonExpression;
18
use Cycle\Database\Injection\Parameter;
19
use Cycle\Database\Injection\ParameterInterface;
20
use Cycle\Database\Query\QueryInterface;
21
use Cycle\Database\Query\QueryParameters;
22
use Cycle\Database\Query\SelectQuery;
23
24
/**
25
 * Caches calculated queries. Code in this class is performance optimized.
26
 */
27
final class CompilerCache implements CompilerInterface
28
{
29
    private array $cache = [];
30 72
31
    public function __construct(
32
        private CachingCompilerInterface $compiler,
33 72
    ) {}
34
35
    /**
36
     * @psalm-param non-empty-string $identifier
37
     */
38 2600
    public function quoteIdentifier(string $identifier): string
39
    {
40 2600
        return $this->compiler->quoteIdentifier($identifier);
41
    }
42
43
    /**
44
     * @psalm-return non-empty-string
45
     */
46 1946
    public function compile(QueryParameters $params, string $prefix, FragmentInterface $fragment): string
47
    {
48 1946
        if ($fragment->getType() === self::SELECT_QUERY) {
49 1344
            $queryHash = $prefix . $this->hashSelectQuery($params, $fragment->getTokens());
50
51 1344
            if (isset($this->cache[$queryHash])) {
52 628
                return $this->cache[$queryHash];
53
            }
54
55 984
            return $this->cache[$queryHash] = $this->compiler->compile(
56 984
                new QueryParameters(),
57
                $prefix,
58
                $fragment,
59
            );
60
        }
61
62 860
        if ($fragment->getType() === self::INSERT_QUERY) {
63 348
            $tokens = $fragment->getTokens();
64
65 348
            if (\count($tokens['values']) === 1) {
66 250
                $queryHash = $prefix . $this->hashInsertQuery($params, $tokens);
67 250
                if (isset($this->cache[$queryHash])) {
68 200
                    return $this->cache[$queryHash];
69
                }
70
71 62
                return $this->cache[$queryHash] = $this->compiler->compile(
72 62
                    new QueryParameters(),
73
                    $prefix,
74
                    $fragment,
75
                );
76
            }
77
        }
78
79 636
        return $this->compiler->compile(
80 636
            $params,
81
            $prefix,
82
            $fragment,
83
        );
84
    }
85
86
    /**
87
     * @psalm-return non-empty-string
88
     */
89 250
    protected function hashInsertQuery(QueryParameters $params, array $tokens): string
90
    {
91 250
        foreach ((array) ($tokens['return'] ?? []) as $return) {
92 250
            if ($return instanceof FragmentInterface) {
93 250
                foreach ($return->getTokens()['parameters'] as $param) {
94
                    $params->push($param);
95
                }
96
            }
97
        }
98
99
        $hash = \sprintf(
100
            'i_%s%s_r%s',
101
            $tokens['table'],
102
            \implode('_', $tokens['columns']),
103
            \implode('_', (array) ($tokens['return'] ?? [])),
104 250
        );
105
        foreach ($tokens['values'] as $value) {
106
            if ($value instanceof FragmentInterface) {
107
                if ($value instanceof Expression || $value instanceof Fragment) {
108 250
                    foreach ($tokens['parameters'] as $param) {
109 250
                        $params->push($param);
110 250
                    }
111
                }
112
113
                $hash .= $value;
114 250
                continue;
115 250
            }
116
117
            if (!$value instanceof ParameterInterface) {
118 250
                $value = new Parameter($value);
119
            }
120
121 250
            if ($value->isArray()) {
122 250
                foreach ($value->getValue() as $child) {
123
                    if ($child instanceof FragmentInterface) {
124
                        if ($child instanceof \Stringable) {
125
                            $hash .= '_F_' . $child;
126
                        }
127
                        continue;
128
                    }
129 250
130
                    if (!$child instanceof ParameterInterface) {
131
                        $child = new Parameter($child);
132
                    }
133
134
                    $params->push($child);
135 1344
                }
136
137
                $hash .= 'P?';
138 1344
                continue;
139 2
            }
140
141 1344
            $params->push($value);
142
            $hash .= 'P?';
143
        }
144 1344
145 1344
        return $hash;
146 6
    }
147 6
148 6
    /**
149
     * @psalm-return non-empty-string
150
     */
151 1344
    protected function hashSelectQuery(QueryParameters $params, array $tokens): string
152
    {
153
        // stable part of hash
154 1344
        if (\is_array($tokens['distinct']) && isset($tokens['distinct']['on'])) {
155
            $hash = 's_' . $tokens['forUpdate'] . '_on_' . $tokens['distinct']['on'];
156 1344
        } else {
157 210
            $hash = 's_' . $tokens['forUpdate'] . '_' . $tokens['distinct'];
158
        }
159 210
160 6
        foreach ($tokens['from'] as $table) {
161 6
            if ($table instanceof SelectQuery) {
162 6
                $hash .= 's_' . ($table->getPrefix() ?? '');
163
                $hash .= $this->hashSelectQuery($params, $table->getTokens());
164
                continue;
165 204
            }
166 204
167
            $hash .= $table;
168
        }
169 1344
170 648
        $hash .= $this->hashColumns($params, $tokens['columns']);
171
172
        foreach ($tokens['join'] as $join) {
173 1344
            $hash .= 'j' . $join['alias'] . \str_replace(['JOIN', ' '], '', $join['type']);
174 156
175
            if ($join['outer'] instanceof SelectQuery) {
176
                $hash .= $join['outer']->getPrefix() === null ? '' : 'p_' . $join['outer']->getPrefix();
177 1344
                $hash .= $this->hashSelectQuery($params, $join['outer']->getTokens());
178
            } else {
179 1344
                $hash .= $join['outer'];
180 102
            }
181
182
            $hash .= 'on' . $this->hashWhere($params, $join['on']);
183 1344
        }
184
185 1344
        if ($tokens['where'] !== []) {
186 18
            $hash .= 'w' . $this->hashWhere($params, $tokens['where']);
187 18
        }
188 18
189 18
        if ($tokens['having'] !== []) {
190 18
            $hash .= 'h' . $this->hashWhere($params, $tokens['having']);
191
        }
192
193
        $hash .= \implode(',', $tokens['groupBy']);
194
195
        $hash .= $this->hashOrderBy($params, $tokens['orderBy']);
196 1344
197
        $hash .= $this->compiler->hashLimit($params, $tokens);
198
199
        foreach ($tokens['union'] as $union) {
200
            $hash .= $union[0];
201
            if ($union[1] instanceof SelectQuery) {
202 990
                $hash .= $union[1]->getPrefix() === null ? '' : 'p_' . $union[1]->getPrefix();
203
                $hash .= $this->hashSelectQuery($params, $union[1]->getTokens());
204 990
                continue;
205 990
            }
206
207 990
            $hash .= $union[1];
208
        }
209 990
210 990
        foreach ($tokens['intersect'] as $intersect) {
211 180
            $hash .= $intersect[0];
212 180
            if ($intersect[1] instanceof SelectQuery) {
213
                $hash .= $intersect[1]->getPrefix() === null ? '' : 'i_' . $intersect[1]->getPrefix();
214
                $hash .= $this->hashSelectQuery($params, $intersect[1]->getTokens());
215 984
                continue;
216 6
            }
217 6
218 6
            $hash .= $intersect[1];
219
        }
220
221
        foreach ($tokens['except'] as $except) {
222 6
            $hash .= $except[0];
223 6
            if ($except[1] instanceof SelectQuery) {
224
                $hash .= $except[1]->getPrefix() === null ? '' : 'e_' . $except[1]->getPrefix();
225
                $hash .= $this->hashSelectQuery($params, $except[1]->getTokens());
226 984
                continue;
227 18
            }
228 18
229 984
            $hash .= $except[1];
230 6
        }
231
232 978
        return $hash;
233 12
    }
234 12
235
    /**
236
     * @psalm-return non-empty-string
237
     */
238 978
    protected function hashWhere(QueryParameters $params, array $where): string
239
    {
240
        $hash = '';
241
        foreach ($where as $condition) {
242 984
            // OR/AND keyword
243 12
            [$boolean, $context] = $condition;
244 6
245
            $hash .= $boolean;
246
            if (\is_string($context)) {
247
                $hash .= $context;
248 984
                continue;
249
            }
250 984
251 30
            if ($context instanceof FragmentInterface) {
252 30
                if (
253 984
                    $context instanceof Expression ||
254 816
                    $context instanceof Fragment ||
255
                    $context instanceof JsonExpression
256 264
                ) {
257 264
                    foreach ($context->getTokens()['parameters'] as $param) {
258 12
                        $params->push($param);
259
                    }
260
                }
261
262 264
                $hash .= $context;
263
                continue;
264
            }
265 984
266 48
            if ($context[0] instanceof QueryInterface) {
267
                $hash .= $context[0]->getPrefix() === null ? '' : 'p_' . $context[0]->getPrefix();
268
                $hash .= $this->hashSelectQuery($params, $context[0]->getTokens());
269 48
            } elseif ($context[0] instanceof ParameterInterface) {
270 48
                $hash .= $this->hashParam($params, $context[0]);
271
            } else {
272
                if ($context[0] instanceof Expression || $context[0] instanceof Fragment) {
273
                    foreach ($context[0]->getTokens()['parameters'] as $param) {
274
                        $params->push($param);
275
                    }
276
                }
277
278
                $hash .= $context[0];
279
            }
280
281
            // operator
282
            if ($context[1] instanceof Expression || $context[1] instanceof Fragment) {
283 990
                foreach ($context[1]->getTokens()['parameters'] as $param) {
284
                    $params->push($param);
285
                }
286
            }
287
288
            $hash .= $context[1];
289 1344
290
            if ($context[2] instanceof QueryInterface) {
291 1344
                $hash .= $context[2]->getPrefix() === null ? '' : 'p_' . $context[2]->getPrefix();
292 1344
                $hash .= $this->hashSelectQuery($params, $context[2]->getTokens());
293 1344
            } elseif ($context[2] instanceof ParameterInterface) {
294 48
                $hash .= $this->hashParam($params, $context[2]);
295 12
            } else {
296
                if ($context[2] instanceof Expression || $context[2] instanceof Fragment) {
297
                    foreach ($context[2]->getTokens()['parameters'] as $param) {
298
                        $params->push($param);
299 1344
                    }
300
                }
301
302 1344
                $hash .= $context[2];
303
            }
304
305
            if (isset($context[3])) {
306
                if ($context[3] instanceof QueryInterface) {
307
                    $hash .= $context[3]->getPrefix() === null ? '' : 'p_' . $context[3]->getPrefix();
308 816
                    $hash .= $this->hashSelectQuery($params, $context[3]->getTokens());
309
                } elseif ($context[3] instanceof ParameterInterface) {
310 816
                    $hash .= $this->hashParam($params, $context[3]);
311 24
                } else {
312
                    if ($context[3] instanceof Expression || $context[3] instanceof Fragment) {
313
                        foreach ($context[3]->getTokens()['parameters'] as $param) {
314 792
                            $params->push($param);
315
                        }
316 792
                    }
317 42
318
                    $hash .= $context[3];
319
                }
320 774
            }
321
        }
322
323
        return $hash;
324
    }
325
326
    /**
327
     * @psalm-return non-empty-string
328
     */
329
    protected function hashColumns(QueryParameters $params, array $columns): string
330
    {
331
        $hash = '';
332
        foreach ($columns as $column) {
333
            if ($column instanceof Expression || $column instanceof Fragment) {
334
                foreach ($column->getTokens()['parameters'] as $param) {
335
                    $params->push($param);
336
                }
337
            }
338
339
            $hash .= (string) $column . ',';
340
        }
341
342
        return $hash;
343
    }
344
345
    /**
346
     * @psalm-return non-empty-string
347
     */
348
    private function hashParam(QueryParameters $params, ParameterInterface $param): string
349
    {
350
        if ($param->isNull()) {
351
            return 'N';
352
        }
353
354
        if ($param->isArray()) {
355
            $simpleParams = [];
356
            foreach ($param->getValue() as $value) {
357
                if ($value instanceof FragmentInterface) {
358
                    foreach ($value->getTokens()['parameters'] as $fragmentParam) {
359
                        $params->push($fragmentParam);
360
                    }
361
                } else {
362
                    $simpleParams[] = $value;
363
                }
364
            }
365
366
            if ($simpleParams !== []) {
367
                $params->push(new Parameter($simpleParams));
368
            }
369
370
            return 'A' . \count($param->getValue());
371
        }
372
373
        $params->push($param);
374
375
        return '?';
376
    }
377
378
    private function hashOrderBy(QueryParameters $params, array $tokens): string
379
    {
380
        $hash = '';
381
        foreach ($tokens as $order) {
382
            if ($order[0] instanceof FragmentInterface) {
383
                foreach ($order[0]->getTokens()['parameters'] as $param) {
384
                    $params->push($param);
385
                }
386
            }
387
            $hash .= $order[0] . $order[1];
388
        }
389
390
        return $hash;
391
    }
392
}
393