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

CompilerCache::hashInsertQuery()   C

Complexity

Conditions 15
Paths 60

Size

Total Lines 57
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 21.7224

Importance

Changes 0
Metric Value
cc 15
eloc 32
nc 60
nop 2
dl 0
loc 57
ccs 20
cts 29
cp 0.6897
crap 21.7224
rs 5.9166
c 0
b 0
f 0

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