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