• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thecodingmachine / magic-query / 7862450178

11 Feb 2024 01:55PM UTC coverage: 70.298%. First build
7862450178

Pull #87

github

web-flow
Merge 3adfbf0ce into 0c0ab130b
Pull Request #87: Update deps + test on dbal v3

2 of 11 new or added lines in 5 files covered. (18.18%)

1084 of 1542 relevant lines covered (70.3%)

4.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.26
/src/SQLParser/Node/NodeFactory.php
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 <d.negrier@thecodingmachine.com>
18
 */
19
class NodeFactory
20
{
21
    public static function toObject(array $desc)
22
    {
23
        if (!isset($desc['expr_type'])) {
18✔
24
            throw new \Exception('Invalid array. Could not find expression type: '.var_export($desc, true));
×
25
        }
26

27
        switch ($desc['expr_type']) {
18✔
28
            case ExpressionType::CONSTANT:
29
                $const = new ConstNode();
11✔
30
                $expr = $desc['base_expr'];
11✔
31
                if (strpos($expr, "'") === 0) {
11✔
32
                    $expr = substr($expr, 1);
7✔
33
                } else {
34
                    $const->setIsString(false);
5✔
35
                }
36
                if (strrpos($expr, "'") === strlen($expr) - 1) {
11✔
37
                    $expr = substr($expr, 0, -1);
7✔
38
                }
39
                $expr = stripslashes($expr);
11✔
40

41
                $const->setValue($expr);
11✔
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']['name'])) {
11✔
46
                    $expression = new Expression();
1✔
47
                    $expression->setBaseExpression($desc['base_expr']);
1✔
48
                    $expression->setSubTree($const);
1✔
49
                    $expression->setAlias($desc['alias']['name']);
1✔
50
                    $expression->setBrackets(false);
1✔
51

52
                    $const = $expression;
1✔
53

54
                    unset($desc['alias']);
1✔
55
                }
56

57
                // Debug:
58
                unset($desc['base_expr']);
11✔
59
                unset($desc['expr_type']);
11✔
60
                unset($desc['sub_tree']);
11✔
61
                unset($desc['delim']);
11✔
62

63
                if (!empty($desc)) {
11✔
64
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
65
                }
66

67
                return $const;
11✔
68

69
            case ExpressionType::OPERATOR:
70
                $operator = new Operator();
15✔
71
                $operator->setValue($desc['base_expr']);
15✔
72
                // Debug:
73
                unset($desc['base_expr']);
15✔
74
                unset($desc['expr_type']);
15✔
75
                if (!empty($desc['sub_tree'])) {
15✔
76
                    error_log('MagicQuery - NodeFactory: Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
×
77
                }
78
                unset($desc['sub_tree']);
15✔
79
                if (!empty($desc)) {
15✔
80
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
81
                }
82

83
                return $operator;
15✔
84

85
            case ExpressionType::COLREF:
86
                if (substr($desc['base_expr'], 0, 1) === ':') {
18✔
87
                    $instance = new Parameter();
7✔
88
                    $instance->setName(substr($desc['base_expr'], 1));
7✔
89
                } else {
90
                    $instance = new ColRef();
17✔
91

92
                    if (isset($desc['no_quotes'])) {
17✔
93
                        $parts = $desc['no_quotes']['parts'];
16✔
94
                    } else {
95
                        $parts = explode('.', str_replace('`', '', $desc['base_expr']));
7✔
96
                    }
97

98
                    $columnName = array_pop($parts);
17✔
99
                    $instance->setColumn($columnName);
17✔
100

101
                    if (!empty($parts)) {
17✔
102
                        $tableName = array_pop($parts);
10✔
103
                        $instance->setTable($tableName);
10✔
104
                    }
105

106
                    if (!empty($parts)) {
17✔
107
                        $baseName = array_pop($parts);
×
108
                        $instance->setDatabase($baseName);
×
109
                    }
110

111
                    if (!empty($desc['alias']['name'])) {
17✔
112
                        $instance->setAlias($desc['alias']['name']);
×
113
                    }
114

115
                    if (!empty($desc['direction'])) {
17✔
116
                        $instance->setDirection($desc['direction']);
4✔
117
                    }
118
                }
119

120
                // Debug:
121
                unset($desc['direction']);
18✔
122
                unset($desc['base_expr']);
18✔
123
                unset($desc['expr_type']);
18✔
124
                if (!empty($desc['sub_tree'])) {
18✔
125
                    error_log('MagicQuery - NodeFactory: Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
×
126
                }
127
                unset($desc['sub_tree']);
18✔
128
                unset($desc['alias']);
18✔
129
                unset($desc['no_quotes']);
18✔
130
                unset($desc['delim']);
18✔
131
                if (!empty($desc)) {
18✔
132
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
133
                }
134

135
                return $instance;
18✔
136
            case ExpressionType::TABLE:
137
                $expr = new Table();
18✔
138

139
                if (isset($desc['no_quotes'])) {
18✔
140
                    $parts = $desc['no_quotes']['parts'];
18✔
141

142
                    $tableName = array_pop($parts);
18✔
143
                    $expr->setTable($tableName);
18✔
144

145
                    if (!empty($parts)) {
18✔
146
                        $baseName = array_pop($parts);
×
147
                        $expr->setDatabase($baseName);
18✔
148
                    }
149
                } else {
150
                    $expr->setTable($desc['table']);
×
151
                }
152

153

154

155
                $expr->setTable(str_replace('`', '', $desc['table']));
18✔
156
                switch ($desc['join_type']) {
18✔
157
                    case 'CROSS':
18✔
158
                        $joinType = 'CROSS JOIN';
1✔
159
                        break;
1✔
160
                    case 'JOIN':
18✔
161
                        $joinType = 'JOIN';
18✔
162
                        break;
18✔
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);
18✔
185

186
                if (isset($desc['alias']['name'])) {
18✔
187
                    $expr->setAlias($desc['alias']['name']);
7✔
188
                }
189
                $subTreeNodes = self::buildFromSubtree($desc['ref_clause']);
18✔
190
                if ($subTreeNodes) {
18✔
191
                    $expr->setRefClause(self::simplify($subTreeNodes));
1✔
192
                }
193

194
                if (isset($desc['hints']) && $desc['hints']) {
18✔
195
                    $expr->setHints(array_map(static function (array $hint) {
1✔
196
                        return new Hint($hint['hint_type'], $hint['hint_list']);
1✔
197
                    }, $desc['hints']));
1✔
198
                }
199

200
                // Debug:
201
                unset($desc['base_expr']);
18✔
202
                unset($desc['expr_type']);
18✔
203
                if (!empty($desc['sub_tree'])) {
18✔
204
                    error_log('MagicQuery - NodeFactory: Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
×
205
                }
206
                unset($desc['sub_tree']);
18✔
207
                unset($desc['join_type']);
18✔
208
                unset($desc['alias']);
18✔
209
                unset($desc['table']);
18✔
210
                unset($desc['ref_type']);
18✔
211
                unset($desc['ref_clause']);
18✔
212
                unset($desc['hints']);
18✔
213
                unset($desc['no_quotes']);
18✔
214
                if (!empty($desc)) {
18✔
215
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
216
                }
217

218
                return $expr;
18✔
219
            case ExpressionType::SUBQUERY:
220
                $expr = new SubQuery();
3✔
221

222
                $expr->setSubQuery(self::buildFromSubtree($desc['sub_tree']));
3✔
223

224
                if (isset($desc['join_type'])) {
3✔
225
                    $expr->setJoinType($desc['join_type']);
1✔
226
                }
227

228
                if (isset($desc['alias']['name'])) {
3✔
229
                    $expr->setAlias($desc['alias']['name']);
1✔
230
                }
231

232
                if (isset($desc['ref_clause'])) {
3✔
233
                    $subTreeNodes = self::buildFromSubtree($desc['ref_clause']);
1✔
234
                    if ($subTreeNodes) {
1✔
235
                        $expr->setRefClause(self::simplify($subTreeNodes));
×
236
                    }
237
                }
238

239
                // Debug:
240
                unset($desc['base_expr']);
3✔
241
                unset($desc['expr_type']);
3✔
242
                unset($desc['sub_tree']);
3✔
243
                unset($desc['join_type']);
3✔
244
                unset($desc['alias']);
3✔
245
                unset($desc['sub_tree']);
3✔
246
                unset($desc['ref_type']);
3✔
247
                unset($desc['ref_clause']);
3✔
248
                unset($desc['hints']);
3✔
249
                if (!empty($desc)) {
3✔
250
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
251
                }
252

253
                return $expr;
3✔
254
            case ExpressionType::AGGREGATE_FUNCTION:
255
                $expr = new AggregateFunction();
2✔
256
                $expr->setFunctionName($desc['base_expr']);
2✔
257

258
                $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
2✔
259

260
                if (isset($desc['alias'])) {
2✔
261
                    $expr->setAlias($desc['alias']);
2✔
262
                }
263

264
                if (isset($desc['direction'])) {
2✔
265
                    $expr->setDirection($desc['direction']);
1✔
266
                }
267

268
                // Debug:
269
                unset($desc['base_expr']);
2✔
270
                unset($desc['expr_type']);
2✔
271
                unset($desc['sub_tree']);
2✔
272
                unset($desc['alias']);
2✔
273
                unset($desc['delim']);
2✔
274
                unset($desc['direction']);
2✔
275
                if (!empty($desc)) {
2✔
276
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in aggregate function: '.var_export($desc, true));
×
277
                }
278

279
                return $expr;
2✔
280
            case ExpressionType::SIMPLE_FUNCTION:
281
                $expr = new SimpleFunction();
2✔
282
                $expr->setBaseExpression($desc['base_expr']);
2✔
283

284
                if (isset($desc['sub_tree'])) {
2✔
285
                    $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
2✔
286
                }
287

288
                if (isset($desc['alias']['name'])) {
2✔
289
                    $expr->setAlias($desc['alias']['name']);
1✔
290
                }
291
                if (isset($desc['direction'])) {
2✔
292
                    $expr->setDirection($desc['direction']);
×
293
                }
294

295
                // Debug:
296
                unset($desc['base_expr']);
2✔
297
                unset($desc['expr_type']);
2✔
298
                unset($desc['sub_tree']);
2✔
299
                unset($desc['alias']);
2✔
300
                unset($desc['direction']);
2✔
301
                unset($desc['delim']);
2✔
302
                unset($desc['no_quotes']);
2✔
303
                if (!empty($desc)) {
2✔
304
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in simple function: '.var_export($desc, true));
×
305
                }
306

307
                return $expr;
2✔
308
            case ExpressionType::RESERVED:
309
                if (in_array(strtoupper($desc['base_expr']), ['CASE', 'WHEN', 'THEN', 'ELSE', 'END'])) {
2✔
310
                    $operator = new Operator();
1✔
311
                    $operator->setValue($desc['base_expr']);
1✔
312
                    // Debug:
313
                    unset($desc['base_expr']);
1✔
314
                    unset($desc['expr_type']);
1✔
315
                    if (!empty($desc['sub_tree'])) {
1✔
316
                        error_log('MagicQuery - NodeFactory: Unexpected operator with subtree: '.var_export($desc['sub_tree'], true));
×
317
                    }
318
                    unset($desc['sub_tree']);
1✔
319
                    unset($desc['delim']);
1✔
320
                    if (!empty($desc)) {
1✔
321
                        error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
322
                    }
323

324
                    return $operator;
1✔
325
                } else {
326
                    $res = new Reserved();
2✔
327
                    $res->setBaseExpression($desc['base_expr']);
2✔
328

329
                    if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) {
2✔
330
                        $res->setBrackets(true);
×
331
                    }
332

333
                    // Debug:
334
                    unset($desc['base_expr']);
2✔
335
                    unset($desc['expr_type']);
2✔
336
                    unset($desc['sub_tree']);
2✔
337
                    unset($desc['alias']);
2✔
338
                    unset($desc['direction']);
2✔
339
                    unset($desc['delim']);
2✔
340
                    if (!empty($desc)) {
2✔
341
                        error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
342
                    }
343

344
                    return $res;
2✔
345
                }
346
            case ExpressionType::USER_VARIABLE:
347
            case ExpressionType::SESSION_VARIABLE:
348
            case ExpressionType::GLOBAL_VARIABLE:
349
            case ExpressionType::LOCAL_VARIABLE:
350
            case ExpressionType::EXPRESSION:
351
            case ExpressionType::BRACKET_EXPRESSION:
352
            case ExpressionType::TABLE_EXPRESSION:
353

354
            case ExpressionType::IN_LIST:
355

356
            case ExpressionType::SIGN:
357
            case ExpressionType::RECORD:
358

359
            case ExpressionType::MATCH_ARGUMENTS:
360

361
            case ExpressionType::ALIAS:
362
            case ExpressionType::POSITION:
363

364
            case ExpressionType::TEMPORARY_TABLE:
365
            case ExpressionType::VIEW:
366
            case ExpressionType::DATABASE:
367
            case ExpressionType::SCHEMA:
368
                $expr = new Expression();
5✔
369
                $expr->setBaseExpression($desc['base_expr']);
5✔
370

371
                if (isset($desc['sub_tree'])) {
5✔
372
                    $expr->setSubTree(self::buildFromSubtree($desc['sub_tree']));
5✔
373
                }
374

375
                if (isset($desc['alias']['name'])) {
5✔
376
                    $expr->setAlias($desc['alias']['name']);
2✔
377
                }
378
                if (isset($desc['direction'])) {
5✔
379
                    $expr->setDirection($desc['direction']);
2✔
380
                }
381

382
                if ($desc['expr_type'] == ExpressionType::BRACKET_EXPRESSION) {
5✔
383
                    $expr->setBrackets(true);
2✔
384
                }
385

386
                if ($desc['expr_type'] == ExpressionType::IN_LIST) {
5✔
387
                    $expr->setBrackets(true);
2✔
388
                    $expr->setDelimiter(',');
2✔
389
                }
390

391
                // Debug:
392
                unset($desc['base_expr']);
5✔
393
                unset($desc['expr_type']);
5✔
394
                unset($desc['sub_tree']);
5✔
395
                unset($desc['alias']);
5✔
396
                unset($desc['direction']);
5✔
397
                unset($desc['delim']);
5✔
398
                unset($desc['no_quotes']);
5✔
399
                if (!empty($desc)) {
5✔
400
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
401
                }
402

403
                return $expr;
5✔
404
            case ExpressionType::MATCH_MODE:
405
                $expr = new ConstNode();
1✔
406
                $expr->setValue($desc['base_expr']);
1✔
407
                $expr->setIsString(false);
1✔
408

409
                // Debug:
410
                unset($desc['base_expr']);
1✔
411
                unset($desc['expr_type']);
1✔
412
                unset($desc['sub_tree']);
1✔
413
                unset($desc['alias']);
1✔
414
                unset($desc['direction']);
1✔
415
                unset($desc['delim']);
1✔
416
                if (!empty($desc)) {
1✔
417
                    error_log('MagicQuery - NodeFactory: Unexpected parameters in exception: '.var_export($desc, true));
×
418
                }
419

420
                return $expr;
1✔
421
            default:
422
                throw new \Exception('Unknown expression type');
×
423
        }
424
    }
425

426

427
    /**
428
     * Transforms a limit or offset value/parameter into a node.
429
     *
430
     * @param string $value
431
     * @return NodeInterface
432
     */
433
    public static function toLimitNode($value)
434
    {
435
        if (substr($value, 0, 1) === ':') {
2✔
436
            $instance = new UnquotedParameter();
2✔
437
            $instance->setName(substr($value, 1));
2✔
438
        } else {
439
            $instance = new LimitNode();
2✔
440
            $expr = $value;
2✔
441
            if (strpos($expr, "'") === 0) {
2✔
442
                $expr = substr($expr, 1);
×
443
            }
444
            if (strrpos($expr, "'") === strlen($expr) - 1) {
2✔
445
                $expr = substr($expr, 0, -1);
×
446
            }
447
            $expr = stripslashes($expr);
2✔
448

449
            $instance->setValue($expr);
2✔
450
        }
451
        return $instance;
2✔
452
    }
453

454
    private static function buildFromSubtree($subTree)
455
    {
456
        if ($subTree && is_array($subTree)) {
18✔
457
            //if (isset($subTree['SELECT'])) {
458
            // If the subtree is a map instead of a list, we are likely to be on a SUBSELECT statement.
459
            if (!empty($subTree) && !isset($subTree[0])) {
7✔
460
                $subTree = StatementFactory::toObject($subTree);
3✔
461
            } else {
462
                $subTree = self::mapArrayToNodeObjectList($subTree);
5✔
463
            }
464
        }
465

466
        return $subTree;
18✔
467
    }
468

469
    /**
470
     * @param array $items An array of objects represented as SQLParser arrays.
471
     */
472
    public static function mapArrayToNodeObjectList(array $items)
473
    {
474
        $list = [];
18✔
475

476
        $nextAndPartOfBetween = false;
18✔
477

478
        // Special case, let's replace the AND of a between with a ANDBETWEEN object.
479
        foreach ($items as $item) {
18✔
480
            $obj = NodeFactory::toObject($item);
18✔
481
            if ($obj instanceof Operator) {
18✔
482
                if ($obj->getValue() == 'BETWEEN') {
15✔
483
                    $nextAndPartOfBetween = true;
1✔
484
                } elseif ($nextAndPartOfBetween && $obj->getValue() == 'AND') {
15✔
485
                    $nextAndPartOfBetween = false;
1✔
486
                    $obj->setValue('AND_FROM_BETWEEN');
1✔
487
                }
488
            }
489
            $list[] = $obj;
18✔
490
        }
491

492
        return $list;
18✔
493
    }
494

495
    private static $PRECEDENCE = array(
496
            array('INTERVAL'),
497
            array('BINARY', 'COLLATE'),
498
            array('!'),
499
            array(/*'-'*/ /* (unary minus) ,*/ '~' /*(unary bit inversion)*/),
500
            array('^'),
501
            array('*', '/', 'DIV', '%', 'MOD'),
502
            array('-', '+'),
503
            array('<<', '>>'),
504
            array('&'),
505
            array('|'),
506
            array('=' /*(comparison)*/, '<=>', '>=', '>', '<=', '<', '<>', '!=', 'IS', 'LIKE', 'REGEXP', 'IN', 'IS NOT', 'NOT IN', 'NOT LIKE'),
507
            array('AND_FROM_BETWEEN'),
508
            array('THEN'),
509
            array('WHEN'),
510
            array('ELSE'),
511
            array('BETWEEN', 'CASE', 'END'),
512
            array('NOT'),
513
            array('&&', 'AND'),
514
            array('XOR'),
515
            array('||', 'OR'), );
516

517
    private static $OPERATOR_TO_CLASS = array(
518
            '=' => 'SQLParser\Node\Equal',
519
            '<' => 'SQLParser\Node\Less',
520
            '>' => 'SQLParser\Node\Greater',
521
            '<=' => 'SQLParser\Node\LessOrEqual',
522
            '>=' => 'SQLParser\Node\GreaterOrEqual',
523
            //'<=>' => '????',
524
            '<>' => 'SQLParser\Node\Different',
525
            '!=' => 'SQLParser\Node\Different',
526
            'IS' => 'SQLParser\Node\Is',
527
            'IS NOT' => 'SQLParser\Node\IsNot',
528
            'LIKE' => 'SQLParser\Node\Like',
529
            'NOT LIKE' => 'SQLParser\Node\NotLike',
530
            'REGEXP' => 'SQLParser\Node\Regexp',
531
            'IN' => 'SQLParser\Node\In',
532
            'NOT IN' => 'SQLParser\Node\NotIn',
533
            '+' => 'SQLParser\Node\Plus',
534
            '-' => 'SQLParser\Node\Minus',
535
            '*' => 'SQLParser\Node\Multiply',
536
            '/' => 'SQLParser\Node\Divide',
537
            '%' => 'SQLParser\Node\Modulo',
538
            'MOD' => 'SQLParser\Node\Modulo',
539
            'DIV' => 'SQLParser\Node\Div',
540
            '&' => 'SQLParser\Node\BitwiseAnd',
541
            '|' => 'SQLParser\Node\BitwiseOr',
542
            '^' => 'SQLParser\Node\BitwiseXor',
543
            '<<' => 'SQLParser\Node\ShiftLeft',
544
            '>>' => 'SQLParser\Node\ShiftRight',
545
            '<=>' => 'SQLParser\Node\NullCompatibleEqual',
546
            'AND' => 'SQLParser\Node\AndOp',
547
            '&&' => 'SQLParser\Node\AndOp',
548
            '||' => 'SQLParser\Node\OrOp',
549
            'OR' => 'SQLParser\Node\OrOp',
550
            'XOR' => 'SQLParser\Node\XorOp',
551
            'THEN' => 'SQLParser\Node\Then',
552
            'ELSE' => 'SQLParser\Node\ElseOperation',
553
    );
554

555
    /**
556
     * Takes an array of nodes (including operators) and try to build a tree from it.
557
     *
558
     * @param NodeInterface[]|NodeInterface $nodes
559
     *
560
     * @return NodeInterface[]|NodeInterface
561
     */
562
    public static function simplify($nodes)
563
    {
564
        if (empty($nodes)) {
18✔
565
            $nodes = array();
3✔
566
        } elseif (!is_array($nodes)) {
18✔
567
            $nodes = array($nodes);
2✔
568
        }
569
        $minPriority = -1;
18✔
570
        $selectedOperators = array();
18✔
571
        $lastSelectedOperator = '';
18✔
572
        $differentOperatorWithSamePriority = false;
18✔
573

574
        // Let's transform "NOT" + "IN" into "NOT IN"
575
        $newNodes = array();
18✔
576
        $nodesCount = count($nodes);
18✔
577
        for ($i = 0; $i < $nodesCount; ++$i) {
18✔
578
            $node = $nodes[$i];
18✔
579
            if ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator
18✔
580
                    && strtoupper($node->getValue()) == 'IS' && strtoupper($nodes[$i + 1]->getValue()) == 'NOT') {
18✔
581
                $notIn = new Operator();
×
582
                $notIn->setValue('IS NOT');
×
583
                $newNodes[] = $notIn;
×
584
                ++$i;
×
585
            } elseif ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator
18✔
586
                    && strtoupper($node->getValue()) == 'NOT' && strtoupper($nodes[$i + 1]->getValue()) == 'IN') {
18✔
587
                $notIn = new Operator();
2✔
588
                $notIn->setValue('NOT IN');
2✔
589
                $newNodes[] = $notIn;
2✔
590
                ++$i;
2✔
591
            } elseif ($node instanceof Operator && isset($nodes[$i + 1]) && $nodes[$i + 1] instanceof Operator
18✔
592
                    && strtoupper($node->getValue()) == 'NOT' && strtoupper($nodes[$i + 1]->getValue()) == 'LIKE') {
18✔
593
                $notLike = new Operator();
1✔
594
                $notLike->setValue('NOT LIKE');
1✔
595
                $newNodes[] = $notLike;
1✔
596
                ++$i;
1✔
597
            } else {
598
                $newNodes[] = $node;
18✔
599
            }
600
        }
601
        $nodes = $newNodes;
18✔
602

603
        // Handle AGAINST function. Without this patch params will be placed after AGAINST() and not inside the brackets
604
        $newNodes = array();
18✔
605
        $nodesCount = count($nodes);
18✔
606
        for ($i = 0; $i < $nodesCount; ++$i) {
18✔
607
            $node = $nodes[$i];
18✔
608
            if ($node instanceof SimpleFunction && $node->getBaseExpression() === 'AGAINST' && isset($nodes[$i + 1])) {
18✔
609
                $node->setSubTree($nodes[$i + 1]);
1✔
610
                $i++;
1✔
611
            }
612
            $newNodes[] = $node;
18✔
613
        }
614
        $nodes = $newNodes;
18✔
615

616
        // Let's find the highest level operator.
617
        for ($i = count($nodes) - 1; $i >= 0; --$i) {
18✔
618
            $node = $nodes[$i];
18✔
619
            if ($node instanceof Operator) {
18✔
620
                $priority = self::getOperatorPrecedence($node);
15✔
621

622
                if ($priority == $minPriority && $lastSelectedOperator != strtoupper($node->getValue())) {
15✔
623
                    $differentOperatorWithSamePriority = true;
2✔
624
                } elseif ($priority > $minPriority) {
15✔
625
                    $minPriority = $priority;
15✔
626
                    $selectedOperators = array($node);
15✔
627
                    $lastSelectedOperator = strtoupper($node->getValue());
15✔
628
                } else {
629
                    if (strtoupper($node->getValue()) == $lastSelectedOperator && !$differentOperatorWithSamePriority) {
5✔
630
                        $selectedOperators[] = $node;
1✔
631
                    }
632
                }
633
            }
634
        }
635
        $selectedOperators = array_reverse($selectedOperators);
18✔
636

637
        // At this point, the $selectedOperator list contains a list of operators of the same kind that will apply
638
        // at the same time.
639
        if (empty($selectedOperators)) {
18✔
640
            // If we have an Expression with no base expression, let's simply discard it.
641
            // Indeed, the tree will add brackets by itself, and no Expression is needed for that.
642
            $newNodes = array();
18✔
643
            /*foreach ($nodes as $key=>$operand) {
644
                if ($operand instanceof Expression) {
645
                    $subTree = $operand->getSubTree();
646
                    if (count($subTree) == 1) {
647
                        $nodes[$key] = self::simplify($subTree);
648
                    }
649
                }
650
            }*/
651
            foreach ($nodes as $operand) {
18✔
652
                if ($operand instanceof Expression) {
18✔
653
                    if (empty($operand->getBaseExpression())) {
5✔
654
                        $subTree = $operand->getSubTree();
×
655
                        if (is_array($subTree) && count($subTree) === 1) {
×
NEW
656
                            $simplifiedSubTree = self::simplify($subTree);
×
657
                            \assert(is_array($simplifiedSubTree));
NEW
658
                            $newNodes = array_merge($newNodes, $simplifiedSubTree);
×
659
                        } else {
660
                            $newNodes[] = $operand;
×
661
                        }
662
                    } else {
663
                        $newNodes[] = $operand;
5✔
664
                    }
665
                } else {
666
                    $newNodes[] = $operand;
18✔
667
                }
668
            }
669

670
            return $newNodes;
18✔
671
        }
672

673
        // Let's grab the operands of the operator.
674
        $operands = array();
15✔
675
        $operand = array();
15✔
676
        $tmpOperators = $selectedOperators;
15✔
677
        $nextOperator = array_shift($tmpOperators);
15✔
678

679
        $isSelectedOperatorFirst = null;
15✔
680

681
        foreach ($nodes as $node) {
15✔
682
            if ($node === $nextOperator) {
15✔
683
                if ($isSelectedOperatorFirst === null) {
15✔
684
                    $isSelectedOperatorFirst = true;
1✔
685
                }
686
                // Let's apply the "simplify" method on the operand before storing it.
687
                //$operands[] = self::simplify($operand);
688
                $simple = self::simplify($operand);
15✔
689
                if (is_array($simple)) {
15✔
690
                    $operands = array_merge($operands, $simple);
15✔
691
                } else {
692
                    $operands[] = $simple;
5✔
693
                }
694

695
                $operand = array();
15✔
696
                $nextOperator = array_shift($tmpOperators);
15✔
697
            } else {
698
                if ($isSelectedOperatorFirst === null) {
15✔
699
                    $isSelectedOperatorFirst = false;
15✔
700
                }
701
                $operand[] = $node;
15✔
702
            }
703
        }
704
        //$operands[] = self::simplify($operand);
705
        //$operands = array_merge($operands, self::simplify($operand));
706
        $simple = self::simplify($operand);
15✔
707
        if (is_array($simple)) {
15✔
708
            $operands = array_merge($operands, $simple);
15✔
709
        } else {
710
            $operands[] = $simple;
5✔
711
        }
712

713
        // Now, if we have an Expression, let's simply discard it.
714
        // Indeed, the tree will add brackets by itself, and no Expression in needed for that.
715
        /*foreach ($operands as $key=>$operand) {
716
            if ($operand instanceof Expression) {
717
                $subTree = $operand->getSubTree();
718
                if (count($subTree) == 1) {
719
                    $operands[$key] = self::simplify($subTree);
720
                }
721
            }
722
        }*/
723

724
        $operation = strtoupper($selectedOperators[0]->getValue());
15✔
725

726
        /* TODO:
727
        Remaining operators to code:
728
        array('INTERVAL'),
729
        array('BINARY', 'COLLATE'),
730
        array('!'),
731
        array('NOT'),
732
        */
733

734
        if (isset(self::$OPERATOR_TO_CLASS[$operation]) && is_subclass_of(self::$OPERATOR_TO_CLASS[$operation], 'SQLParser\Node\AbstractTwoOperandsOperator')) {
15✔
735
            if (count($operands) != 2) {
15✔
736
                throw new MagicQueryException('An error occured while parsing SQL statement. Invalid character found next to "'.$operation.'"');
1✔
737
            }
738

739
            $leftOperand = array_shift($operands);
14✔
740
            $rightOperand = array_shift($operands);
14✔
741

742
            $instance = new self::$OPERATOR_TO_CLASS[$operation]();
14✔
743
            $instance->setLeftOperand($leftOperand);
14✔
744
            $instance->setRightOperand($rightOperand);
14✔
745

746
            return $instance;
14✔
747
        } elseif (isset(self::$OPERATOR_TO_CLASS[$operation]) && is_subclass_of(self::$OPERATOR_TO_CLASS[$operation], 'SQLParser\Node\AbstractManyInstancesOperator')) {
5✔
748
            $instance = new self::$OPERATOR_TO_CLASS[$operation]();
5✔
749
            $instance->setOperands($operands);
5✔
750

751
            return $instance;
5✔
752
        } elseif ($operation === 'BETWEEN') {
1✔
753
            $leftOperand = array_shift($operands);
1✔
754
            $rightOperand = array_shift($operands);
1✔
755
            if (!$rightOperand instanceof Operation || $rightOperand->getOperatorSymbol() !== 'AND_FROM_BETWEEN') {
1✔
756
                throw new MagicQueryException('Missing AND in BETWEEN filter.');
×
757
            }
758

759
            $innerOperands = $rightOperand->getOperands();
1✔
760
            $minOperand = array_shift($innerOperands);
1✔
761
            $maxOperand = array_shift($innerOperands);
1✔
762

763
            $instance = new Between();
1✔
764
            $instance->setLeftOperand($leftOperand);
1✔
765
            $instance->setMinValueOperand($minOperand);
1✔
766
            $instance->setMaxValueOperand($maxOperand);
1✔
767

768
            return $instance;
1✔
769
        } elseif ($operation === 'WHEN') {
1✔
770
            $instance = new WhenConditions();
1✔
771

772
            if (!$isSelectedOperatorFirst) {
1✔
773
                $value = array_shift($operands);
1✔
774
                $instance->setValue($value);
1✔
775
            }
776
            $instance->setOperands($operands);
1✔
777

778
            return $instance;
1✔
779
        } elseif ($operation === 'CASE') {
1✔
780
            $innerOperation = array_shift($operands);
1✔
781

782
            if (!empty($operands)) {
1✔
783
                throw new MagicQueryException('A CASE statement should contain only a ThenConditions or a ElseOperand object.');
×
784
            }
785

786
            $instance = new CaseOperation();
1✔
787
            $instance->setOperation($innerOperation);
1✔
788

789
            return $instance;
1✔
790
        } elseif ($operation === 'END') {
1✔
791
            // Simply bypass the END operation. We already have a CASE matching node:
792
            $caseOperation = array_shift($operands);
1✔
793

794
            return $caseOperation;
1✔
795
        } else {
796
            $instance = new Operation();
1✔
797
            $instance->setOperatorSymbol($operation);
1✔
798
            $instance->setOperands($operands);
1✔
799

800
            return $instance;
1✔
801
        }
802
    }
