1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Bdf\Prime\Query\Compiler\AliasResolver; |
4
|
|
|
|
5
|
|
|
use Bdf\Prime\Exception\QueryBuildingException; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Compiler for relations and attributes expressions |
9
|
|
|
* |
10
|
|
|
* Syntax : |
11
|
|
|
* ========= |
12
|
|
|
* $NAME => ($CHR | _ | #)+ |
13
|
|
|
* $ATTR => >$NAME |
14
|
|
|
* $ALIAS => $$NAME |
15
|
|
|
* $DYN => $NAME(.$NAME)* |
16
|
|
|
* $STA => "$DYN" |
17
|
|
|
* $EXPR => |
18
|
|
|
* ($ALIAS | $STA)($DYN | $ATTR) |
19
|
|
|
* $DYN($ATTR)? |
20
|
|
|
* $ATTR |
21
|
|
|
* |
22
|
|
|
* Definitions : |
23
|
|
|
* ============== |
24
|
|
|
* $NAME : |
25
|
|
|
* The part name. |
26
|
|
|
* Can be relation or attribute. |
27
|
|
|
* |
28
|
|
|
* $ATTR : |
29
|
|
|
* Force specify that is an attribute. |
30
|
|
|
* Should be the last part of the expression. |
31
|
|
|
* Should not be analyzed. |
32
|
|
|
* |
33
|
|
|
* $ALIAS : |
34
|
|
|
* A registered alias. |
35
|
|
|
* Should be the first part of the expression. |
36
|
|
|
* The alias represents a (possibly) deep relation path. |
37
|
|
|
* The alias should not be analyzed, only check the metadata table. |
38
|
|
|
* Cannot represents an attribute, so an alias should be followed by one of $DYN or $ATTR. |
39
|
|
|
* |
40
|
|
|
* $DYN : |
41
|
|
|
* The dynamic expression. |
42
|
|
|
* Always analyzed, can be attribute, or relation path. |
43
|
|
|
* This expression is sufficient for most of request. |
44
|
|
|
* This is the only expression that can be preceded AND flowed by tokens |
45
|
|
|
* |
46
|
|
|
* $STA : |
47
|
|
|
* The "static" expression. |
48
|
|
|
* A static expression is ALWAYS related to an alias. |
49
|
|
|
* So, one $STA is related to one AND ONLY one $ALIAS. |
50
|
|
|
* Also has save constrains than $ALIAS (should be the first token, cannot respresents an attribute...). |
51
|
|
|
* |
52
|
|
|
* $EXPR : |
53
|
|
|
* The complete expression. |
54
|
|
|
* Should represents the complete relations path plus the attribute. |
55
|
|
|
* This is a database value. |
56
|
|
|
* |
57
|
|
|
* Example : |
58
|
|
|
* ========== |
59
|
|
|
* |
60
|
|
|
* All those examples do the same |
61
|
|
|
* |
62
|
|
|
* user.location.address.name : |
63
|
|
|
* $DYN = user location address name |
64
|
|
|
* Will : |
65
|
|
|
* - resolve user, and set as t1 |
66
|
|
|
* - resolve user.location and set as t2 |
67
|
|
|
* - Find that address.name is an attribute of user.location |
68
|
|
|
* Conclusion : |
69
|
|
|
* The simple way to do that. But resolve too many times. |
70
|
|
|
* So for multiple queries on same attribute path, do useless resolutions |
71
|
|
|
* |
72
|
|
|
* "user.location"address.name : |
73
|
|
|
* $STA = user.location |
74
|
|
|
* $ATTR = address name |
75
|
|
|
* Will : |
76
|
|
|
* - Check in the path table user.location |
77
|
|
|
* - If exists, get the metadata |
78
|
|
|
* - If not, resolve as $DYN, and store the path |
79
|
|
|
* - Find that address.name is an attribute of user.location |
80
|
|
|
* Conclusion : |
81
|
|
|
* If many filters applies on user.location, the metadata and path are already loaded |
82
|
|
|
* So do not add overhead for useless resolutions |
83
|
|
|
* But use the $DYN algo for find the attribute, that can decreases performances |
84
|
|
|
* |
85
|
|
|
* "user.location">address.name : |
86
|
|
|
* $STA = user.location |
87
|
|
|
* $ATTR = address.name |
88
|
|
|
* Will : |
89
|
|
|
* - Check user.location in the path table |
90
|
|
|
* - use address.name as attribute |
91
|
|
|
* Conclusion : |
92
|
|
|
* The best usage. Remove overhead for resolve relations path AND for resolving attribute name |
93
|
|
|
* |
94
|
|
|
* $t2>address.name |
95
|
|
|
* $ALIAS = t2 |
96
|
|
|
* $ATTR = address.name |
97
|
|
|
* Will : |
98
|
|
|
* - Get t2 alias |
99
|
|
|
* - use address.name as attribute |
100
|
|
|
* Conclusion : |
101
|
|
|
* Same as "user.location">address.name, but the user should care about the alias. |
102
|
|
|
* The best for internal usages |
103
|
|
|
* |
104
|
|
|
* $t1.location>address.name : |
105
|
|
|
* $ALIAS = t1 |
106
|
|
|
* $DYN = location |
107
|
|
|
* $ATTR = address.name |
108
|
|
|
* Will : |
109
|
|
|
* - Get the t1 alias |
110
|
|
|
* - Resolve location into t1 (via $DYN) |
111
|
|
|
* - use address.name as attribute |
112
|
|
|
* Conclusion ; |
113
|
|
|
* The worst way, but can be usefull internally if (and only if) |
114
|
|
|
* - The context is an alias |
115
|
|
|
* - Cannot ensure that the "right part" is an attribute |
116
|
|
|
* - The real attribute is known |
117
|
|
|
*/ |
118
|
|
|
class ExpressionCompiler |
119
|
|
|
{ |
120
|
|
|
public const DYN_SEPARATOR = '.'; |
121
|
|
|
public const ATTR_IDENTIFIER = '>'; |
122
|
|
|
public const STA_IDENTIFIER = '"'; |
123
|
|
|
public const ALIAS_IDENTIFIER = '$'; |
124
|
|
|
|
125
|
|
|
public const RESERVED = [ |
126
|
|
|
self::DYN_SEPARATOR => true, |
127
|
|
|
self::ATTR_IDENTIFIER => true, |
128
|
|
|
self::STA_IDENTIFIER => true, |
129
|
|
|
self::ALIAS_IDENTIFIER => true, |
130
|
|
|
]; |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @var static |
134
|
|
|
*/ |
135
|
|
|
private static $instance; |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Compile the expression to expression tokens |
139
|
|
|
* |
140
|
|
|
* @param string $expression |
141
|
|
|
* |
142
|
|
|
* @return ExpressionToken[] |
143
|
|
|
*/ |
144
|
145 |
|
public function compile($expression) |
145
|
|
|
{ |
146
|
145 |
|
$len = strlen($expression); |
147
|
145 |
|
$pos = 0; |
148
|
145 |
|
$tokens = []; |
149
|
|
|
|
150
|
145 |
|
while ($pos < $len) { |
151
|
145 |
|
switch ($expression[$pos]) { |
152
|
145 |
|
case self::ALIAS_IDENTIFIER: |
153
|
71 |
|
$tokens[] = $this->compileAlias($expression, $pos, $len); |
154
|
71 |
|
break; |
155
|
145 |
|
case self::STA_IDENTIFIER: |
156
|
5 |
|
$tokens[] = $this->compileStatic($expression, $pos, $len); |
157
|
5 |
|
break; |
158
|
145 |
|
case self::ATTR_IDENTIFIER: |
159
|
106 |
|
$tokens[] = $this->compileAttribute($expression, $pos, $len); |
160
|
106 |
|
break; |
161
|
|
|
default: |
162
|
141 |
|
$tokens[] = $this->compileDynamic($expression, $pos, $len); |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
|
166
|
145 |
|
return $tokens; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Compile $ALIAS. |
171
|
|
|
* |
172
|
|
|
* $t1 => new ExpressionToken(TYPE_ALIAS, 't1') |
173
|
|
|
* |
174
|
|
|
* @param string $expression |
175
|
|
|
* @param int $pos |
176
|
|
|
* @param int $len |
177
|
|
|
* |
178
|
|
|
* @return ExpressionToken |
179
|
|
|
*/ |
180
|
71 |
|
protected function compileAlias($expression, &$pos, $len) |
181
|
|
|
{ |
182
|
71 |
|
if ($pos !== 0) { |
183
|
|
|
throw new QueryBuildingException('Alias should be the first expression token'); |
184
|
|
|
} |
185
|
|
|
|
186
|
71 |
|
++$pos; |
187
|
|
|
|
188
|
71 |
|
return new ExpressionToken( |
189
|
|
|
ExpressionToken::TYPE_ALIAS, |
190
|
71 |
|
$this->compileName($expression, $pos, $len) |
191
|
|
|
); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Compile $ATTR |
196
|
|
|
* |
197
|
|
|
* xxx>my.attribute => new ExpressionToken(TYPE_ATTR, 'my.attribute') |
198
|
|
|
* |
199
|
|
|
* @param string $expression |
200
|
|
|
* @param int $pos |
201
|
|
|
* @param int $len |
202
|
|
|
* |
203
|
|
|
* @return ExpressionToken |
204
|
|
|
*/ |
205
|
106 |
|
protected function compileAttribute($expression, &$pos, $len) |
206
|
|
|
{ |
207
|
106 |
|
$value = substr($expression, $pos + 1); |
208
|
106 |
|
$pos = $len; |
209
|
|
|
|
210
|
106 |
|
return new ExpressionToken( |
211
|
|
|
ExpressionToken::TYPE_ATTR, |
212
|
|
|
$value |
213
|
|
|
); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Compile a $STA |
218
|
|
|
* |
219
|
|
|
* "my.static.exp" => new ExpressionToken(TYPE_STA, 'my.static.exp') |
220
|
|
|
* |
221
|
|
|
* @param string $expression |
222
|
|
|
* @param int $pos |
223
|
|
|
* @param int $len |
224
|
|
|
* |
225
|
|
|
* @return ExpressionToken |
226
|
|
|
*/ |
227
|
5 |
|
protected function compileStatic($expression, &$pos, $len) |
|
|
|
|
228
|
|
|
{ |
229
|
5 |
|
if ($pos !== 0) { |
230
|
|
|
throw new QueryBuildingException('Static expression should be the first expression token'); |
231
|
|
|
} |
232
|
|
|
|
233
|
5 |
|
++$pos; |
234
|
5 |
|
$end = strpos($expression, self::STA_IDENTIFIER, $pos); |
235
|
|
|
|
236
|
5 |
|
$value = substr($expression, $pos, $end - $pos); |
237
|
|
|
|
238
|
5 |
|
$pos = $end + 1; |
239
|
|
|
|
240
|
5 |
|
return new ExpressionToken( |
241
|
|
|
ExpressionToken::TYPE_STA, |
242
|
|
|
$value |
243
|
|
|
); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Compile an $DYN |
248
|
|
|
* |
249
|
|
|
* my.super.expr => new ExpressionToken(TYPE_DYN, ['my', 'super', 'expr']) |
250
|
|
|
* |
251
|
|
|
* @param string $expression |
252
|
|
|
* @param int $pos |
253
|
|
|
* @param int $len |
254
|
|
|
* |
255
|
|
|
* @return ExpressionToken |
256
|
|
|
*/ |
257
|
141 |
|
protected function compileDynamic($expression, &$pos, $len) |
258
|
|
|
{ |
259
|
141 |
|
if ($expression[$pos] === self::DYN_SEPARATOR) { |
260
|
52 |
|
++$pos; |
261
|
|
|
} |
262
|
|
|
|
263
|
141 |
|
$names = []; |
264
|
|
|
|
265
|
141 |
|
for (;;) { |
266
|
141 |
|
$names[] = $this->compileName($expression, $pos, $len); |
267
|
|
|
|
268
|
|
|
if ( |
269
|
|
|
$pos >= $len |
270
|
141 |
|
|| $expression[$pos] !== self::DYN_SEPARATOR |
271
|
|
|
) { |
272
|
141 |
|
break; |
273
|
|
|
} |
274
|
|
|
|
275
|
141 |
|
++$pos; |
276
|
|
|
} |
277
|
|
|
|
278
|
141 |
|
return new ExpressionToken( |
279
|
|
|
ExpressionToken::TYPE_DYN, |
280
|
|
|
$names |
281
|
|
|
); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Compile a $NAME. |
286
|
|
|
* |
287
|
|
|
* The compilation stops when encounter a reserved character, or the end of the expression |
288
|
|
|
* |
289
|
|
|
* @param string $expression |
290
|
|
|
* @param int $pos |
291
|
|
|
* @param int $len |
292
|
|
|
* |
293
|
|
|
* @return string |
294
|
|
|
*/ |
295
|
142 |
|
protected function compileName($expression, &$pos, $len) |
296
|
|
|
{ |
297
|
142 |
|
$name = ''; |
298
|
|
|
|
299
|
142 |
|
for (;$pos < $len; ++$pos) { |
300
|
142 |
|
$chr = $expression[$pos]; |
301
|
|
|
|
302
|
142 |
|
if (array_key_exists($chr, self::RESERVED)) { |
303
|
141 |
|
break; |
304
|
|
|
} |
305
|
|
|
|
306
|
142 |
|
$name .= $chr; |
307
|
|
|
} |
308
|
|
|
|
309
|
142 |
|
return $name; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Get the compiler instance |
314
|
|
|
* |
315
|
|
|
* @return static |
316
|
|
|
*/ |
317
|
136 |
|
public static function instance() |
318
|
|
|
{ |
319
|
136 |
|
if (static::$instance === null) { |
|
|
|
|
320
|
1 |
|
static::$instance = new static(); |
321
|
|
|
} |
322
|
|
|
|
323
|
136 |
|
return static::$instance; |
324
|
|
|
} |
325
|
|
|
} |
326
|
|
|
|
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.