Completed
Push — master ( ff61aa...6aad8a )
by Alex
21s queued 15s
created

ExpressionParser::getLexer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace POData\UriProcessor\QueryProcessor\ExpressionParser;
6
7
use POData\Common\Messages;
8
use POData\Common\NotImplementedException;
9
use POData\Common\ODataConstants;
10
use POData\Common\ODataException;
11
use POData\Providers\Metadata\ResourcePropertyKind;
12
use POData\Providers\Metadata\ResourceType;
13
use POData\Providers\Metadata\Type\Binary;
14
use POData\Providers\Metadata\Type\Boolean;
15
use POData\Providers\Metadata\Type\DateTime;
16
use POData\Providers\Metadata\Type\Decimal;
17
use POData\Providers\Metadata\Type\Double;
18
use POData\Providers\Metadata\Type\Guid;
19
use POData\Providers\Metadata\Type\Int32;
20
use POData\Providers\Metadata\Type\Int64;
21
use POData\Providers\Metadata\Type\IType;
22
use POData\Providers\Metadata\Type\Null1;
23
use POData\Providers\Metadata\Type\Single;
24
use POData\Providers\Metadata\Type\StringType;
25
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\AbstractExpression;
26
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ArithmeticExpression;
27
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ConstantExpression;
28
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ExpressionType;
29
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\FunctionCallExpression;
30
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\LogicalExpression;
31
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\PropertyAccessExpression;
32
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\RelationalExpression;
33
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\UnaryExpression;
34
use POData\UriProcessor\QueryProcessor\FunctionDescription;
35
36
/**
37
 * Class ExpressionParser.
38
 */
39
class ExpressionParser
40
{
41
    const RECURSION_LIMIT = 200;
42
43
    /**
44
     * The Lexical analyzer.
45
     *
46
     * @var ExpressionLexer
47
     */
48
    private $lexer;
49
50
    /**
51
     * The current recursion depth.
52
     *
53
     * @var int
54
     */
55
    private $recursionDepth;
56
57
    /**
58
     * The ResourceType on which $filter condition needs to be applied.
59
     *
60
     * @var ResourceType
61
     */
62
    private $resourceType;
63
64
    /**
65
     * @var bool
66
     */
67
    private $isPHPExpressionProvider;
68
69
    /**
70
     * True if the filter expression contains level 2 property access, for example
71
     * Customers?$filter=Address/LineNumber eq 12
72
     * Customer?$filter=Order/OrderID gt 1234
73
     * False otherwise.
74
     *
75
     * @var bool
76
     */
77
    private $hasLevel2PropertyInTheExpression;
78
79
    /**
80
     * Construct a new instance of ExpressionParser.
81
     *
82
     * @param string       $text                    The expression to parse
83
     * @param ResourceType $resourceType            The resource type of the resource targeted by the resource path
84
     * @param bool         $isPHPExpressionProvider
85
     *
86
     * TODO Expression parser should not depend on the fact that end user is implementing IExpressionProvider or not
87
     * @throws ODataException
88
     */
89
    public function __construct($text, ResourceType $resourceType, $isPHPExpressionProvider)
90
    {
91
        $this->lexer                            = new ExpressionLexer($text);
92
        $this->resourceType                     = $resourceType;
93
        $this->isPHPExpressionProvider          = $isPHPExpressionProvider;
94
        $this->hasLevel2PropertyInTheExpression = false;
95
    }
96
97
    /**
98
     * Checks whether the expression contains level 2 property access.
99
     *
100
     * @return bool
101
     */
102
    public function hasLevel2Property()
103
    {
104
        return $this->hasLevel2PropertyInTheExpression;
105
    }
106
107
    /**
108
     * Get the current token from lexer.
109
     *
110
     * @return ExpressionToken
111
     */
112
    private function getCurrentToken()
113
    {
114
        return $this->getLexer()->getCurrentToken();
115
    }
116
117
    /**
118
     * Set the current token in lexer.
119
     *
120
     * @param ExpressionToken $token The token to set as current token
121
     */
122
    private function setCurrentToken(ExpressionToken $token)
123
    {
124
        $this->getLexer()->setCurrentToken($token);
125
    }
126
127
    /**
128
     * Resets parser with new expression string.
129
     *
130
     * @param  string         $text Reset the expression to parse
131
     * @throws ODataException
132
     */
133
    public function resetParser($text)
134
    {
135
        $this->lexer          = new ExpressionLexer($text);
136
        $this->recursionDepth = 0;
137
    }
138
139
    /**
140
     * Parse the expression in filter option.
141
     *
142
     * @throws NotImplementedException
143
     * @throws ODataException
144
     * @throws \ReflectionException
145
     * @return AbstractExpression
146
     */
147
    public function parseFilter()
148
    {
149
        return $this->parseExpression();
150
    }
151
152
    /**
153
     * Start parsing the expression.
154
     *
155
     * @throws NotImplementedException
156
     * @throws ODataException
157
     * @throws \ReflectionException
158
     * @return AbstractExpression
159
     */
160
    private function parseExpression()
161
    {
162
        $this->recurseEnter();
163
        $expr = $this->parseLogicalOr();
164
        $this->recurseLeave();
165
166
        return $expr;
167
    }
168
169
    /**
170
     * Parse logical or (or).
171
     *
172
     * @throws NotImplementedException
173
     * @throws ODataException
174
     * @throws \ReflectionException
175
     * @return AbstractExpression
176
     */
177
    private function parseLogicalOr()
178
    {
179
        $this->recurseEnter();
180
        $left = $this->parseLogicalAnd();
181
        while ($this->tokenIdentifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_OR)) {
182
            $logicalOpToken = clone $this->getCurrentToken();
183
            $this->getLexer()->nextToken();
184
            $right = $this->parseLogicalAnd();
185
            FunctionDescription::verifyLogicalOpArguments($logicalOpToken, $left, $right);
186
            $left = new LogicalExpression(
187
                $left,
188
                $right,
189
                ExpressionType::OR_LOGICAL()
190
            );
191
        }
192
193
        $this->recurseLeave();
194
195
        return $left;
196
    }
