1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Spiral Framework. |
4
|
|
|
* |
5
|
|
|
* @license MIT |
6
|
|
|
* @author Anton Titov (Wolfy-J) |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Spiral\Database\Entities; |
10
|
|
|
|
11
|
|
|
use Spiral\Database\Builders\QueryBuilder; |
12
|
|
|
use Spiral\Database\Exceptions\CompilerException; |
13
|
|
|
use Spiral\Database\Injections\ExpressionInterface; |
14
|
|
|
use Spiral\Database\Injections\FragmentInterface; |
15
|
|
|
use Spiral\Database\Injections\ParameterInterface; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Responsible for conversion of set of query parameters (where tokens, table names and etc) into |
19
|
|
|
* sql to be send into specific Driver. |
20
|
|
|
* |
21
|
|
|
* Source of Compiler must be optimized in nearest future. |
22
|
|
|
*/ |
23
|
|
|
class QueryCompiler |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* Query types for parameter ordering. |
27
|
|
|
*/ |
28
|
|
|
const SELECT_QUERY = 'select'; |
29
|
|
|
const UPDATE_QUERY = 'update'; |
30
|
|
|
const DELETE_QUERY = 'delete'; |
31
|
|
|
const INSERT_QUERY = 'insert'; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Quotes names and expressions. |
35
|
|
|
* |
36
|
|
|
* @var Quoter |
37
|
|
|
*/ |
38
|
|
|
private $quoter = null; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* QueryCompiler constructor. |
42
|
|
|
* |
43
|
|
|
* @param Quoter $quoter |
44
|
|
|
*/ |
45
|
|
|
public function __construct(Quoter $quoter) |
46
|
|
|
{ |
47
|
|
|
$this->quoter = $quoter; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Prefix associated with compiler. |
52
|
|
|
* |
53
|
|
|
* @return string |
54
|
|
|
*/ |
55
|
|
|
public function getPrefix(): string |
56
|
|
|
{ |
57
|
|
|
return $this->quoter->getPrefix(); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Reset table aliases cache, required if same compiler used twice. |
62
|
|
|
* |
63
|
|
|
* @return self |
64
|
|
|
*/ |
65
|
|
|
public function resetQuoter(): QueryCompiler |
66
|
|
|
{ |
67
|
|
|
$this->quoter->reset(); |
68
|
|
|
|
69
|
|
|
return $this; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Query query identifier, if identified stated as table - table prefix must be added. |
74
|
|
|
* |
75
|
|
|
* @param string $identifier Identifier can include simple column operations and functions, |
76
|
|
|
* having "." in it will automatically force table prefix to first |
77
|
|
|
* value. |
78
|
|
|
* @param bool $isTable Set to true to let quote method know that identified is related |
79
|
|
|
* to table name. |
80
|
|
|
* |
81
|
|
|
* @return string |
82
|
|
|
*/ |
83
|
|
|
public function quote(string $identifier, bool $isTable = false): string |
84
|
|
|
{ |
85
|
|
|
if ($identifier instanceof FragmentInterface) { |
86
|
|
|
return $this->prepareFragment($identifier); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
return $this->quoter->quote($identifier, $isTable); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Sort list of parameters in dbms query specific order, query type must be provided. This |
94
|
|
|
* method was used at times when delete and update queries supported joins, i might need to |
95
|
|
|
* drop it now. |
96
|
|
|
* |
97
|
|
|
* @param string $queryType |
98
|
|
|
* @param array $whereParameters |
99
|
|
|
* @param array $onParameters |
100
|
|
|
* @param array $havingParameters |
101
|
|
|
* @param array $columnIdentifiers Column names (if any). |
102
|
|
|
* |
103
|
|
|
* @return array |
104
|
|
|
*/ |
105
|
|
|
public function orderParameters( |
106
|
|
|
string $queryType, |
107
|
|
|
array $whereParameters = [], |
108
|
|
|
array $onParameters = [], |
109
|
|
|
array $havingParameters = [], |
110
|
|
|
array $columnIdentifiers = [] |
111
|
|
|
): array { |
112
|
|
|
return array_merge($columnIdentifiers, $onParameters, $whereParameters, $havingParameters); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Create insert query using table names, columns and rowsets. Must support both - single and |
117
|
|
|
* batch inserts. |
118
|
|
|
* |
119
|
|
|
* @param string $table |
120
|
|
|
* @param array $columns |
121
|
|
|
* @param FragmentInterface[] $rowsets Every rowset has to be convertable into string. Raw data |
122
|
|
|
* not allowed! |
123
|
|
|
* |
124
|
|
|
* @return string |
125
|
|
|
* |
126
|
|
|
* @throws CompilerException |
127
|
|
|
*/ |
128
|
|
|
public function compileInsert(string $table, array $columns, array $rowsets): string |
129
|
|
|
{ |
130
|
|
|
if (empty($columns)) { |
131
|
|
|
throw new CompilerException( |
132
|
|
|
'Unable to build insert statement, columns must be set' |
133
|
|
|
); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
if (empty($rowsets)) { |
137
|
|
|
throw new CompilerException( |
138
|
|
|
'Unable to build insert statement, at least one value set must be provided' |
139
|
|
|
); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
//To add needed prefixes (if any) |
143
|
|
|
$table = $this->quote($table, true); |
144
|
|
|
|
145
|
|
|
//Compiling list of columns |
146
|
|
|
$columns = $this->prepareColumns($columns); |
147
|
|
|
|
148
|
|
|
//Simply joining every rowset |
149
|
|
|
$rowsets = implode(",\n", $rowsets); |
150
|
|
|
|
151
|
|
|
return "INSERT INTO {$table} ({$columns})\nVALUES {$rowsets}"; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Create update statement. |
156
|
|
|
* |
157
|
|
|
* @param string $table |
158
|
|
|
* @param array $updates |
159
|
|
|
* @param array $whereTokens |
160
|
|
|
* |
161
|
|
|
* @return string |
162
|
|
|
* |
163
|
|
|
* @throws CompilerException |
164
|
|
|
*/ |
165
|
|
|
public function compileUpdate(string $table, array $updates, array $whereTokens = []): string |
166
|
|
|
{ |
167
|
|
|
$table = $this->quote($table, true); |
168
|
|
|
|
169
|
|
|
//Preparing update column statement |
170
|
|
|
$updates = $this->prepareUpdates($updates); |
171
|
|
|
|
172
|
|
|
//Where statement is optional for update queries |
173
|
|
|
$whereStatement = $this->optional("\nWHERE", $this->compileWhere($whereTokens)); |
174
|
|
|
|
175
|
|
|
return rtrim("UPDATE {$table}\nSET {$updates} {$whereStatement}"); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Create delete statement. |
180
|
|
|
* |
181
|
|
|
* @param string $table |
182
|
|
|
* @param array $whereTokens |
183
|
|
|
* |
184
|
|
|
* @return string |
185
|
|
|
* |
186
|
|
|
* @throws CompilerException |
187
|
|
|
*/ |
188
|
|
|
public function compileDelete(string $table, array $whereTokens = []): string |
189
|
|
|
{ |
190
|
|
|
$table = $this->quote($table, true); |
191
|
|
|
|
192
|
|
|
//Where statement is optional for delete query (which is weird) |
193
|
|
|
$whereStatement = $this->optional("\nWHERE", $this->compileWhere($whereTokens)); |
194
|
|
|
|
195
|
|
|
return rtrim("DELETE FROM {$table} {$whereStatement}"); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Create select statement. Compiler must validly resolve table and column aliases used in |
200
|
|
|
* conditions and joins. |
201
|
|
|
* |
202
|
|
|
* @param array $fromTables |
203
|
|
|
* @param bool|string $distinct String only for PostgresSQL. |
204
|
|
|
* @param array $columns |
205
|
|
|
* @param array $joinTokens |
206
|
|
|
* @param array $whereTokens |
207
|
|
|
* @param array $havingTokens |
208
|
|
|
* @param array $grouping |
209
|
|
|
* @param array $ordering |
210
|
|
|
* @param int $limit |
211
|
|
|
* @param int $offset |
212
|
|
|
* @param array $unionTokens |
213
|
|
|
* |
214
|
|
|
* @return string |
215
|
|
|
* |
216
|
|
|
* @throws CompilerException |
217
|
|
|
*/ |
218
|
|
|
public function compileSelect( |
219
|
|
|
array $fromTables, |
220
|
|
|
$distinct, |
221
|
|
|
array $columns, |
222
|
|
|
array $joinTokens = [], |
223
|
|
|
array $whereTokens = [], |
224
|
|
|
array $havingTokens = [], |
225
|
|
|
array $grouping = [], |
226
|
|
|
array $ordering = [], |
227
|
|
|
int $limit = 0, |
228
|
|
|
int $offset = 0, |
229
|
|
|
array $unionTokens = [] |
230
|
|
|
): string { |
231
|
|
|
//This statement parts should be processed first to define set of table and column aliases |
232
|
|
|
$fromTables = $this->compileTables($fromTables); |
233
|
|
|
|
234
|
|
|
$joinsStatement = $this->optional(' ', $this->compileJoins($joinTokens), ' '); |
235
|
|
|
|
236
|
|
|
//Distinct flag (if any) |
237
|
|
|
$distinct = $this->optional(' ', $this->compileDistinct($distinct)); |
238
|
|
|
|
239
|
|
|
//Columns are compiled after table names and joins to ensure aliases and prefixes |
240
|
|
|
$columns = $this->prepareColumns($columns); |
241
|
|
|
|
242
|
|
|
//A lot of constrain and other statements |
243
|
|
|
$whereStatement = $this->optional("\nWHERE", $this->compileWhere($whereTokens)); |
244
|
|
|
$havingStatement = $this->optional("\nHAVING", $this->compileWhere($havingTokens)); |
245
|
|
|
$groupingStatement = $this->optional("\nGROUP BY", $this->compileGrouping($grouping), ' '); |
246
|
|
|
|
247
|
|
|
//Union statement has new line at beginning of every union |
248
|
|
|
$unionsStatement = $this->optional("\n", $this->compileUnions($unionTokens)); |
249
|
|
|
$orderingStatement = $this->optional("\nORDER BY ", $this->compileOrdering($ordering)); |
250
|
|
|
|
251
|
|
|
$limingStatement = $this->optional("\n", $this->compileLimit($limit, $offset)); |
252
|
|
|
|
253
|
|
|
//Initial statement have predictable order |
254
|
|
|
$statement = "SELECT{$distinct}\n{$columns}\nFROM {$fromTables}"; |
255
|
|
|
$statement .= "{$joinsStatement}{$whereStatement}{$groupingStatement}{$havingStatement}"; |
256
|
|
|
$statement .= "{$unionsStatement}{$orderingStatement}{$limingStatement}"; |
257
|
|
|
|
258
|
|
|
return rtrim($statement); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Quote and wrap column identifiers (used in insert statement compilation). |
263
|
|
|
* |
264
|
|
|
* @param array $columnIdentifiers |
265
|
|
|
* @param int $maxLength Automatically wrap columns. |
266
|
|
|
* |
267
|
|
|
* @return string |
268
|
|
|
*/ |
269
|
|
|
protected function prepareColumns(array $columnIdentifiers, int $maxLength = 180): string |
270
|
|
|
{ |
271
|
|
|
//Let's quote every identifier |
272
|
|
|
$columnIdentifiers = array_map([$this, 'quote'], $columnIdentifiers); |
273
|
|
|
|
274
|
|
|
return wordwrap(implode(', ', $columnIdentifiers), $maxLength); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Prepare column values to be used in UPDATE statement. |
279
|
|
|
* |
280
|
|
|
* @param array $updates |
281
|
|
|
* |
282
|
|
|
* @return string |
283
|
|
|
*/ |
284
|
|
|
protected function prepareUpdates(array $updates): string |
285
|
|
|
{ |
286
|
|
|
foreach ($updates as $column => &$value) { |
287
|
|
|
if ($value instanceof FragmentInterface) { |
288
|
|
|
$value = $this->prepareFragment($value); |
289
|
|
|
} else { |
290
|
|
|
//Simple value (such condition should never be met since every value has to be |
291
|
|
|
//wrapped using parameter interface) |
292
|
|
|
$value = '?'; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
$value = "{$this->quote($column)} = {$value}"; |
296
|
|
|
unset($value); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
return trim(implode(',', $updates)); |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Compile DISTINCT statement. |
304
|
|
|
* |
305
|
|
|
* @param mixed $distinct Not every DBMS support distinct expression, only Postgres does. |
306
|
|
|
* |
307
|
|
|
* @return string |
308
|
|
|
*/ |
309
|
|
|
protected function compileDistinct($distinct): string |
310
|
|
|
{ |
311
|
|
|
if (empty($distinct)) { |
312
|
|
|
return ''; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
return 'DISTINCT'; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Compile table names statement. |
320
|
|
|
* |
321
|
|
|
* @param array $tables |
322
|
|
|
* |
323
|
|
|
* @return string |
324
|
|
|
*/ |
325
|
|
|
protected function compileTables(array $tables): string |
326
|
|
|
{ |
327
|
|
|
foreach ($tables as &$table) { |
328
|
|
|
$table = $this->quote($table, true); |
329
|
|
|
unset($table); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
return implode(', ', $tables); |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Compiler joins statement. |
337
|
|
|
* |
338
|
|
|
* @param array $joinTokens |
339
|
|
|
* |
340
|
|
|
* @return string |
341
|
|
|
*/ |
342
|
|
|
protected function compileJoins(array $joinTokens): string |
343
|
|
|
{ |
344
|
|
|
$statement = ''; |
345
|
|
|
foreach ($joinTokens as $table => $join) { |
346
|
|
|
$statement .= "\n" . $join['type'] . ' JOIN ' . $this->quote($table, true); |
347
|
|
|
$statement .= $this->optional("\n ON", $this->compileWhere($join['on'])); |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
return $statement; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Compile union statement chunk. Keywords UNION and ALL will be included, this methods will |
355
|
|
|
* automatically move every union on new line. |
356
|
|
|
* |
357
|
|
|
* @param array $unionTokens |
358
|
|
|
* |
359
|
|
|
* @return string |
360
|
|
|
*/ |
361
|
|
|
protected function compileUnions(array $unionTokens): string |
362
|
|
|
{ |
363
|
|
|
if (empty($unionTokens)) { |
364
|
|
|
return ''; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
$statement = ''; |
368
|
|
|
foreach ($unionTokens as $union) { |
369
|
|
|
//First key is union type, second united query (no need to share compiler) |
370
|
|
|
$statement .= "\nUNION {$union[1]}\n({$union[0]})"; |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
return ltrim($statement, "\n"); |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
/** |
377
|
|
|
* Compile ORDER BY statement. |
378
|
|
|
* |
379
|
|
|
* @param array $ordering |
380
|
|
|
* |
381
|
|
|
* @return string |
382
|
|
|
*/ |
383
|
|
|
protected function compileOrdering(array $ordering): string |
384
|
|
|
{ |
385
|
|
|
$result = []; |
386
|
|
|
foreach ($ordering as $order) { |
387
|
|
|
$result[] = $this->quote($order[0]) . ' ' . strtoupper($order[1]); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
return implode(', ', $result); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Compiler GROUP BY statement. |
395
|
|
|
* |
396
|
|
|
* @param array $grouping |
397
|
|
|
* |
398
|
|
|
* @return string |
399
|
|
|
*/ |
400
|
|
|
protected function compileGrouping(array $grouping): string |
401
|
|
|
{ |
402
|
|
|
$statement = ''; |
403
|
|
|
foreach ($grouping as $identifier) { |
404
|
|
|
$statement .= $this->quote($identifier); |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
return $statement; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Compile limit statement. |
412
|
|
|
* |
413
|
|
|
* @param int $limit |
414
|
|
|
* @param int $offset |
415
|
|
|
* |
416
|
|
|
* @return string |
417
|
|
|
*/ |
418
|
|
|
protected function compileLimit(int $limit, int $offset): string |
419
|
|
|
{ |
420
|
|
|
if (empty($limit) && empty($offset)) { |
421
|
|
|
return ''; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
$statement = ''; |
425
|
|
|
if (!empty($limit)) { |
426
|
|
|
$statement = "LIMIT {$limit} "; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
if (!empty($offset)) { |
430
|
|
|
$statement .= "OFFSET {$offset}"; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
return trim($statement); |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* Compile where statement. |
438
|
|
|
* |
439
|
|
|
* @param array $tokens |
440
|
|
|
* |
441
|
|
|
* @return string |
442
|
|
|
* |
443
|
|
|
* @throws CompilerException |
444
|
|
|
*/ |
445
|
|
|
protected function compileWhere(array $tokens): string |
446
|
|
|
{ |
447
|
|
|
if (empty($tokens)) { |
448
|
|
|
return ''; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
$statement = ''; |
452
|
|
|
|
453
|
|
|
$activeGroup = true; |
454
|
|
|
foreach ($tokens as $condition) { |
455
|
|
|
//OR/AND keyword |
456
|
|
|
$boolean = $condition[0]; |
457
|
|
|
|
458
|
|
|
//See AbstractWhere |
459
|
|
|
$context = $condition[1]; |
460
|
|
|
|
461
|
|
|
//First condition in group/query, no any AND, OR required |
462
|
|
|
if ($activeGroup) { |
463
|
|
|
//Kill AND, OR and etc. |
|
|
|
|
464
|
|
|
$boolean = ''; |
465
|
|
|
|
466
|
|
|
//Next conditions require AND or OR |
|
|
|
|
467
|
|
|
$activeGroup = false; |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
/* |
471
|
|
|
* When context is string it usually represent control keyword/syntax such as opening |
472
|
|
|
* or closing braces. |
473
|
|
|
*/ |
474
|
|
|
if (is_string($context)) { |
475
|
|
|
if ($context == '(') { |
476
|
|
|
//New where group. |
477
|
|
|
$activeGroup = true; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
$statement .= ltrim("{$boolean} {$context} "); |
481
|
|
|
|
482
|
|
|
if ($context == ')') { |
483
|
|
|
//We don't need trailing space |
484
|
|
|
$statement = rtrim($statement); |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
continue; |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
if ($context instanceof FragmentInterface) { |
491
|
|
|
//Fragments has to be compiled separately |
492
|
|
|
$statement .= "{$boolean} {$this->prepareFragment($context)} "; |
493
|
|
|
continue; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
//Now we are operating with "class" where function, where we need 3 variables where(id, =, 1) |
497
|
|
|
if (!is_array($context)) { |
498
|
|
|
throw new CompilerException('Invalid where token, context expected to be an array'); |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/* |
502
|
|
|
* This is "normal" where token which includes identifier, operator and value. |
503
|
|
|
*/ |
504
|
|
|
list($identifier, $operator, $value) = $context; |
505
|
|
|
|
506
|
|
|
//Identifier can be column name, expression or even query builder |
507
|
|
|
$identifier = $this->quote($identifier); |
508
|
|
|
|
509
|
|
|
//Value has to be prepared as well |
510
|
|
|
$placeholder = $this->prepareValue($value); |
511
|
|
|
|
512
|
|
|
if ($operator == 'BETWEEN' || $operator == 'NOT BETWEEN') { |
513
|
|
|
//Between statement has additional parameter |
514
|
|
|
$right = $this->prepareValue($context[3]); |
515
|
|
|
|
516
|
|
|
$statement .= "{$boolean} {$identifier} {$operator} {$placeholder} AND {$right} "; |
517
|
|
|
continue; |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
//Compiler can switch equal to IN if value points to array (do we need it?) |
521
|
|
|
$operator = $this->prepareOperator($value, $operator); |
522
|
|
|
|
523
|
|
|
$statement .= "{$boolean} {$identifier} {$operator} {$placeholder} "; |
524
|
|
|
} |
525
|
|
|
|
526
|
|
|
if ($activeGroup) { |
527
|
|
|
throw new CompilerException('Unable to build where statement, unclosed where group'); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
return trim($statement); |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
/** |
534
|
|
|
* Combine expression with prefix/postfix (usually SQL keyword) but only if expression is not |
535
|
|
|
* empty. |
536
|
|
|
* |
537
|
|
|
* @param string $prefix |
538
|
|
|
* @param string $expression |
539
|
|
|
* @param string $postfix |
540
|
|
|
* |
541
|
|
|
* @return string |
542
|
|
|
*/ |
543
|
|
|
protected function optional(string $prefix, string $expression, string $postfix = ''): string |
544
|
|
|
{ |
545
|
|
|
if (empty($expression)) { |
546
|
|
|
return ''; |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
if ($prefix != "\n" && $prefix != ' ') { |
550
|
|
|
$prefix .= ' '; |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
return $prefix . $expression . $postfix; |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
/** |
557
|
|
|
* Resolve operator value based on value value. ;). |
558
|
|
|
* |
559
|
|
|
* @param mixed $parameter |
560
|
|
|
* @param string $operator |
561
|
|
|
* |
562
|
|
|
* @return string |
563
|
|
|
*/ |
564
|
|
|
protected function prepareOperator($parameter, string $operator): string |
565
|
|
|
{ |
566
|
|
|
if (!$parameter instanceof ParameterInterface) { |
567
|
|
|
//Probably fragment |
568
|
|
|
return $operator; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
if ($operator != '=' || is_scalar($parameter->getValue())) { |
572
|
|
|
//Doing nothing for non equal operators |
573
|
|
|
return $operator; |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
if ($parameter->isArray()) { |
577
|
|
|
//Automatically switching between equal and IN |
578
|
|
|
return 'IN'; |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
return $operator; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
/** |
585
|
|
|
* Prepare value to be replaced into query (replace ?). |
586
|
|
|
* |
587
|
|
|
* @param mixed $value |
588
|
|
|
* |
589
|
|
|
* @return string |
590
|
|
|
*/ |
591
|
|
|
protected function prepareValue($value): string |
592
|
|
|
{ |
593
|
|
|
if ($value instanceof FragmentInterface) { |
594
|
|
|
return $this->prepareFragment($value); |
595
|
|
|
} |
596
|
|
|
|
597
|
|
|
//Technically should never happen (but i prefer to keep this legacy code) |
598
|
|
|
return '?'; |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* Prepare where fragment to be injected into statement. |
603
|
|
|
* |
604
|
|
|
* @param FragmentInterface $context |
605
|
|
|
* |
606
|
|
|
* @return string |
607
|
|
|
*/ |
608
|
|
|
protected function prepareFragment(FragmentInterface $context): string |
609
|
|
|
{ |
610
|
|
|
if ($context instanceof QueryBuilder) { |
611
|
|
|
//Nested queries has to be wrapped with braces |
612
|
|
|
return '(' . $context->sqlStatement($this) . ')'; |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
if ($context instanceof ExpressionInterface) { |
616
|
|
|
//Fragments does not need braces around them |
617
|
|
|
return $context->sqlStatement($this); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
return $context->sqlStatement(); |
621
|
|
|
} |
622
|
|
|
} |
623
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.