|
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.