197
198
    /**
199
     * Parse logical and (and).
200
     *
201
     * @throws NotImplementedException
202
     * @throws ODataException
203
     * @throws \ReflectionException
204
     * @return AbstractExpression
205
     */
206
    private function parseLogicalAnd()
207
    {
208
        $this->recurseEnter();
209
        $left = $this->parseComparison();
210
        while ($this->tokenIdentifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_AND)) {
211
            $logicalOpToken = clone $this->getCurrentToken();
212
            $this->getLexer()->nextToken();
213
            $right = $this->parseComparison();
214
            FunctionDescription::verifyLogicalOpArguments($logicalOpToken, $left, $right);
215
            $left = new LogicalExpression($left, $right, ExpressionType::AND_LOGICAL());
216
        }
217
218
        $this->recurseLeave();
219
220
        return $left;
221
    }
222
223
    /**
224
     * Parse comparison operation (eq, ne, gt, ge, lt, le).
225
     *
226
     * @throws NotImplementedException
227
     * @throws ODataException
228
     * @throws \ReflectionException
229
     * @return AbstractExpression
230
     */
231
    private function parseComparison()
232
    {
233
        $this->recurseEnter();
234
        $left = $this->parseAdditive();
235
        while ($this->getCurrentToken()->isComparisonOperator()) {
236
            $comparisonToken = clone $this->getCurrentToken();
237
            $this->getLexer()->nextToken();
238
            $right = $this->parseAdditive();
239
            $left  = self::generateComparisonExpression(
240
                $left,
241
                $right,
242
                $comparisonToken,
243
                $this->isPHPExpressionProvider
244
            );
245
        }
246
247
        $this->recurseLeave();
248
249
        return $left;
250
    }
251
252
    /**
253
     * Parse additive operation (add, sub).
254
     *
255
     * @throws NotImplementedException
256
     * @throws ODataException
257
     * @throws \ReflectionException
258
     * @return AbstractExpression
259
     */
260
    private function parseAdditive()
