Completed
Push — 2.x ( 122163...e6f7bd )
by Aleksei
26s queued 15s
created

CompilerCache::hashSelectQuery()   F

Complexity

Conditions 13
Paths 384

Size

Total Lines 60
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 13.0051

Importance

Changes 1
Bugs 1 Features 1
Metric Value
cc 13
eloc 34
c 1
b 1
f 1
nc 384
nop 2
dl 0
loc 60
ccs 31
cts 32
cp 0.9688
crap 13.0051
rs 3.4833

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