1 <?php
2 3 4 5 6 7 8 9 10
11
12
13 14 15 16 17
18 class NeevoParser {
19
20
21
22 protected $stmt;
23
24
25 protected $clauses = array();
26
27
28 29 30 31
32 public function __construct(NeevoBaseStmt $statement){
33 $this->stmt = $statement;
34 }
35
36
37
38
39
40 41 42 43
44 public function parse(){
45 $where = $order = $group = $limit = $q = '';
46 $source = $this->parseSource();
47
48 if($this->stmt->getConditions())
49 $where = $this->parseWhere();
50 if($this->stmt instanceof NeevoResult && $this->stmt->getGrouping())
51 $group = $this->parseGrouping();
52 if($this->stmt->getSorting())
53 $order = $this->parseSorting();
54
55 $this->clauses = array($source, $where, $group, $order);
56
57 if($this->stmt->getType() === Neevo::STMT_SELECT)
58 $q = $this->parseSelectStmt();
59 elseif($this->stmt->getType() === Neevo::STMT_INSERT)
60 $q = $this->parseInsertStmt();
61 elseif($this->stmt->getType() === Neevo::STMT_UPDATE)
62 $q = $this->parseUpdateStmt();
63 elseif($this->stmt->getType() === Neevo::STMT_DELETE)
64 $q = $this->parseDeleteStmt();
65
66 return $q;
67 }
68
69
70 71 72 73
74 protected function parseSelectStmt(){
75 $cols = $this->stmt->getColumns();
76 list($source, $where, $group, $order) = $this->clauses;
77 foreach($cols as $key => $col){
78 $col = preg_match('~^[\w.]+$~', $col) ? ":$col" : $col;
79 $cols[$key] = $this->tryDelimite($col);
80 }
81 $cols = implode(', ', $cols);
82
83 return $this->applyLimit("SELECT $cols FROM " . $source . $where . $group . $order);
84 }
85
86
87 88 89 90
91 protected function parseInsertStmt(){
92 $cols = array();
93 foreach($this->escapeValue($this->stmt->getValues()) as $col => $value){
94 $cols[] = $this->parseFieldName($col);
95 $values[] = $value;
96 }
97 $data = ' (' . implode(', ', $cols) . ') VALUES (' . implode(', ', $values). ')';
98
99 return 'INSERT INTO ' . $this->clauses[0] . $data;
100 }
101
102
103 104 105 106
107 protected function parseUpdateStmt(){
108 $values = array();
109 list($table, $where) = $this->clauses;
110 foreach($this->escapeValue($this->stmt->getValues()) as $col => $value){
111 $values[] = $this->parseFieldName($col) . ' = ' . $value;
112 }
113 $data = ' SET ' . implode(', ', $values);
114 return 'UPDATE ' . $table . $data . $where;
115 }
116
117
118 119 120 121
122 protected function parseDeleteStmt(){
123 list($table, $where) = $this->clauses;
124 return 'DELETE FROM ' . $table . $where;
125 }
126
127
128 129 130 131
132 protected function parseSource(){
133 if(!($this->stmt instanceof NeevoResult))
134 return $this->escapeValue($this->stmt->getTable(), Neevo::IDENTIFIER);
135
136 if($this->stmt->getTable() !== null){
137 $source = $this->escapeValue($this->stmt->getTable(), Neevo::IDENTIFIER);
138 } else{
139 $subq = $this->stmt->getSource();
140 $alias = $this->escapeValue($subq->getAlias()
141 ? $subq->getAlias() : '_t', Neevo::IDENTIFIER);
142 $source = "($subq) $alias";
143 }
144 $source = $this->tryDelimite($source);
145
146 foreach($this->stmt->getJoins() as $key => $join){
147 list($join_source, $cond, $type) = $join;
148
149 if($join_source instanceof NeevoResult){
150 $join_alias = $this->escapeValue($join_source->getAlias()
151 ? $join_source->getAlias() : '_j' . ($key+1), Neevo::IDENTIFIER);
152
153 $join_source = "($join_source) $join_alias";
154 } elseif($join_source instanceof NeevoLiteral){
155 $join_source = $join_source->value;
156 }
157
158 elseif(is_scalar($join_source)){
159 $join_source = $this->parseFieldName($join_source, true);
160 }
161 $type = strtoupper(substr($type, 5));
162 $type .= ($type === '') ? '' : ' ';
163 $source .= $cond instanceof NeevoLiteral
164 ? " {$type}JOIN $join_source ON $cond->value"
165 : $this->tryDelimite(" {$type}JOIN $join_source ON $cond");
166 }
167 return $source;
168 }
169
170
171 172 173 174
175 protected function parseWhere(){
176 $conds = $this->stmt->getConditions();
177
178
179 unset($conds[count($conds)-1]['glue']);
180
181 $conditions = array();
182 foreach($conds as $cond){
183
184
185 if($cond['simple'] === false){
186 $values = $this->escapeValue($cond['values'], $cond['types']);
187 $s = '(' . $this->applyModifiers($cond['expr'], $cond['modifiers'], $values) . ')';
188 if(isset($cond['glue']))
189 $s .= ' ' . $cond['glue'];
190
191 $conditions[] = $s;
192 continue;
193 }
194
195
196 $field = $this->parseFieldName($cond['field']);
197 $operator = '';
198 $value = $cond['value'];
199 if($value === null){
200 $value = ' IS NULL';
201 } elseif($value === true){
202 $value = '';
203 } elseif($value === false){
204 $value = $field;
205 $field = 'NOT ';
206 } elseif($value instanceof NeevoResult){
207 $operator = ' IN ';
208 $value = $this->escapeValue($value, Neevo::SUBQUERY);
209 } elseif(is_array($value) || $value instanceof Traversable){
210 $value = ' IN ' . $this->escapeValue($value, Neevo::ARR);
211 } elseif($value instanceof NeevoLiteral){
212 $operator = ' = ';
213 $value = $this->escapeValue($value, Neevo::LITERAL);
214 } elseif($value instanceof DateTime){
215 $operator = ' = ';
216 $value = $this->escapeValue($value, Neevo::DATETIME);
217 } else{
218 $operator = ' = ';
219 $value = $this->escapeValue($value);
220 }
221 $s = '(' . $field . $operator . $value . ')';
222 if(isset($cond['glue']))
223 $s .= ' '.$cond['glue'];
224
225 $conditions[] = $s;
226
227 }
228
229 return ' WHERE ' . implode(' ', $conditions);
230 }
231
232
233 234 235 236
237 protected function parseSorting(){
238 $order = array();
239 foreach($this->stmt->getSorting() as $rule){
240 list($field, $type) = $rule;
241 $order[] = $this->tryDelimite($field) . ($type !== null ? ' ' . $type : '');
242 }
243 return ' ORDER BY ' . implode(', ', $order);
244 }
245
246
247 248 249 250
251 protected function parseGrouping(){
252 list($group, $having) = $this->stmt->getGrouping();
253 return $this->tryDelimite(" GROUP BY $group" . ($having !== null ? " HAVING $having" : ''));
254 }
255
256
257 258 259 260 261 262
263 protected function parseFieldName($field, $table = false){
264
265 if(is_array($field))
266 $field = $field[0];
267 if($field instanceof NeevoLiteral)
268 return $field->value;
269
270 $field = trim($field);
271
272 if($field === '*')
273 return $field;
274
275 if(strpos($field, ' '))
276 return $field;
277
278 $field = str_replace(':', '', $field);
279
280 if(strpos($field, '.') !== false || $table === true){
281 $prefix = $this->stmt->getConnection()->getPrefix();
282 $field = $prefix . $field;
283 }
284
285 return $this->stmt->getConnection()->getDriver()->escape($field, Neevo::IDENTIFIER);
286 }
287
288
289 290 291 292 293
294 protected function applyLimit($sql){
295 list($limit, $offset) = $this->stmt->getLimit();
296
297 if((int) $limit > 0){
298 $sql .= ' LIMIT ' . (int) $limit;
299 if((int) $offset > 0)
300 $sql .= ' OFFSET ' . (int) $offset;
301 }
302 return $sql;
303 }
304
305
306
307
308
309 310 311 312 313 314
315 protected function escapeValue($value, $type = null){
316 if(!$type){
317
318 if($value === null)
319 return 'NULL';
320
321
322 elseif(is_array($value) || $value instanceof Traversable){
323 foreach($value as $k => $v)
324 $value[$k] = $this->escapeValue($v);
325 return $value;
326 }
327
328
329 else{
330 if($value instanceof DateTime){
331 return $this->escapeValue($value, Neevo::DATETIME);
332 } elseif($value instanceof NeevoLiteral){
333 return $value->value;
334 } else{
335 return is_numeric($value)
336 ? 1 * $value
337 : $this->stmt->getConnection()->getDriver()->escape($value, Neevo::TEXT);
338 }
339 }
340 }
341
342
343 elseif(is_array($type)){
344 foreach($value as $k => $v)
345 $value[$k] = $this->escapeValue($v, $type[$k]);
346 return $value;
347 }
348
349
350 elseif($type !== null){
351 if($type === Neevo::INT){
352 return (int) $value;
353 } elseif($type === Neevo::FLOAT){
354 return (float) $value;
355 } elseif($type === Neevo::SUBQUERY && $value instanceof NeevoResult){
356 return "($value)";
357 } elseif($type === Neevo::ARR){
358 $arr = $value instanceof Traversable ? iterator_to_array($value) : (array) $value;
359 return '(' . implode(', ', $this->escapeValue($arr)) . ')';
360 } elseif($type === Neevo::LITERAL){
361 return $value instanceof NeevoLiteral ? $value->value : $value;
362 } else{
363 return $this->stmt->getConnection()->getDriver()->escape($value, $type);
364 }
365 }
366 }
367
368
369 370 371 372 373 374 375
376 protected function applyModifiers($expr, array $modifiers, array $values){
377 foreach($modifiers as &$mod){
378 $mod = "/$mod/";
379 }
380 $expr = $this->tryDelimite($expr);
381 return preg_replace($modifiers, $values, $expr, 1);
382 }
383
384
385 386 387 388 389
390 protected function tryDelimite($expr){
391 if($expr instanceof NeevoLiteral)
392 return $expr->value;
393 return preg_replace_callback('~:([a-z_\*][a-z0-9._\*]*)~', array($this, 'parseFieldName'), $expr);
394 }
395
396
397 }
398