261
    {
262
        $this->recurseEnter();
263
        $left = $this->parseMultiplicative();
264
        while ($this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_ADD)
265
            || $this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_SUB)) {
266
            $additiveToken = clone $this->getCurrentToken();
267
            $this->getLexer()->nextToken();
268
            $right        = $this->parseMultiplicative();
269
            $opReturnType = FunctionDescription::verifyAndPromoteArithmeticOpArguments($additiveToken, $left, $right);
270
            if ($additiveToken->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_ADD)) {
271
                $left = new ArithmeticExpression($left, $right, ExpressionType::ADD(), $opReturnType);
272
            } else {
273
                $left = new ArithmeticExpression($left, $right, ExpressionType::SUBTRACT(), $opReturnType);
274
            }
275
        }
276
277
        $this->recurseLeave();
278
279
        return $left;
280
    }
281
282
    /**
283
     * Parse multiplicative operators (mul, div, mod).
284
     *
285
     * @throws NotImplementedException
286
     * @throws ODataException
287
     * @throws \ReflectionException
288
     * @return AbstractExpression
289
     */
290
    private function parseMultiplicative()
291
    {
292
        $this->recurseEnter();
293
        $left = $this->parseUnary();
294
        while ($this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_MULTIPLY)
295
            || $this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_DIVIDE)
296
            || $this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_MODULO)
297
        ) {
298
            $multiplicativeToken = clone $this->getCurrentToken();
299
            $this->getLexer()->nextToken();
300
            $right        = $this->parseUnary();
301
            $opReturnType = FunctionDescription::verifyAndPromoteArithmeticOpArguments(
302
                $multiplicativeToken,
303
                $left,
304
                $right
305
            );
306
            if ($multiplicativeToken->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_MULTIPLY)) {
307
                $left = new ArithmeticExpression($left, $right, ExpressionType::MULTIPLY(), $opReturnType);
308
            } elseif ($multiplicativeToken->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_DIVIDE)) {
309
                $left = new ArithmeticExpression($left, $right, ExpressionType::DIVIDE(), $opReturnType);
310
            } else {
311
                $left = new ArithmeticExpression($left, $right, ExpressionType::MODULO(), $opReturnType);
312
            }
313
        }
314
315
        $this->recurseLeave();
316
317
        return $left;
318
    }
319
320
    /**
321
     * Parse unary operator (- ,not).
322
     *
323
     * @throws NotImplementedException
324
     * @throws ODataException
325
     * @throws \ReflectionException
326
     * @return AbstractExpression
327
     */
328
    private function parseUnary()
329
    {
330
        $this->recurseEnter();
331
332
        if ($this->getCurrentToken()->getId() == ExpressionTokenId::MINUS()
333
            || $this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ODataConstants::KEYWORD_NOT)
334
        ) {
335
            $op = clone $this->getCurrentToken();
336
            $this->getLexer()->nextToken();
337
            if ($op->getId() == ExpressionTokenId::MINUS()
338
                && (ExpressionLexer::isNumeric($this->getCurrentToken()->getId()))
339
            ) {
340
                $numberLiteral           = $this->getCurrentToken();
341
                $numberLiteral->Text     = '-' . $numberLiteral->Text;
342
                $numberLiteral->Position = $op->Position;
343
                $this->setCurrentToken($numberLiteral);
344
                $this->recurseLeave();
345
346
                return $this->parsePrimary();
347
            }
348
349
            $expr = $this->parsePrimary();
350
            FunctionDescription::validateUnaryOpArguments($op, $expr);
351
            if ($op->getId() == ExpressionTokenId::MINUS()) {
352
                $expr = new UnaryExpression($expr, ExpressionType::NEGATE(), $expr->getType());
353
            } else {
354
                $expr = new UnaryExpression($expr, ExpressionType::NOT_LOGICAL(), new Boolean());
355
            }
356
357
            $this->recurseLeave();
358
359
            return $expr;
360
        }
361
362
        $this->recurseLeave();
363
364
        return $this->parsePrimary();
365
    }
366
367
    /**
368
     * Start parsing the primary.
369
     *
370
     * @throws NotImplementedException
371
     * @throws ODataException
372
     * @throws \ReflectionException
373
     * @return AbstractExpression
374
     */
375
    private function parsePrimary()
376
    {
377
        $this->recurseEnter();
378
        $expr = $this->parsePrimaryStart();
379
        while (true) {
380
            if ($this->getCurrentToken()->getId() == ExpressionTokenId::SLASH()) {
381
                $this->getLexer()->nextToken();
382
                $expr = $this->parsePropertyAccess($expr);
383
            } else {
384
                break;
385
            }
386
        }
387
388
        $this->recurseLeave();
389
390
        return $expr;
391
    }
392
393
    /**
394
     * Parse primary tokens [literals, identifiers (e.g. function call), open param for sub expressions].
395
     *
396
     * @throws ODataException
397
     * @throws NotImplementedException
398
     * @throws \ReflectionException
399
     * @return AbstractExpression
400
     */
401
    private function parsePrimaryStart()
402
    {
403
        switch ($this->getLexer()->getCurrentToken()->getId()) {
404
            case ExpressionTokenId::BOOLEAN_LITERAL():
405
                return $this->parseTypedLiteral(new Boolean());
406
            case ExpressionTokenId::DATETIME_LITERAL():
407
                return $this->parseTypedLiteral(new DateTime());
408
            case ExpressionTokenId::DECIMAL_LITERAL():
409
                return $this->parseTypedLiteral(new Decimal());
410
            case ExpressionTokenId::NULL_LITERAL():
411
                return $this->parseNullLiteral();
412
            case ExpressionTokenId::IDENTIFIER():
413
                return $this->parseIdentifier();
414
            case ExpressionTokenId::STRING_LITERAL():
415
                return $this->parseTypedLiteral(new StringType());
416
            case ExpressionTokenId::INT64_LITERAL():
417
                return $this->parseTypedLiteral(new Int64());
418
            case ExpressionTokenId::INTEGER_LITERAL():
419
                return $this->parseTypedLiteral(new Int32());
420
            case ExpressionTokenId::DOUBLE_LITERAL():
421
                return $this->parseTypedLiteral(new Double());
422
            case ExpressionTokenId::SINGLE_LITERAL():
423
                return $this->parseTypedLiteral(new Single());
424
            case ExpressionTokenId::GUID_LITERAL():
425
                return $this->parseTypedLiteral(new Guid());
426
            case ExpressionTokenId::BINARY_LITERAL():
427
                throw new NotImplementedException(
428
                    'Support for binary is not implemented'
429
                );
430
                //return $this->parseTypedLiteral(new Binary());
431
            case ExpressionTokenId::OPENPARAM():
432
                return $this->parseParenExpression();
433
            default:
434
                throw ODataException::createSyntaxError('Expression expected.');
435
        }
436
    }
437
438
    /**
439
     * Parse Sub expression.
440
     *
441
     * @throws ODataException
442
     * @throws NotImplementedException
443
     * @throws \ReflectionException
444
     * @return AbstractExpression
445
     */
446
    private function parseParenExpression()
447
    {
448
        if ($this->getCurrentToken()->getId() != ExpressionTokenId::OPENPARAM()) {
449
            throw ODataException::createSyntaxError('Open parenthesis expected.');
450
        }
451
452
        $this->getLexer()->nextToken();
453
        $expr = $this->parseExpression();
454
        if ($this->getCurrentToken()->getId() != ExpressionTokenId::CLOSEPARAM()) {
455
            throw ODataException::createSyntaxError('Close parenthesis expected.');
456
        }
457
458
        $this->getLexer()->nextToken();
459
460
        return $expr;
461
    }
462
463
    /**
464
     * Parse an identifier.
465
     *
466
     * @throws ODataException
467
     * @throws NotImplementedException
468
     * @throws \ReflectionException
469
     * @return FunctionCallExpression|PropertyAccessExpression
470
     */
471
    private function parseIdentifier()
472
    {
473
        $this->validateToken(ExpressionTokenId::IDENTIFIER());
474
475
        // An open paren here would indicate calling a method
476
        $identifierIsFunction = $this->getLexer()->peekNextToken()->getId() == ExpressionTokenId::OPENPARAM();
477
        if ($identifierIsFunction) {
478
            return $this->parseIdentifierAsFunction();
479
        } else {
480
            return $this->parsePropertyAccess(null);
481
        }
482
    }
483
484
    /**
485
     * Parse a property access.
486
     *
487
     * @param PropertyAccessExpression|null $parentExpression Parent expression
488
     *
489
     * @throws ODataException
490
     * @throws \ReflectionException
491
     *
492
     * @return PropertyAccessExpression
493
     */