803

804
    /**
805
     * Finds the precedence for operator $node (highest number has the least precedence).
806
     *
807
     * @param Operator $node
808
     *
809
     * @throws MagicQueryException
810
     *
811
     * @return int
812
     */
813
    private static function getOperatorPrecedence(Operator $node): int
814
    {
815
        $value = strtoupper($node->getValue());
15✔
816

817
        foreach (self::$PRECEDENCE as $priority => $arr) {
15✔
818
            foreach ($arr as $op) {
15✔
819
                if ($value == $op) {
15✔
820
                    return $priority;
15✔
821
                }
822
            }
823
        }
824
        throw new MagicQueryException('Unknown operator precedence for operator '.$value);
×
825
    }
826

827
    /**
828
     * @param mixed       $node        a node of a recursive array of node
829
     * @param MoufManager $moufManager
830
     *
831
     * @return MoufInstanceDescriptor
832
     */
833
    public static function nodeToInstanceDescriptor($node, MoufManager $moufManager)
834
    {
835
        return self::array_map_deep($node, function ($item) use ($moufManager) {
×
836
            if ($item instanceof NodeInterface) {
×
837
                return $item->toInstanceDescriptor($moufManager);
×
838
            } else {
839
                return $item;
×
840
            }
841
        });
×
842
    }
