Completed
Push — 2.x ( be80d1...0b4d06 )
by Aleksei
17s queued 15s
created

CompilerCache::hashWhere()   F

Complexity

Conditions 30
Paths 304

Size

Total Lines 86
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 31.9956

Importance

Changes 0
Metric Value
cc 30
eloc 52
c 0
b 0
f 0
nc 304
nop 2
dl 0
loc 86
rs 2.0333
ccs 40
cts 46
cp 0.8696
crap 31.9956

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
        $hash = 'i_' . $tokens['table'] . implode('_', $tokens['columns']) . '_r' . ($tokens['return'] ?? '');
93 250
        foreach ($tokens['values'] as $value) {
94
            if ($value instanceof FragmentInterface) {
95
                if ($value instanceof Expression || $value instanceof Fragment) {
96
                    foreach ($tokens['parameters'] as $param) {
97
                        $params->push($param);
98
                    }
99
                }
100
101
                $hash .= $value;
102
                continue;
103
            }
104 250
105
            if (!$value instanceof ParameterInterface) {
106
                $value = new Parameter($value);
107
            }
108 250
109 250
            if ($value->isArray()) {
110 250
                foreach ($value->getValue() as $child) {
111
                    if ($child instanceof FragmentInterface) {
112
                        continue;
113
                    }
114 250
115 250
                    if (!$child instanceof ParameterInterface) {
116
                        $child = new Parameter($child);
117
                    }
118 250
119
                    $params->push($child);
120
                }
121 250
122 250
                $hash .= 'P?';
123
                continue;
124
            }
125
126
            $params->push($value);
127
            $hash .= 'P?';
128
        }
129 250
130
        return $hash;
131
    }
132
133
    /**
134
     * @psalm-return non-empty-string
135 1344
     */
136
    protected function hashSelectQuery(QueryParameters $params, array $tokens): string
137
    {
138 1344
        // stable part of hash
139 2
        if (is_array($tokens['distinct']) && isset($tokens['distinct']['on'])) {
140
            $hash = 's_' . $tokens['forUpdate'] . '_on_' . $tokens['distinct']['on'];
141 1344
        } else {
142
            $hash = 's_' . $tokens['forUpdate'] . '_' . $tokens['distinct'];
143
        }
144 1344
145 1344
        foreach ($tokens['from'] as $table) {
146 6
            if ($table instanceof SelectQuery) {
147 6
                $hash .= 's_' . ($table->getPrefix() ?? '');
148 6
                $hash .= $this->hashSelectQuery($params, $table->getTokens());
149
                continue;
150
            }
151 1344
152
            $hash .= $table;
153
        }
154 1344
155
        $hash .= $this->hashColumns($params, $tokens['columns']);
156 1344
157 210
        foreach ($tokens['join'] as $join) {
158
            $hash .= 'j' . $join['alias'] . $join['type'];
159 210
160 6
            if ($join['outer'] instanceof SelectQuery) {
161 6
                $hash .= $join['outer']->getPrefix() === null ? '' : 'p_' . $join['outer']->getPrefix();
162 6
                $hash .= $this->hashSelectQuery($params, $join['outer']->getTokens());
163
            } else {
164
                $hash .= $join['outer'];
165 204
            }
166 204
167
            $hash .= 'on' . $this->hashWhere($params, $join['on']);
168
        }
169 1344
170 648
        if ($tokens['where'] !== []) {
171
            $hash .= 'w' . $this->hashWhere($params, $tokens['where']);
172
        }
173 1344
174 156
        if ($tokens['having'] !== []) {
175
            $hash .= 'h' . $this->hashWhere($params, $tokens['having']);
176
        }
177 1344
178
        $hash .= implode(',', $tokens['groupBy']);
179 1344
180 102
        foreach ($tokens['orderBy'] as $order) {
181
            $hash .= $order[0] . $order[1];
182
        }
183 1344
184
        $hash .= $this->compiler->hashLimit($params, $tokens);
185 1344
186 18
        foreach ($tokens['union'] as $union) {
187 18
            $hash .= $union[0];
188 18
            if ($union[1] instanceof SelectQuery) {
189 18
                $hash .= $union[1]->getPrefix() === null ? '' : 'p_' . $union[1]->getPrefix();
190 18
                $hash .= $this->hashSelectQuery($params, $union[1]->getTokens());
191
                continue;
192
            }
193
194
            $hash .= $union[1];
195
        }
196 1344
197
        return $hash;
198
    }
199
200
    /**
201
     * @psalm-return non-empty-string
202 990
     */
203
    protected function hashWhere(QueryParameters $params, array $where): string