494
    private function parsePropertyAccess(PropertyAccessExpression $parentExpression = null)
495
    {
496
        $identifier = $this->getCurrentToken()->getIdentifier();
497
        if (null === $parentExpression) {
498
            $parentResourceType = $this->resourceType;
499
        } else {
500
            $parentResourceType                     = $parentExpression->getResourceType();
501
            $this->hasLevel2PropertyInTheExpression = true;
502
        }
503
504
        $resourceProperty = $parentResourceType->resolveProperty($identifier);
505
        if (null === $resourceProperty) {
506
            throw ODataException::createSyntaxError(
507
                Messages::expressionLexerNoPropertyInType(
508
                    $identifier,
509
                    $parentResourceType->getFullName(),
510
                    $this->getCurrentToken()->Position
511
                )
512
            );
513
        }
514
515
        if ($resourceProperty->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE) {
0 ignored issues
show
introduced by
The condition $resourceProperty->getKi...::RESOURCESET_REFERENCE is always false.
Loading history...
516
            throw ODataException::createSyntaxError(
517
                Messages::expressionParserEntityCollectionNotAllowedInFilter(
518
                    $resourceProperty->getName(),
519
                    $parentResourceType->getFullName(),
520
                    $this->getCurrentToken()->Position
521
                )
522
            );
523
        }
524
525
        $exp = new PropertyAccessExpression($resourceProperty, $parentExpression);
526
        $this->getLexer()->nextToken();
527
528
        return $exp;
529
    }
530
531
    /**
532
     * Try to parse an identifier which is followed by an open bracket as an astoria URI function call.
533
     *
534
     * @throws ODataException
535
     * @throws NotImplementedException
536
     * @throws \ReflectionException
537
     *
538
     * @return FunctionCallExpression
539
     */
540
    private function parseIdentifierAsFunction()
541
    {
542
        $functionToken = clone $this->getCurrentToken();
543
        $functions     = FunctionDescription::verifyFunctionExists($functionToken);
544
        $this->getLexer()->nextToken();
545
        $paramExpressions = $this->parseArgumentList();
546
        $function         = FunctionDescription::verifyFunctionCallOpArguments(
547
            $functions,
548
            $paramExpressions,
549
            $functionToken
550
        );
551
552
        return new FunctionCallExpression($function, $paramExpressions);
553
    }
554
555
    /**
556
     * Start parsing argument list of a function-call.
557
     *
558
     * @throws ODataException
559
     * @throws NotImplementedException
560
     * @throws \ReflectionException
561
     * @return array<AbstractExpression>
562
     */
563
    private function parseArgumentList()
564
    {
565
        if ($this->getCurrentToken()->getId() != ExpressionTokenId::OPENPARAM()) {
566
            throw ODataException::createSyntaxError('Open parenthesis expected.');
567
        }
568
569
        $this->getLexer()->nextToken();
570
        $args = $this->getCurrentToken()->getId() != ExpressionTokenId::CLOSEPARAM()
571
             ? $this->parseArguments() : [];
572
        if ($this->getCurrentToken()->getId() != ExpressionTokenId::CLOSEPARAM()) {
573
            throw ODataException::createSyntaxError('Close parenthesis expected.');
574
        }
575
576
        $this->getLexer()->nextToken();
577
578
        return $args;
579
    }
580
581
    /**
582
     * Parse arguments of a function-call.
583
     *
584
     * @throws ODataException
585
     * @throws NotImplementedException
586
     * @throws \ReflectionException
587
     * @return array<AbstractExpression>
588
     */
589
    private function parseArguments()
590
    {
591
        $argList = [];
592
        while (true) {
593
            $argList[] = $this->parseExpression();
594
            if ($this->getCurrentToken()->getId() != ExpressionTokenId::COMMA()) {
595
                break;
596
            }
597
598
            $this->getLexer()->nextToken();
599
        }
600
601
        return $argList;
602
    }
603
604
    /**
605
     * Parse primitive type literal.
606
     *
607
     * @param IType $targetType Expected type of the current literal
608
     *
609
     * @throws ODataException
610
     *
611
     * @return ConstantExpression
612
     */