843

844
    private static function array_map_deep($array, $callback)
845
    {
846
        $new = array();
×
847
        if (is_array($array)) {
×
848
            foreach ($array as $key => $val) {
×
849
                if (is_array($val)) {
×
850
                    $new[$key] = self::array_map_deep($val, $callback);
×
851
                } else {
852
                    $new[$key] = call_user_func($callback, $val);
×
853
                }
854
            }
855
        } else {
856
            $new = call_user_func($callback, $array);
×
857
        }
858

859
        return $new;
×
860
    }
861

862
    /**
863
     * Tansforms the array of nodes (or the node) passed in parameter into a SQL string.
864
     *
865
     * @param mixed       $nodes          Recursive array of node interface
866
     * @param AbstractPlatform  $platform
867
     * @param array       $parameters
868
     * @param string      $delimiter
869
     * @param bool|string $wrapInBrackets
870
     * @param int         $indent
871
     * @param int         $conditionsMode
872
     *
873
     * @return null|string
874
     */
875
    public static function toSql($nodes, AbstractPlatform $platform, array $parameters = array(), $delimiter = ',', $wrapInBrackets = true, $indent = 0, $conditionsMode = SqlRenderInterface::CONDITION_APPLY, bool $extrapolateParameters = true)
876
    {
877
        if (is_array($nodes)) {
12✔
878
            $elems = array();
12✔
879
            array_walk_recursive($nodes, function ($item) use (&$elems, $platform, $indent, $parameters, $conditionsMode, $extrapolateParameters) {
12✔
880
                if ($item instanceof SqlRenderInterface) {
12✔
881
                    $itemSql = $item->toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters);
12✔
882
                    if ($itemSql !== null) {
12✔
883
                        $elems[] = str_repeat(' ', $indent).$itemSql;
12✔
884
                    }
885
                } else {
886
                    if ($item !== null) {
×
887
                        $elems[] = str_repeat(' ', $indent).$item;
×
888
                    }
889
                }
890
            });
12✔
891
            $sql = implode($delimiter, $elems);
12✔
892
        } else {
893
            $item = $nodes;
9✔
894
            if ($item instanceof SqlRenderInterface) {
9✔
895
                $itemSql = $item->toSql($parameters, $platform, $indent, $conditionsMode, $extrapolateParameters);
9✔
896
                if ($itemSql === null || $itemSql === '') {
8✔
897
                    return null;
2✔
898
                }
899
                $sql = str_repeat(' ', $indent).$itemSql;
8✔
900
            } else {
901
                if ($item === null || $item === '') {
×
902
                    return null;
×
903
                }
904
                $sql = str_repeat(' ', $indent).$item;
×
905
            }
906
        }
907
        if ($wrapInBrackets) {
12✔
908
            $sql = '('.$sql.')';
5✔
909
        }
910

911
        return $sql;
12✔
912
    }
913
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc