1
|
|
|
<?php |
2
|
|
|
namespace SQLParser\Node; |
3
|
|
|
|
4
|
|
|
use Doctrine\DBAL\Platforms\AbstractPlatform; |
5
|
|
|
use Mouf\Database\MagicQueryException; |
6
|
|
|
use Mouf\Database\MagicQueryParserException; |
7
|
|
|
use SQLParser\SqlRenderInterface; |
8
|
|
|
use Mouf\MoufManager; |
9
|
|
|
use Mouf\MoufInstanceDescriptor; |
10
|
|
|
use SQLParser\Query\StatementFactory; |
11
|
|
|
use PHPSQLParser\utils\ExpressionType; |
12
|
|
|
|
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* This class has the ability to create instances implementing NodeInterface based on a descriptive array. |
16
|
|
|
* |
17
|
|
|
* @author David Négrier <[email protected]> |
18
|
|
|
*/ |
19
|
|
|
class NodeFactory |
20
|
|
|
{ |
21
|
|
|
public static function toObject(array $desc) |
22
|
|
|
{ |
23
|
|
|
if (!isset($desc['expr_type'])) { |
24
|
|
|
throw new \Exception('Invalid array. Could not find expression type: '.var_export($desc, true)); |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
switch ($desc['expr_type']) { |
28
|
|
|
case ExpressionType::CONSTANT: |
29
|
|
|
$const = new ConstNode(); |
30
|
|
|
$expr = $desc['base_expr']; |
31
|
|
|
if (strpos($expr, "'") === 0) { |
32
|
|
|
$expr = substr($expr, 1); |
33
|
|
|
} else { |
34
|
|
|
$const->setIsString(false); |
35
|
|
|
} |
36
|
|
View Code Duplication |
if (strrpos($expr, "'") === strlen($expr) - 1) { |
|
|
|
|
37
|
|
|
$expr = substr($expr, 0, -1); |
38
|
|
|
} |
39
|
|
|
$expr = stripslashes($expr); |
40
|
|
|
|
41
|
|
|
$const->setValue($expr); |
42
|
|
|
|
43
|
|
|
// If the constant has an alias, it is declared in the columns section. |
44
|
|
|
// If this is the case, let's wrap it in an "expression" |
45
|
|
|
if (isset($desc['alias'])) { |
46
|
|
|
$expression = new Expression(); |
47
|
|
|
$expression->setBaseExpression($desc['base_expr']); |
48
|
|
|
$expression->setSubTree($const); |
49
|
|
|
$expression->setAlias($desc['alias']['name']); |
50
|
|
|
$expression->setBrackets(false); |
51
|
|
|
|
52
|
|
|
$const = $expression; |
53
|
|
|
|
54
|
|
|
unset($desc['alias']); |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
// Debug: |
58
|
|
|
unset($desc['base_expr']); |
59
|
|
|
unset($desc['expr_type']); |
60
|
|
|
unset($desc['sub_tree']); |
61
|
|
|
unset($desc['delim']); |
62
|
|
|
|
63
|
|
|
if (!empty($desc)) { |
64
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
return $const; |
68
|
|
|
|
69
|
|
|
case ExpressionType::OPERATOR: |
70
|
|
|
$operator = new Operator(); |
71
|
|
|
$operator->setValue($desc['base_expr']); |
72
|
|
|
// Debug: |
73
|
|
|
unset($desc['base_expr']); |
74
|
|
|
unset($desc['expr_type']); |
75
|
|
View Code Duplication |
if (!empty($desc['sub_tree'])) { |
|
|
|
|
76
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected operator with subtree: '.var_export($desc['sub_tree'], true)); |
77
|
|
|
} |
78
|
|
|
unset($desc['sub_tree']); |
79
|
|
|
if (!empty($desc)) { |
80
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
return $operator; |
84
|
|
|
|
85
|
|
|
case ExpressionType::COLREF: |
86
|
|
|
if (substr($desc['base_expr'], 0, 1) === ':') { |
87
|
|
|
$instance = new Parameter(); |
88
|
|
|
$instance->setName(substr($desc['base_expr'], 1)); |
89
|
|
|
} else { |
90
|
|
|
$instance = new ColRef(); |
91
|
|
|
|
92
|
|
|
if (isset($desc['no_quotes'])) { |
93
|
|
|
$parts = $desc['no_quotes']['parts']; |
94
|
|
|
} else { |
95
|
|
|
$parts = explode('.', str_replace('`', '', $desc['base_expr'])); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$columnName = array_pop($parts); |
99
|
|
|
$instance->setColumn($columnName); |
100
|
|
|
|
101
|
|
|
if (!empty($parts)) { |
102
|
|
|
$tableName = array_pop($parts); |
103
|
|
|
$instance->setTable($tableName); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
if (!empty($parts)) { |
107
|
|
|
$baseName = array_pop($parts); |
108
|
|
|
$instance->setDatabase($baseName); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
if (!empty($desc['alias'])) { |
112
|
|
|
$instance->setAlias($desc['alias']['name']); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
if (!empty($desc['direction'])) { |
116
|
|
|
$instance->setDirection($desc['direction']); |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
// Debug: |
121
|
|
|
unset($desc['direction']); |
122
|
|
|
unset($desc['base_expr']); |
123
|
|
|
unset($desc['expr_type']); |
124
|
|
View Code Duplication |
if (!empty($desc['sub_tree'])) { |
|
|
|
|
125
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected operator with subtree: '.var_export($desc['sub_tree'], true)); |
126
|
|
|
} |
127
|
|
|
unset($desc['sub_tree']); |
128
|
|
|
unset($desc['alias']); |
129
|
|
|
unset($desc['no_quotes']); |
130
|
|
|
unset($desc['delim']); |
131
|
|
|
if (!empty($desc)) { |
132
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
return $instance; |
136
|
|
|
case ExpressionType::TABLE: |
137
|
|
|
$expr = new Table(); |
138
|
|
|
|
139
|
|
|
if (isset($desc['no_quotes'])) { |
140
|
|
|
$parts = $desc['no_quotes']['parts']; |
141
|
|
|
|
142
|
|
|
$tableName = array_pop($parts); |
143
|
|
|
$expr->setTable($tableName); |
144
|
|
|
|
145
|
|
|
if (!empty($parts)) { |
146
|
|
|
$baseName = array_pop($parts); |
147
|
|
|
$expr->setDatabase($baseName); |
148
|
|
|
} |
149
|
|
|
} else { |
150
|
|
|
$expr->setTable($desc['table']); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
|
154
|
|
|
|
155
|
|
|
$expr->setTable(str_replace('`', '', $desc['table'])); |
156
|
|
|
switch ($desc['join_type']) { |
157
|
|
|
case 'CROSS': |
158
|
|
|
$joinType = 'CROSS JOIN'; |
159
|
|
|
break; |
160
|
|
|
case 'JOIN': |
161
|
|
|
$joinType = 'JOIN'; |
162
|
|
|
break; |
163
|
|
|
case 'LEFT': |
164
|
|
|
$joinType = 'LEFT JOIN'; |
165
|
|
|
break; |
166
|
|
|
case 'RIGHT': |
167
|
|
|
$joinType = 'RIGHT JOIN'; |
168
|
|
|
break; |
169
|
|
|
case 'INNER': |
170
|
|
|
$joinType = 'INNER JOIN'; |
171
|
|
|
break; |
172
|
|
|
case 'OUTER': |
173
|
|
|
$joinType = 'OUTER JOIN'; |
174
|
|
|
break; |
175
|
|
|
case 'NATURAL': |
176
|
|
|
$joinType = 'NATURAL JOIN'; |
177
|
|
|
break; |
178
|
|
|
case ',': |
179
|
|
|
$joinType = ','; |
180
|
|
|
break; |
181
|
|
|
default: |
182
|
|
|
throw new \Exception("Unexpected join type: '".$desc['join_type']."'"); |
183
|
|
|
} |
184
|
|
|
$expr->setJoinType($joinType); |
185
|
|
|
|
186
|
|
|
if (isset($desc['alias'])) { |
187
|
|
|
$expr->setAlias($desc['alias']['name']); |
188
|
|
|
} |
189
|
|
|
$subTreeNodes = self::buildFromSubtree($desc['ref_clause']); |
190
|
|
|
if ($subTreeNodes) { |
191
|
|
|
$expr->setRefClause(self::simplify($subTreeNodes)); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
// Debug: |
195
|
|
|
unset($desc['base_expr']); |
196
|
|
|
unset($desc['expr_type']); |
197
|
|
View Code Duplication |
if (!empty($desc['sub_tree'])) { |
|
|
|
|
198
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected operator with subtree: '.var_export($desc['sub_tree'], true)); |
199
|
|
|
} |
200
|
|
|
unset($desc['sub_tree']); |
201
|
|
|
unset($desc['join_type']); |
202
|
|
|
unset($desc['alias']); |
203
|
|
|
unset($desc['table']); |
204
|
|
|
unset($desc['ref_type']); |
205
|
|
|
unset($desc['ref_clause']); |
206
|
|
|
unset($desc['hints']); |
207
|
|
|
unset($desc['no_quotes']); |
208
|
|
|
if (!empty($desc)) { |
209
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
return $expr; |
213
|
|
|
case ExpressionType::SUBQUERY: |
214
|
|
|
$expr = new SubQuery(); |
215
|
|
|
|
216
|
|
|
$expr->setSubQuery(self::buildFromSubtree($desc['sub_tree'])); |
217
|
|
|
|
218
|
|
|
if (isset($desc['join_type'])) { |
219
|
|
|
$expr->setJoinType($desc['join_type']); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
if (isset($desc['alias'])) { |
223
|
|
|
$expr->setAlias($desc['alias']['name']); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
if (isset($desc['ref_clause'])) { |
227
|
|
|
$subTreeNodes = self::buildFromSubtree($desc['ref_clause']); |
228
|
|
|
if ($subTreeNodes) { |
229
|
|
|
$expr->setRefClause(self::simplify($subTreeNodes)); |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
// Debug: |
234
|
|
|
unset($desc['base_expr']); |
235
|
|
|
unset($desc['expr_type']); |
236
|
|
|
unset($desc['sub_tree']); |
237
|
|
|
unset($desc['join_type']); |
238
|
|
|
unset($desc['alias']); |
239
|
|
|
unset($desc['sub_tree']); |
240
|
|
|
unset($desc['ref_type']); |
241
|
|
|
unset($desc['ref_clause']); |
242
|
|
|
unset($desc['hints']); |
243
|
|
|
if (!empty($desc)) { |
244
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
return $expr; |
248
|
|
|
case ExpressionType::AGGREGATE_FUNCTION: |
249
|
|
|
$expr = new AggregateFunction(); |
250
|
|
|
$expr->setFunctionName($desc['base_expr']); |
251
|
|
|
|
252
|
|
|
$expr->setSubTree(self::buildFromSubtree($desc['sub_tree'])); |
253
|
|
|
|
254
|
|
|
if (isset($desc['alias'])) { |
255
|
|
|
$expr->setAlias($desc['alias']); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
if (isset($desc['direction'])) { |
259
|
|
|
$expr->setDirection($desc['direction']); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// Debug: |
263
|
|
|
unset($desc['base_expr']); |
264
|
|
|
unset($desc['expr_type']); |
265
|
|
|
unset($desc['sub_tree']); |
266
|
|
|
unset($desc['alias']); |
267
|
|
|
unset($desc['delim']); |
268
|
|
|
unset($desc['direction']); |
269
|
|
|
if (!empty($desc)) { |
270
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in aggregate function: '.var_export($desc, true)); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
return $expr; |
274
|
|
|
case ExpressionType::SIMPLE_FUNCTION: |
275
|
|
|
$expr = new SimpleFunction(); |
276
|
|
|
$expr->setBaseExpression($desc['base_expr']); |
277
|
|
|
|
278
|
|
|
if (isset($desc['sub_tree'])) { |
279
|
|
|
$expr->setSubTree(self::buildFromSubtree($desc['sub_tree'])); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
if (isset($desc['alias'])) { |
283
|
|
|
$expr->setAlias($desc['alias']['name']); |
284
|
|
|
} |
285
|
|
|
if (isset($desc['direction'])) { |
286
|
|
|
$expr->setDirection($desc['direction']); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
// Debug: |
290
|
|
|
unset($desc['base_expr']); |
291
|
|
|
unset($desc['expr_type']); |
292
|
|
|
unset($desc['sub_tree']); |
293
|
|
|
unset($desc['alias']); |
294
|
|
|
unset($desc['direction']); |
295
|
|
|
unset($desc['delim']); |
296
|
|
|
unset($desc['no_quotes']); |
297
|
|
|
if (!empty($desc)) { |
298
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in simple function: '.var_export($desc, true)); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
return $expr; |
302
|
|
|
case ExpressionType::RESERVED: |
303
|
|
|
if (in_array(strtoupper($desc['base_expr']), ['CASE', 'WHEN', 'THEN', 'ELSE', 'END'])) { |
304
|
|
|
$operator = new Operator(); |
305
|
|
|
$operator->setValue($desc['base_expr']); |
306
|
|
|
// Debug: |
307
|
|
|
unset($desc['base_expr']); |
308
|
|
|
unset($desc['expr_type']); |
309
|
|
View Code Duplication |
if (!empty($desc['sub_tree'])) { |
|
|
|
|
310
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected operator with subtree: '.var_export($desc['sub_tree'], true)); |
311
|
|
|
} |
312
|
|
|
unset($desc['sub_tree']); |
313
|
|
|
unset($desc['delim']); |
314
|
|
|
if (!empty($desc)) { |
315
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
return $operator; |
319
|
|
|
} else { |
320
|
|
|
$res = new Reserved(); |
321
|
|
|
$res->setBaseExpression($desc['base_expr']); |
322
|
|
|
|
323
|
|
|
if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) { |
324
|
|
|
$res->setBrackets(true); |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
// Debug: |
328
|
|
|
unset($desc['base_expr']); |
329
|
|
|
unset($desc['expr_type']); |
330
|
|
|
unset($desc['sub_tree']); |
331
|
|
|
unset($desc['alias']); |
332
|
|
|
unset($desc['direction']); |
333
|
|
|
unset($desc['delim']); |
334
|
|
|
if (!empty($desc)) { |
335
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
return $res; |
339
|
|
|
} |
340
|
|
|
case ExpressionType::USER_VARIABLE: |
341
|
|
|
case ExpressionType::SESSION_VARIABLE: |
342
|
|
|
case ExpressionType::GLOBAL_VARIABLE: |
343
|
|
|
case ExpressionType::LOCAL_VARIABLE: |
344
|
|
|
case ExpressionType::EXPRESSION: |
345
|
|
|
case ExpressionType::BRACKET_EXPRESSION: |
346
|
|
|
case ExpressionType::TABLE_EXPRESSION: |
347
|
|
|
|
348
|
|
|
case ExpressionType::IN_LIST: |
349
|
|
|
|
350
|
|
|
case ExpressionType::SIGN: |
351
|
|
|
case ExpressionType::RECORD: |
352
|
|
|
|
353
|
|
|
case ExpressionType::MATCH_ARGUMENTS: |
354
|
|
|
|
355
|
|
|
case ExpressionType::ALIAS: |
356
|
|
|
case ExpressionType::POSITION: |
357
|
|
|
|
358
|
|
|
case ExpressionType::TEMPORARY_TABLE: |
359
|
|
|
case ExpressionType::VIEW: |
360
|
|
|
case ExpressionType::DATABASE: |
361
|
|
|
case ExpressionType::SCHEMA: |
362
|
|
|
$expr = new Expression(); |
363
|
|
|
$expr->setBaseExpression($desc['base_expr']); |
364
|
|
|
|
365
|
|
|
if (isset($desc['sub_tree'])) { |
366
|
|
|
$expr->setSubTree(self::buildFromSubtree($desc['sub_tree'])); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
if (isset($desc['alias'])) { |
370
|
|
|
$expr->setAlias($desc['alias']['name']); |
371
|
|
|
} |
372
|
|
|
if (isset($desc['direction'])) { |
373
|
|
|
$expr->setDirection($desc['direction']); |
374
|
|
|
} |
375
|
|
|
|
376
|
|
|
if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) { |
377
|
|
|
$expr->setBrackets(true); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
if ($desc['expr_type'] == ExpressionType::IN_LIST) { |
381
|
|
|
$expr->setBrackets(true); |
382
|
|
|
$expr->setDelimiter(','); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
// Debug: |
386
|
|
|
unset($desc['base_expr']); |
387
|
|
|
unset($desc['expr_type']); |
388
|
|
|
unset($desc['sub_tree']); |
389
|
|
|
unset($desc['alias']); |
390
|
|
|
unset($desc['direction']); |
391
|
|
|
unset($desc['delim']); |
392
|
|
|
unset($desc['no_quotes']); |
393
|
|
|
if (!empty($desc)) { |
394
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
return $expr; |
398
|
|
|
case ExpressionType::MATCH_MODE: |
399
|
|
|
$expr = new ConstNode(); |
400
|
|
|
$expr->setValue($desc['base_expr']); |
401
|
|
|
$expr->setIsString(false); |
402
|
|
|
|
403
|
|
|
// Debug: |
404
|
|
|
unset($desc['base_expr']); |
405
|
|
|
unset($desc['expr_type']); |
406
|
|
|
unset($desc['sub_tree']); |
407
|
|
|
unset($desc['alias']); |
408
|
|
|
unset($desc['direction']); |
409
|
|
|
unset($desc['delim']); |
410
|
|
|
if (!empty($desc)) { |
411
|
|
|
error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true)); |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
return $expr; |
415
|
|
|
default: |
416
|
|
|
throw new \Exception('Unknown expression type'); |
417
|
|
|
} |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
|
421
|
|
|
/** |
422
|
|
|
* Transforms a limit or offset value/parameter into a node. |
423
|
|
|
* |
424
|
|
|
* @param string $value |
425
|
|
|
* @return NodeInterface |
426
|
|
|
*/ |
427
|
|
|
public static function toLimitNode($value) |
428
|
|
|
{ |
429
|
|
|
if (substr($value, 0, 1) === ':') { |
430
|
|
|
$instance = new UnquotedParameter(); |
431
|
|
|
$instance->setName(substr($value, 1)); |
432
|
|
|
} else { |
433
|
|
|
$instance = new LimitNode(); |
434
|
|
|
$expr = $value; |
435
|
|
|
if (strpos($expr, "'") === 0) { |
436
|
|
|
$expr = substr($expr, 1); |
437
|
|
|
} |
438
|
|
View Code Duplication |
if (strrpos($expr, "'") === strlen($expr) - 1) { |
|
|
|
|
439
|
|
|
$expr = substr($expr, 0, -1); |
440
|
|
|
} |
441
|
|
|
$expr = stripslashes($expr); |
442
|
|
|
|
443
|
|
|
$instance->setValue($expr); |
444
|
|
|
} |
445
|
|
|
return $instance; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
private static function buildFromSubtree($subTree) |
449
|
|
|
{ |
450
|
|
|
if ($subTree && is_array($subTree)) { |
451
|
|
|
//if (isset($subTree['SELECT'])) { |
452
|
|
|
// If the subtree is a map instead of a list, we are likely to be on a SUBSELECT statement. |
453
|
|
|
if (!empty($subTree) && !isset($subTree[0])) { |
454
|
|
|
$subTree = StatementFactory::toObject($subTree); |
455
|
|
|
} else { |
456
|
|
|
$subTree = self::mapArrayToNodeObjectList($subTree); |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
return $subTree; |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
/** |
464
|
|
|
* @param array $items An array of objects represented as SQLParser arrays. |
465
|
|
|
*/ |
466
|
|
|
public static function mapArrayToNodeObjectList(array $items) |
467
|
|
|
{ |
468
|
|
|
$list = []; |
469
|
|
|
|
470
|
|
|
$nextAndPartOfBetween = false; |
471
|
|
|
|
472
|
|
|
// Special case, let's replace the AND of a between with a ANDBETWEEN object. |
473
|
|
|
foreach ($items as $item) { |
474
|
|
|
$obj = NodeFactory::toObject($item); |
475
|
|
|
if ($obj instanceof Operator) { |
476
|
|
|
if ($obj->getValue() == 'BETWEEN') { |
477
|
|
|
$nextAndPartOfBetween = true; |
478
|
|
|
} elseif ($nextAndPartOfBetween && $obj->getValue() == 'AND') { |
479
|
|
|
$nextAndPartOfBetween = false; |
480
|
|
|
$obj->setValue('AND_FROM_BETWEEN'); |
481
|
|
|
} |
482
|
|
|
} |
483
|
|
|
$list[] = $obj; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
return $list; |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
private static $PRECEDENCE = array( |
490
|
|
|
array('INTERVAL'), |
491
|
|
|
array('BINARY', 'COLLATE'), |
492
|
|
|
array('!'), |
493
|
|
|
array(/*'-'*/ /* (unary minus) ,*/ '~' /*(unary bit inversion)*/), |
494
|
|
|
array('^'), |
495
|
|
|
array('*', '/', 'DIV', '%', 'MOD'), |
496
|
|
|
array('-', '+'), |
497
|
|
|
array('<<', '>>'), |
498
|
|
|
array('&'), |
499
|
|
|
array('|'), |
500
|
|
|
array('=' /*(comparison)*/, '<=>', '>=', '>', '<=', '<', '<>', '!=', 'IS', 'LIKE', 'REGEXP', 'IN', 'IS NOT', 'NOT IN'), |
501
|
|
|
array('AND_FROM_BETWEEN'), |
502
|
|
|
array('THEN'), |
503
|
|
|
array('WHEN'), |
504
|
|
|
array('ELSE'), |
505
|
|
|
array('BETWEEN', 'CASE', 'END'), |
506
|
|
|
array('NOT'), |
507
|
|
|
array('&&', 'AND'), |
508
|
|
|
array('XOR'), |
509
|
|
|
array('||', 'OR'), ); |
510
|
|
|
|
511
|
|
|
private static $OPERATOR_TO_CLASS = array( |
512
|
|
|
'=' => 'SQLParser\Node\Equal', |
513
|
|
|
'<' => 'SQLParser\Node\Less', |
514
|
|
|
'>' => 'SQLParser\Node\Greater', |
515
|
|
|
'<=' => 'SQLParser\Node\LessOrEqual', |
516
|
|
|
'>=' => 'SQLParser\Node\GreaterOrEqual', |
517
|
|
|
//'<=>' => '????', |
518
|
|
|
'<>' => 'SQLParser\Node\Different', |
519
|
|
|
'!=' => 'SQLParser\Node\Different', |
520
|
|
|
'IS' => 'SQLParser\Node\Is', |
521
|
|
|
'IS NOT' => 'SQLParser\Node\IsNot', |
522
|
|
|
'LIKE' => 'SQLParser\Node\Like', |
523
|
|
|
'REGEXP' => 'SQLParser\Node\Regexp', |
524
|
|
|
'IN' => 'SQLParser\Node\In', |
525
|
|
|
'NOT IN' => 'SQLParser\Node\NotIn', |
526
|
|
|
'+' => 'SQLParser\Node\Plus', |
527
|
|
|
'-' => 'SQLParser\Node\Minus', |
528
|
|
|
'*' => 'SQLParser\Node\Multiply', |
529
|
|
|
'/' => 'SQLParser\Node\Divide', |
530
|
|
|
'%' => 'SQLParser\Node\Modulo', |
531
|
|
|
'MOD' => 'SQLParser\Node\Modulo', |
532
|
|
|
'DIV' => 'SQLParser\Node\Div', |
533
|
|
|
'&' => 'SQLParser\Node\BitwiseAnd', |
534
|
|
|
'|' => 'SQLParser\Node\BitwiseOr', |
535
|
|
|
'^' => 'SQLParser\Node\BitwiseXor', |
536
|
|
|
'<<' => 'SQLParser\Node\ShiftLeft', |
537
|
|
|
'>>' => 'SQLParser\Node\ShiftRight', |
538
|
|
|
'<=>' => 'SQLParser\Node\NullCompatibleEqual', |
539
|
|
|
'AND' => 'SQLParser\Node\AndOp', |
540
|
|
|
'&&' => 'SQLParser\Node\AndOp', |
541
|
|
|
'||' => 'SQLParser\Node\OrOp', |
542
|
|
|
'OR' => 'SQLParser\Node\OrOp', |
543
|
|
|
'XOR' => 'SQLParser\Node\XorOp', |
544
|
|
|
'THEN' => 'SQLParser\Node\Then', |
545
|
|
|
'ELSE' => 'SQLParser\Node\ElseOperation', |
546
|
|
|
); |
547
|
|
|
|
548
|
|
|
/** |
549
|
|
|
* Takes an array of nodes (including operators) and try to build a tree from it. |
550
|
|
|
* |
551
|
|
|
* @param NodeInterface[]|NodeInterface $nodes |
552
|
|
|
*/ |
553
|
|
|
public static function simplify($nodes) |
554
|
|
|
{ |
555
|
|
|
if (empty($nodes)) { |
556
|
|
|
$nodes = array(); |
557
|
|
|
} elseif (!is_array($nodes)) { |
558
|
|
|
$nodes = array($nodes); |
559
|
|
|
} |
560
|
|
|
$minPriority = -1; |
561
|
|
|
$selectedOperators = array(); |
562
|
|
|
$lastSelectedOperator = ''; |
563
|
|
|
$differentOperatorWithSamePriority = false; |
564
|
|
|
|
565
|
|
|
// Let's transform "NOT" + "IN" into "NOT IN" |
566
|
|
|
$newNodes = array(); |
567
|
|
|
for ($i = 0; $i < count($nodes); ++$i) { |
|
|
|
|
568
|
|
|
$node = $nodes[$i]; |
569
|
|
|
if ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator |
570
|
|
|
&& strtoupper($node->getValue()) == 'IS' && strtoupper($nodes[$i + 1]->getValue()) == 'NOT') { |
571
|
|
|
$notIn = new Operator(); |
572
|
|
|
$notIn->setValue('IS NOT'); |
573
|
|
|
$newNodes[] = $notIn; |
574
|
|
|
++$i; |
575
|
|
|
} elseif ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator |
576
|
|
|
&& strtoupper($node->getValue()) == 'NOT' && strtoupper($nodes[$i + 1]->getValue()) == 'IN') { |
577
|
|
|
$notIn = new Operator(); |
578
|
|
|
$notIn->setValue('NOT IN'); |
579
|
|
|
$newNodes[] = $notIn; |
580
|
|
|
++$i; |
581
|
|
|
} else { |
582
|
|
|
$newNodes[] = $node; |
583
|
|
|
} |
584
|
|
|
} |
585
|
|
|
$nodes = $newNodes; |
586
|
|
|
|
587
|
|
|
// Handle AGAINST function. Without this patch params will be placed after AGAINST() and not inside the brackets |
588
|
|
|
$newNodes = array(); |
589
|
|
|
for ($i = 0; $i < count($nodes); ++$i) { |
|
|
|
|
590
|
|
|
$node = $nodes[$i]; |
591
|
|
|
if ($node instanceof SimpleFunction && $node->getBaseExpression() === 'AGAINST' && isset($nodes[$i + 1])) { |
592
|
|
|
$node->setSubTree($nodes[$i + 1]); |
593
|
|
|
$i++; |
594
|
|
|
} |
595
|
|
|
$newNodes[] = $node; |
596
|
|
|
} |
597
|
|
|
$nodes = $newNodes; |
598
|
|
|
|
599
|
|
|
// Let's find the highest level operator. |
600
|
|
|
for ($i = count($nodes) - 1; $i >= 0; --$i) { |
601
|
|
|
$node = $nodes[$i]; |
602
|
|
|
if ($node instanceof Operator) { |
603
|
|
|
$priority = self::getOperatorPrecedence($node); |
604
|
|
|
|
605
|
|
|
if ($priority == $minPriority && $lastSelectedOperator != strtoupper($node->getValue())) { |
606
|
|
|
$differentOperatorWithSamePriority = true; |
607
|
|
|
} elseif ($priority > $minPriority) { |
608
|
|
|
$minPriority = $priority; |
609
|
|
|
$selectedOperators = array($node); |
610
|
|
|
$lastSelectedOperator = strtoupper($node->getValue()); |
611
|
|
|
} else { |
612
|
|
|
if (strtoupper($node->getValue()) == $lastSelectedOperator && !$differentOperatorWithSamePriority) { |
613
|
|
|
$selectedOperators[] = $node; |
614
|
|
|
} |
615
|
|
|
} |
616
|
|
|
} |
617
|
|
|
} |
618
|
|
|
$selectedOperators = array_reverse($selectedOperators); |
619
|
|
|
|
620
|
|
|
// At this point, the $selectedOperator list contains a list of operators of the same kind that will apply |
621
|
|
|
// at the same time. |
622
|
|
|
if (empty($selectedOperators)) { |
623
|
|
|
// If we have an Expression with no base expression, let's simply discard it. |
624
|
|
|
// Indeed, the tree will add brackets by itself, and no Expression is needed for that. |
625
|
|
|
$newNodes = array(); |
626
|
|
|
/*foreach ($nodes as $key=>$operand) { |
627
|
|
|
if ($operand instanceof Expression) { |
628
|
|
|
$subTree = $operand->getSubTree(); |
629
|
|
|
if (count($subTree) == 1) { |
630
|
|
|
$nodes[$key] = self::simplify($subTree); |
631
|
|
|
} |
632
|
|
|
} |
633
|
|
|
}*/ |
634
|
|
|
foreach ($nodes as $operand) { |
635
|
|
|
if ($operand instanceof Expression) { |
636
|
|
|
if (empty($operand->getBaseExpression())) { |
637
|
|
|
$subTree = $operand->getSubTree(); |
638
|
|
|
if (count($subTree) == 1) { |
639
|
|
|
$newNodes = array_merge($newNodes, self::simplify($subTree)); |
|
|
|
|
640
|
|
|
} else { |
641
|
|
|
$newNodes[] = $operand; |
642
|
|
|
} |
643
|
|
|
} else { |
644
|
|
|
$newNodes[] = $operand; |
645
|
|
|
} |
646
|
|
|
} else { |
647
|
|
|
$newNodes[] = $operand; |
648
|
|
|
} |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
return $newNodes; |
652
|
|
|
} |
653
|
|
|
|
654
|
|
|
// Let's grab the operands of the operator. |
655
|
|
|
$operands = array(); |
656
|
|
|
$operand = array(); |
657
|
|
|
$tmpOperators = $selectedOperators; |
658
|
|
|
$nextOperator = array_shift($tmpOperators); |
659
|
|
|
|
660
|
|
|
$isSelectedOperatorFirst = null; |
661
|
|
|
|
662
|
|
|
foreach ($nodes as $node) { |
663
|
|
|
if ($node === $nextOperator) { |
664
|
|
|
if ($isSelectedOperatorFirst === null) { |
665
|
|
|
$isSelectedOperatorFirst = true; |
666
|
|
|
} |
667
|
|
|
// Let's apply the "simplify" method on the operand before storing it. |
668
|
|
|
//$operands[] = self::simplify($operand); |
669
|
|
|
$simple = self::simplify($operand); |
670
|
|
|
if (is_array($simple)) { |
671
|
|
|
$operands = array_merge($operands, $simple); |
672
|
|
|
} else { |
673
|
|
|
$operands[] = $simple; |
674
|
|
|
} |
675
|
|
|
|
676
|
|
|
$operand = array(); |
677
|
|
|
$nextOperator = array_shift($tmpOperators); |
678
|
|
|
} else { |
679
|
|
|
if ($isSelectedOperatorFirst === null) { |
680
|
|
|
$isSelectedOperatorFirst = false; |
681
|
|
|
} |
682
|
|
|
$operand[] = $node; |
683
|
|
|
} |
684
|
|
|
} |
685
|
|
|
//$operands[] = self::simplify($operand); |
686
|
|
|
//$operands = array_merge($operands, self::simplify($operand)); |
687
|
|
|
$simple = self::simplify($operand); |
688
|
|
|
if (is_array($simple)) { |
689
|
|
|
$operands = array_merge($operands, $simple); |
690
|
|
|
} else { |
691
|
|
|
$operands[] = $simple; |
692
|
|
|
} |
693
|
|
|
|
694
|
|
|
// Now, if we have an Expression, let's simply discard it. |
695
|
|
|
// Indeed, the tree will add brackets by itself, and no Expression in needed for that. |
696
|
|
|
/*foreach ($operands as $key=>$operand) { |
697
|
|
|
if ($operand instanceof Expression) { |
698
|
|
|
$subTree = $operand->getSubTree(); |
699
|
|
|
if (count($subTree) == 1) { |
700
|
|
|
$operands[$key] = self::simplify($subTree); |
701
|
|
|
} |
702
|
|
|
} |
703
|
|
|
}*/ |
704
|
|
|
|
705
|
|
|
$operation = strtoupper($selectedOperators[0]->getValue()); |
706
|
|
|
|
707
|
|
|
/* TODO: |
708
|
|
|
Remaining operators to code: |
709
|
|
|
array('INTERVAL'), |
710
|
|
|
array('BINARY', 'COLLATE'), |
711
|
|
|
array('!'), |
712
|
|
|
array('NOT'), |
713
|
|
|
*/ |
714
|
|
|
|
715
|
|
|
if (isset(self::$OPERATOR_TO_CLASS[$operation]) && is_subclass_of(self::$OPERATOR_TO_CLASS[$operation], 'SQLParser\Node\AbstractTwoOperandsOperator')) { |
716
|
|
|
if (count($operands) != 2) { |
717
|
|
|
throw new MagicQueryException('An error occured while parsing SQL statement. Invalid character found next to "'.$operation.'"'); |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
$leftOperand = array_shift($operands); |
721
|
|
|
$rightOperand = array_shift($operands); |
722
|
|
|
|
723
|
|
|
$instance = new self::$OPERATOR_TO_CLASS[$operation](); |
724
|
|
|
$instance->setLeftOperand($leftOperand); |
725
|
|
|
$instance->setRightOperand($rightOperand); |
726
|
|
|
|
727
|
|
|
return $instance; |
728
|
|
|
} elseif (isset(self::$OPERATOR_TO_CLASS[$operation]) && is_subclass_of(self::$OPERATOR_TO_CLASS[$operation], 'SQLParser\Node\AbstractManyInstancesOperator')) { |
729
|
|
|
$instance = new self::$OPERATOR_TO_CLASS[$operation](); |
730
|
|
|
$instance->setOperands($operands); |
731
|
|
|
|
732
|
|
|
return $instance; |
733
|
|
|
} elseif ($operation === 'BETWEEN') { |
734
|
|
|
$leftOperand = array_shift($operands); |
735
|
|
|
$rightOperand = array_shift($operands); |
736
|
|
|
if (!$rightOperand instanceof Operation || $rightOperand->getOperatorSymbol() !== 'AND_FROM_BETWEEN') { |
737
|
|
|
throw new MagicQueryException('Missing AND in BETWEEN filter.'); |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
$innerOperands = $rightOperand->getOperands(); |
741
|
|
|
$minOperand = array_shift($innerOperands); |
742
|
|
|
$maxOperand = array_shift($innerOperands); |
743
|
|
|
|
744
|
|
|
$instance = new Between(); |
745
|
|
|
$instance->setLeftOperand($leftOperand); |
746
|
|
|
$instance->setMinValueOperand($minOperand); |
747
|
|
|
$instance->setMaxValueOperand($maxOperand); |
748
|
|
|
|
749
|
|
|
return $instance; |
750
|
|
|
} elseif ($operation === 'WHEN') { |
751
|
|
|
$instance = new WhenConditions(); |
752
|
|
|
|
753
|
|
|
if (!$isSelectedOperatorFirst) { |
|
|
|
|
754
|
|
|
$value = array_shift($operands); |
755
|
|
|
$instance->setValue($value); |
756
|
|
|
} |
757
|
|
|
$instance->setOperands($operands); |
758
|
|
|
|
759
|
|
|
return $instance; |
760
|
|
|
} elseif ($operation === 'CASE') { |
761
|
|
|
$innerOperation = array_shift($operands); |
762
|
|
|
|
763
|
|
|
if (!empty($operands)) { |
764
|
|
|
throw new MagicQueryException('A CASE statement should contain only a ThenConditions or a ElseOperand object.'); |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
$instance = new CaseOperation(); |
768
|
|
|
$instance->setOperation($innerOperation); |
769
|
|
|
|
770
|
|
|
return $instance; |
771
|
|
|
} elseif ($operation === 'END') { |
772
|
|
|
// Simply bypass the END operation. We already have a CASE matching node: |
773
|
|
|
$caseOperation = array_shift($operands); |
774
|
|
|
|
775
|
|
|
return $caseOperation; |
776
|
|
|
} else { |
777
|
|
|
$instance = new Operation(); |
778
|
|
|
$instance->setOperatorSymbol($operation); |
779
|
|
|
$instance->setOperands($operands); |
780
|
|
|
|
781
|
|
|
return $instance; |
782
|
|
|
} |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
/** |
786
|
|
|
* Finds the precedence for operator $node (highest number has the least precedence). |
787
|
|
|
* |
788
|
|
|
* @param Operator $node |
789
|
|
|
* |
790
|
|
|
* @throws MagicQueryException |
791
|
|
|
* |
792
|
|
|
* @return int |
793
|
|
|
*/ |
794
|
|
|
private static function getOperatorPrecedence(Operator $node): int |
795
|
|
|
{ |
796
|
|
|
$value = strtoupper($node->getValue()); |
797
|
|
|
|
798
|
|
|
foreach (self::$PRECEDENCE as $priority => $arr) { |
799
|
|
|
foreach ($arr as $op) { |
800
|
|
|
if ($value == $op) { |
801
|
|
|
return $priority; |
802
|
|
|
} |
803
|
|
|
} |
804
|
|
|
} |
805
|
|
|
throw new MagicQueryException('Unknown operator precedence for operator '.$value); |
806
|
|
|
} |
807
|
|
|
|
808
|
|
|
/** |
809
|
|
|
* @param mixed $node a node of a recursive array of node |
810
|
|
|
* @param MoufManager $moufManager |
811
|
|
|
* |
812
|
|
|
* @return MoufInstanceDescriptor |
813
|
|
|
*/ |
814
|
|
|
public static function nodeToInstanceDescriptor($node, MoufManager $moufManager) |
815
|
|
|
{ |
816
|
|
|
return self::array_map_deep($node, function ($item) use ($moufManager) { |
817
|
|
|
if ($item instanceof NodeInterface) { |
818
|
|
|
return $item->toInstanceDescriptor($moufManager); |
819
|
|
|
} else { |
820
|
|
|
return $item; |
821
|
|
|
} |
822
|
|
|
}); |
823
|
|
|
} |
824
|
|
|
|
825
|
|
|
private static function array_map_deep($array, $callback) |
826
|
|
|
{ |
827
|
|
|
$new = array(); |
828
|
|
|
if (is_array($array)) { |
829
|
|
|
foreach ($array as $key => $val) { |
830
|
|
|
if (is_array($val)) { |
831
|
|
|
$new[$key] = self::array_map_deep($val, $callback); |
832
|
|
|
} else { |
833
|
|
|
$new[$key] = call_user_func($callback, $val); |
834
|
|
|
} |
835
|
|
|
} |
836
|
|
|
} else { |
837
|
|
|
$new = call_user_func($callback, $array); |
838
|
|
|
} |
839
|
|
|
|
840
|
|
|
return $new; |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
/** |
844
|
|
|
* Tansforms the array of nodes (or the node) passed in parameter into a SQL string. |
845
|
|
|
* |
846
|
|
|
* @param mixed $nodes Recursive array of node interface |
847
|
|
|
* @param AbstractPlatform $platform |
848
|
|
|
* @param array $parameters |
849
|
|
|
* @param string $delimiter |
850
|
|
|
* @param bool|string $wrapInBrackets |
851
|
|
|
* @param int $indent |
852
|
|
|
* @param int $conditionsMode |
853
|
|
|
* |
854
|
|
|
* @return null|string |
855
|
|
|
*/ |
856
|
|
|
public static function toSql($nodes, AbstractPlatform $platform, array $parameters = array(), $delimiter = ',', $wrapInBrackets = true, $indent = 0, $conditionsMode = SqlRenderInterface::CONDITION_APPLY, bool $extrapolateParameters = true) |
857
|
|
|
{ |
858
|
|
|
if (is_array($nodes)) { |
859
|
|
|
$elems = array(); |
860
|
|
|
array_walk_recursive($nodes, function ($item) use (&$elems, $platform, $indent, $parameters, $conditionsMode, $extrapolateParameters) { |
861
|
|
|
if ($item instanceof SqlRenderInterface) { |
862
|
|
|
$itemSql = $item->toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters); |
863
|
|
|
if ($itemSql !== null) { |
864
|
|
|
$elems[] = str_repeat(' ', $indent).$itemSql; |
865
|
|
|
} |
866
|
|
|
} else { |
867
|
|
|
if ($item !== null) { |
868
|
|
|
$elems[] = str_repeat(' ', $indent).$item; |
869
|
|
|
} |
870
|
|
|
} |
871
|
|
|
}); |
872
|
|
|
$sql = implode($delimiter, $elems); |
873
|
|
|
} else { |
874
|
|
|
$item = $nodes; |
875
|
|
|
if ($item instanceof SqlRenderInterface) { |
876
|
|
|
$itemSql = $item->toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters); |
877
|
|
|
if ($itemSql === null || $itemSql === '') { |
878
|
|
|
return null; |
879
|
|
|
} |
880
|
|
|
$sql = str_repeat(' ', $indent).$itemSql; |
881
|
|
|
} else { |
882
|
|
|
if ($item === null || $item === '') { |
883
|
|
|
return null; |
884
|
|
|
} |
885
|
|
|
$sql = str_repeat(' ', $indent).$item; |
886
|
|
|
} |
887
|
|
|
} |
888
|
|
|
if ($wrapInBrackets) { |
889
|
|
|
$sql = '('.$sql.')'; |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
return $sql; |
893
|
|
|
} |
894
|
|
|
} |
895
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.