613
    private function parseTypedLiteral(IType $targetType)
614
    {
615
        $literal = $this->getLexer()->getCurrentToken()->Text;
616
        $outVal  = null;
617
        if (!$targetType->validate($literal, $outVal)) {
618
            throw ODataException::createSyntaxError(
619
                Messages::expressionParserUnrecognizedLiteral(
620
                    $targetType->getFullTypeName(),
621
                    $literal,
622
                    $this->getLexer()->getCurrentToken()->Position
623
                )
624
            );
625
        }
626
627
        $result = new ConstantExpression($outVal, $targetType);
628
        $this->getLexer()->nextToken();
629
630
        return $result;
631
    }
632
633
    /**
634
     * Parse null literal.
635
     *
636
     * @throws ODataException
637
     * @return ConstantExpression
638
     */
639
    private function parseNullLiteral()
640
    {
641
        $this->getLexer()->nextToken();
642
643
        return new ConstantExpression(null, new Null1());
644
    }
645
646
    /**
647
     * Check the current token is of a specific kind.
648
     *
649
     * @param ExpressionTokenId $expressionTokenId Token to check
650
     *                                             with current token
651
     *
652
     * @return bool
653
     */
654
    private function tokenIdentifierIs($expressionTokenId)
655
    {
656
        return $this->getCurrentToken()->identifierIs($expressionTokenId);
657
    }
658
659
    /**
660
     * Validate the current token.
661
     *
662
     * @param ExpressionTokenId $expressionTokenId Token to check
663
     *                                             with current token
664
     *
665
     * @throws ODataException
666
     */
667
    private function validateToken(ExpressionTokenId $expressionTokenId)
668
    {
669
        if ($this->getCurrentToken()->getId() != $expressionTokenId) {
670
            throw ODataException::createSyntaxError('Syntax error.');
671
        }
672
    }
673
674
    /**
675
     * Increment recursion count and throw error if beyond limit.
676
     *
677
     *
678
     * @throws ODataException If max recursion limit hits
679
     */
680
    private function recurseEnter()
681
    {
682
        ++$this->recursionDepth;
683
        if ($this->recursionDepth == self::RECURSION_LIMIT) {
684
            throw ODataException::createSyntaxError('Recursion limit reached.');
685
        }
686
    }
687
688
    /**
689
     * Decrement recursion count.
690
     */
691
    private function recurseLeave()
692
    {
693
        --$this->recursionDepth;
694
    }
695
696
    /**
697
     * Generates Comparison Expression.
698
     *
699
     * @param AbstractExpression $left                    The LHS expression
700
     * @param AbstractExpression $right                   The RHS expression
701
     * @param ExpressionToken    $expressionToken         The comparison expression token
702
     * @param bool               $isPHPExpressionProvider
703
     *
704
     * @throws ODataException
705
     * @return FunctionCallExpression|UnaryExpression|RelationalExpression
706
     */
707
    private static function generateComparisonExpression($left, $right, $expressionToken, $isPHPExpressionProvider)