204 990
    {
205 990
        $hash = '';
206
        foreach ($where as $condition) {
207 990
            // OR/AND keyword
208
            [$boolean, $context] = $condition;
209 990
210 990
            $hash .= $boolean;
211 180
            if (is_string($context)) {
212 180
                $hash .= $context;
213
                continue;
214
            }
215 984
216 6
            if ($context instanceof FragmentInterface) {
217 6
                if (
218 6
                    $context instanceof Expression ||
219
                    $context instanceof Fragment ||
220
                    $context instanceof JsonExpression
221
                ) {
222 6
                    foreach ($context->getTokens()['parameters'] as $param) {
223 6
                        $params->push($param);
224
                    }
225
                }
226 984
227 18
                $hash .= $context;
228 18
                continue;
229 984
            }
230 6
231
            if ($context[0] instanceof QueryInterface) {
232 978
                $hash .= $context[0]->getPrefix() === null ? '' : 'p_' . $context[0]->getPrefix();
233 12
                $hash .= $this->hashSelectQuery($params, $context[0]->getTokens());
234 12
            } elseif ($context[0] instanceof ParameterInterface) {
235
                $hash .= $this->hashParam($params, $context[0]);
236
            } else {
237
                if ($context[0] instanceof Expression || $context[0] instanceof Fragment) {
238 978
                    foreach ($context[0]->getTokens()['parameters'] as $param) {
239
                        $params->push($param);
240
                    }
241
                }
242 984
243 12
                $hash .= $context[0];
244 6
            }
245
246
            // operator
247
            if ($context[1] instanceof Expression || $context[1] instanceof Fragment) {
248 984
                foreach ($context[1]->getTokens()['parameters'] as $param) {
249
                    $params->push($param);
250 984
                }
251 30
            }
252 30
253 984
            $hash .= $context[1];
254 816
255
            if ($context[2] instanceof QueryInterface) {
256 264
                $hash .= $context[2]->getPrefix() === null ? '' : 'p_' . $context[2]->getPrefix();
257 264
                $hash .= $this->hashSelectQuery($params, $context[2]->getTokens());
258 12
            } elseif ($context[2] instanceof ParameterInterface) {
259
                $hash .= $this->hashParam($params, $context[2]);
260
            } else {
261
                if ($context[2] instanceof Expression || $context[2] instanceof Fragment) {
262 264
                    foreach ($context[2]->getTokens()['parameters'] as $param) {
263
                        $params->push($param);
264
                    }
265 984
                }
266 48
267
                $hash .= $context[2];
268
            }
269 48
270 48
            if (isset($context[3])) {
271
                if ($context[3] instanceof QueryInterface) {
272
                    $hash .= $context[3]->getPrefix() === null ? '' : 'p_' . $context[3]->getPrefix();
273
                    $hash .= $this->hashSelectQuery($params, $context[3]->getTokens());
274
                } elseif ($context[3] instanceof ParameterInterface) {
275
                    $hash .= $this->hashParam($params, $context[3]);
276
                } else {
277
                    if ($context[3] instanceof Expression || $context[3] instanceof Fragment) {
278
                        foreach ($context[3]->getTokens()['parameters'] as $param) {
279
                            $params->push($param);
280
                        }
281
                    }
282
283 990
                    $hash .= $context[3];
284
                }
285
            }
286
        }
287
288
        return $hash;
289 1344
    }
290
291 1344
    /**
292 1344
     * @psalm-return non-empty-string
293 1344
     */
294 48
    protected function hashColumns(QueryParameters $params, array $columns): string
295 12
    {
296
        $hash = '';
297
        foreach ($columns as $column) {
298
            if ($column instanceof Expression || $column instanceof Fragment) {
299 1344
                foreach ($column->getTokens()['parameters'] as $param) {
300
                    $params->push($param);
301
                }
302 1344
            }
303
304
            $hash .= (string) $column . ',';
305
        }
306
307
        return $hash;
308 816
    }
309
310 816
    /**
311 24
     * @psalm-return non-empty-string
312
     */
313
    private function hashParam(QueryParameters $params, ParameterInterface $param): string
314 792
    {
315
        if ($param->isNull()) {
316 792
            return 'N';
317 42
        }
318
319
        if ($param->isArray()) {
320 774
            $simpleParams = [];
321
            foreach ($param->getValue() as $value) {
322
                if ($value instanceof FragmentInterface) {
323
                    foreach ($value->getTokens()['parameters'] as $fragmentParam) {
324
                        $params->push($fragmentParam);
325
                    }
326
                } else {
327
                    $simpleParams[] = $value;
328
                }
329
            }
330
331
            if ($simpleParams !== []) {
332
                $params->push(new Parameter($simpleParams));
333
            }
334
335
            return 'A' . count($param->getValue());
336
        }
337
338
        $params->push($param);
339
340
        return '?';
341
    }
342
}
343