708
    {
709
        FunctionDescription::verifyRelationalOpArguments($expressionToken, $left, $right);
710
711
        //We need special handling for comparison of following types:
712
        //1. EdmString
713
        //2. DateTime
714
        //3. Guid
715
        //4. Binary
716
        //Will make these comparison as function calls, which will
717
        // be converted to language specific function call by expression
718
        // provider
719
        $string = new StringType();
720
        if ($left->typeIs($string) && $right->typeIs($string)) {
721
            $strcmpFunctions = FunctionDescription::stringComparisonFunctions();
722
            $left            = new FunctionCallExpression($strcmpFunctions[0], [$left, $right]);
723
            $right           = new ConstantExpression(0, new Int32());
724
        }
725
726
        $dateTime = new DateTime();
727
        if ($left->typeIs($dateTime) && $right->typeIs($dateTime)) {
728
            $dateTimeCmpFunctions = FunctionDescription::dateTimeComparisonFunctions();
729
            $left                 = new FunctionCallExpression($dateTimeCmpFunctions[0], [$left, $right]);
730
            $right                = new ConstantExpression(0, new Int32());
731
        }
732
733
        $guid = new Guid();
734
        if ($left->typeIs($guid) && $right->typeIs($guid)) {
735
            $guidEqualityFunctions = FunctionDescription::guidEqualityFunctions();
736
            $left                  = new FunctionCallExpression($guidEqualityFunctions[0], [$left, $right]);
737
            $right                 = new ConstantExpression(true, new Boolean());
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

737
            $right                 = new ConstantExpression(/** @scrutinizer ignore-type */ true, new Boolean());
Loading history...
738
        }
739
740
        $binary = new Binary();
741
        if ($left->typeIs($binary) && $right->typeIs($binary)) {
742
            $binaryEqualityFunctions = FunctionDescription::binaryEqualityFunctions();
743
            $left                    = new FunctionCallExpression($binaryEqualityFunctions[0], [$left, $right]);
744
            $right                   = new ConstantExpression(true, new Boolean());
745
        }
746
747
        $null = new Null1();
748
        if ($left->typeIs($null) || $right->typeIs($null)) {
749
            // If the end user is responsible for implementing IExpressionProvider
750
            // then the sub-tree for a nullability check would be:
751
752
            //          RelationalExpression(EQ/NE)
753
            //                    |
754
            //               ------------
755
            //               |           |
756
            //               |           |
757
            //            CustomerID    NULL
758
759
            // Otherwise (In case of default PHPExpressionProvider):
760
761
            //  CustomerID eq null
762
            //  ==================
763
764
            //              FunctionCallExpression(is_null)
765
            //                       |
766
            //                       |- Signature => bool (typeof(CustomerID))
767
            //                       |- args => {CustomerID}
768
769
            //  CustomerID ne null
770
            //  ==================
771
772
            //              UnaryExpression (not)
773
            //                       |
774
            //              FunctionCallExpression(is_null)
775
            //                       |
776
            //                       |- Signature => bool (typeof(CustomerID))
777
            //                       |- args => {CustomerID}
778
779
            if ($isPHPExpressionProvider) {
780
                $arg                       = $left->typeIs($null) ? $right : $left;
781
                $isNullFunctionDescription = new FunctionDescription('is_null', new Boolean(), [$arg->getType()]);
782
                switch ($expressionToken->Text) {
783
                    case ODataConstants::KEYWORD_EQUAL:
784
                        return new FunctionCallExpression($isNullFunctionDescription, [$arg]);
785
                    case ODataConstants::KEYWORD_NOT_EQUAL:
786
                        return new UnaryExpression(
787
                            new FunctionCallExpression($isNullFunctionDescription, [$arg]),
788
                            ExpressionType::NOT_LOGICAL(),
789
                            new Boolean()
790
                        );
791
                }
792
            }
793
        }
794
795
        switch ($expressionToken->Text) {
796
            case ODataConstants::KEYWORD_EQUAL:
797
                return new RelationalExpression(
798
                    $left,
799
                    $right,
800
                    ExpressionType::EQUAL()
801
                );
802
            case ODataConstants::KEYWORD_NOT_EQUAL:
803
                return new RelationalExpression(
804
                    $left,
805
                    $right,
806
                    ExpressionType::NOTEQUAL()
807
                );
808
            case ODataConstants::KEYWORD_GREATERTHAN:
809
                return new RelationalExpression(
810
                    $left,
811
                    $right,
812
                    ExpressionType::GREATERTHAN()
813
                );
814
            case ODataConstants::KEYWORD_GREATERTHAN_OR_EQUAL:
815
                return new RelationalExpression(
816
                    $left,
817
                    $right,
818
                    ExpressionType::GREATERTHAN_OR_EQUAL()
819
                );
820
            case ODataConstants::KEYWORD_LESSTHAN:
821
                return new RelationalExpression(
822
                    $left,
823
                    $right,
824
                    ExpressionType::LESSTHAN()
825
                );
826
            default:
827
                return new RelationalExpression(
828
                    $left,
829
                    $right,
830
                    ExpressionType::LESSTHAN_OR_EQUAL()
831
                );
832
        }
833
    }
834
835
    /**
836
     * Retrieve current lexer instance.
837
     *
838
     * @return ExpressionLexer
839
     */
840
    public function getLexer(): ExpressionLexer
841
    {
842
        return $this->lexer;
843
    }
844
}
845