Skip to content

Commit 8d3d69b

Browse files
committed
Implement parsing for Delete Statement
Signed-off-by: Deven Bansod <[email protected]>
1 parent 5988855 commit 8d3d69b

File tree

2 files changed

+258
-16
lines changed

2 files changed

+258
-16
lines changed

src/Components/Limit.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public static function parse(Parser $parser, TokensList $list, array $options =
127127
*/
128128
public static function build($component, array $options = array())
129129
{
130-
if (empty($component->offset)) {
130+
if (count($component->offset) === 0) {
131131
return (string) $component->rowCount;
132132
} else {
133133
return $component->offset . ', ' . $component->rowCount;

src/Statements/DeleteStatement.php

Lines changed: 257 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99
namespace SqlParser\Statements;
1010

1111
use SqlParser\Statement;
12+
use SqlParser\Parser;
13+
use SqlParser\Token;
14+
use SqlParser\TokensList;
1215
use SqlParser\Components\ArrayObj;
1316
use SqlParser\Components\Expression;
17+
use SqlParser\Components\ExpressionArray;
1418
use SqlParser\Components\Limit;
1519
use SqlParser\Components\OrderKeyword;
1620
use SqlParser\Components\Condition;
21+
use SqlParser\Components\OptionsArray;
1722

1823
/**
1924
* `DELETE` statement.
@@ -24,6 +29,21 @@
2429
* [ORDER BY ...]
2530
* [LIMIT row_count]
2631
*
32+
* Multi-table syntax
33+
*
34+
* DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
35+
* tbl_name[.*] [, tbl_name[.*]] ...
36+
* FROM table_references
37+
* [WHERE where_condition]
38+
*
39+
* OR
40+
*
41+
* DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
42+
* FROM tbl_name[.*] [, tbl_name[.*]] ...
43+
* USING table_references
44+
* [WHERE where_condition]
45+
*
46+
*
2747
* @category Statements
2848
* @package SqlParser
2949
* @subpackage Statements
@@ -45,29 +65,25 @@ class DeleteStatement extends Statement
4565
);
4666

4767
/**
48-
* The clauses of this statement, in order.
68+
* Table(s) used as sources for this statement.
4969
*
50-
* @see Statement::$CLAUSES
70+
* @var Expression[]
71+
*/
72+
public $from;
73+
74+
/**
75+
* Tables used as sources for this statement
5176
*
52-
* @var array
77+
* @var Expression[]
5378
*/
54-
public static $CLAUSES = array(
55-
'DELETE' => array('DELETE', 2),
56-
// Used for options.
57-
'_OPTIONS' => array('_OPTIONS', 1),
58-
'FROM' => array('FROM', 3),
59-
'PARTITION' => array('PARTITION', 3),
60-
'WHERE' => array('WHERE', 3),
61-
'ORDER BY' => array('ORDER BY', 3),
62-
'LIMIT' => array('LIMIT', 3),
63-
);
79+
public $using;
6480

6581
/**
66-
* Tables used as sources for this statement.
82+
* Columns used in this statement
6783
*
6884
* @var Expression[]
6985
*/
70-
public $from;
86+
public $columns;
7187

7288
/**
7389
* Partitions used as source for this statement.
@@ -96,4 +112,230 @@ class DeleteStatement extends Statement
96112
* @var Limit
97113
*/
98114
public $limit;
115+
116+
117+
/**
118+
* @return string
119+
*/
120+
public function build()
121+
{
122+
$ret = 'DELETE ' . OptionsArray::build($this->options);
123+
124+
if ($this->columns != NULL && count($this->columns) > 0) {
125+
$ret .= ' ' . ExpressionArray::build($this->columns);
126+
}
127+
if ($this->from != NULL && count($this->from) > 0) {
128+
$ret .= ' FROM ' . ExpressionArray::build($this->from);
129+
}
130+
if ($this->using != NULL && count($this->using) > 0) {
131+
$ret .= ' USING ' . ExpressionArray::build($this->using);
132+
}
133+
if ($this->where != NULL && count($this->where) > 0) {
134+
$ret .= ' WHERE ' . Condition::build($this->where);
135+
}
136+
if ($this->order != NULL && count($this->order) > 0) {
137+
$ret .= ' ORDER BY ' . ExpressionArray::build($this->order);
138+
}
139+
if ($this->limit != NULL && count($this->limit) > 0) {
140+
$ret .= ' LIMIT ' . Limit::build($this->limit);
141+
}
142+
143+
return $ret;
144+
145+
}
146+
147+
148+
/**
149+
* @param Parser $parser The instance that requests parsing.
150+
* @param TokensList $list The list of tokens to be parsed.
151+
*
152+
* @return void
153+
*/
154+
public function parse(Parser $parser, TokensList $list)
155+
{
156+
++$list->idx; // Skipping `DELETE`.
157+
158+
// parse any options if provided
159+
$this->options = OptionsArray::parse(
160+
$parser,
161+
$list,
162+
static::$OPTIONS
163+
);
164+
++$list->idx;
165+
166+
/**
167+
* The state of the parser.
168+
*
169+
* Below are the states of the parser.
170+
*
171+
* 0 ---------------------------------[ FROM ]----------------------------------> 2
172+
* 0 ------------------------------[ table[.*] ]--------------------------------> 1
173+
* 1 ---------------------------------[ FROM ]----------------------------------> 2
174+
* 2 --------------------------------[ USING ]----------------------------------> 3
175+
* 2 --------------------------------[ WHERE ]----------------------------------> 4
176+
* 2 --------------------------------[ ORDER ]----------------------------------> 5
177+
* 2 --------------------------------[ LIMIT ]----------------------------------> 6
178+
*
179+
* @var int $state
180+
*/
181+
$state = 0;
182+
183+
for (; $list->idx < $list->count; ++$list->idx) {
184+
/**
185+
* Token parsed at this moment.
186+
*
187+
* @var Token $token
188+
*/
189+
$token = $list->tokens[$list->idx];
190+
191+
// End of statement.
192+
if ($token->type === Token::TYPE_DELIMITER) {
193+
break;
194+
}
195+
196+
// Skipping whitespaces and comments.
197+
if (($token->type === Token::TYPE_WHITESPACE) || ($token->type === Token::TYPE_COMMENT)) {
198+
continue;
199+
}
200+
201+
if ($state === 0) {
202+
if ($token->type === Token::TYPE_KEYWORD
203+
&& $token->value !== 'FROM'
204+
) {
205+
$parser->error(__('Unexpected keyword.'), $token);
206+
break;
207+
} elseif ($token->type === Token::TYPE_KEYWORD
208+
&& $token->value === 'FROM'
209+
) {
210+
++$list->idx; // Skip 'FROM'
211+
$this->from = ExpressionArray::parse($parser, $list);
212+
$state = 2;
213+
} else {
214+
$this->columns = ExpressionArray::parse($parser, $list);
215+
$state = 1;
216+
}
217+
} elseif ($state === 1) {
218+
if ($token->type === Token::TYPE_KEYWORD
219+
&& $token->value !== 'FROM'
220+
) {
221+
$parser->error(__('Unexpected keyword.'), $token);
222+
break;
223+
} elseif ($token->type === Token::TYPE_KEYWORD
224+
&& $token->value === 'FROM'
225+
) {
226+
++$list->idx; // Skip 'FROM'
227+
$this->from = ExpressionArray::parse($parser, $list);
228+
$state = 2;
229+
} elseif ($token->type === Token::TYPE_KEYWORD) {
230+
$parser->error(__('Unexpected keyword.'), $token);
231+
break;
232+
} else {
233+
$parser->error(__('Unexpected token.'), $token);
234+
break;
235+
}
236+
} elseif ($state === 2) {
237+
if ($token->type === Token::TYPE_KEYWORD
238+
&& $token->value === 'USING'
239+
) {
240+
++$list->idx; // Skip 'FROM'
241+
$this->using = ExpressionArray::parse($parser, $list);
242+
$state = 3;
243+
} elseif ($token->type === Token::TYPE_KEYWORD
244+
&& $token->value === 'WHERE'
245+
) {
246+
++$list->idx; // Skip 'WHERE'
247+
$this->where = Condition::parse($parser, $list);
248+
$state = 4;
249+
} elseif ($token->type === Token::TYPE_KEYWORD
250+
&& $token->value === 'ORDER BY'
251+
) {
252+
++$list->idx; // Skip 'ORDER BY'
253+
$this->order = OrderKeyword::parse($parser, $list);
254+
$state = 5;
255+
} elseif ($token->type === Token::TYPE_KEYWORD
256+
&& $token->value === 'LIMIT'
257+
) {
258+
++$list->idx; // Skip 'LIMIT'
259+
$this->limit = Limit::parse($parser, $list);
260+
$state = 6;
261+
} elseif ($token->type === Token::TYPE_KEYWORD) {
262+
$parser->error(__('Unexpected keyword.'), $token);
263+
break;
264+
} else {
265+
$parser->error(__('Unexpected token.'), $token);
266+
break;
267+
}
268+
} elseif ($state === 3) {
269+
if ($token->type === Token::TYPE_KEYWORD
270+
&& $token->value === 'WHERE'
271+
) {
272+
++$list->idx; // Skip 'WHERE'
273+
$this->where = Condition::parse($parser, $list);
274+
$state = 4;
275+
} elseif ($token->type === Token::TYPE_KEYWORD
276+
&& $token->value === 'ORDER BY'
277+
) {
278+
++$list->idx; // Skip 'ORDER BY'
279+
$this->order = OrderKeyword::parse($parser, $list);
280+
$state = 5;
281+
} elseif ($token->type === Token::TYPE_KEYWORD
282+
&& $token->value === 'LIMIT'
283+
) {
284+
++$list->idx; // Skip 'LIMIT'
285+
$this->limit = Limit::parse($parser, $list);
286+
$state = 6;
287+
} elseif ($token->type === Token::TYPE_KEYWORD) {
288+
$parser->error(__('Unexpected keyword.'), $token);
289+
break;
290+
} else {
291+
$parser->error(__('Unexpected token.'), $token);
292+
break;
293+
}
294+
} elseif ($state === 4) {
295+
if ($token->type === Token::TYPE_KEYWORD
296+
&& $token->value === 'ORDER BY'
297+
) {
298+
++$list->idx; // Skip 'ORDER BY'
299+
$this->order = OrderKeyword::parse($parser, $list);
300+
$state = 5;
301+
} elseif ($token->type === Token::TYPE_KEYWORD
302+
&& $token->value === 'LIMIT'
303+
) {
304+
++$list->idx; // Skip 'LIMIT'
305+
$this->limit = Limit::parse($parser, $list);
306+
$state = 6;
307+
} elseif ($token->type === Token::TYPE_KEYWORD) {
308+
$parser->error(__('Unexpected keyword.'), $token);
309+
break;
310+
} else {
311+
$parser->error(__('Unexpected token.'), $token);
312+
break;
313+
}
314+
} elseif ($state === 5) {
315+
if ($token->type === Token::TYPE_KEYWORD
316+
&& $token->value === 'LIMIT'
317+
) {
318+
++$list->idx; // Skip 'LIMIT'
319+
$this->limit = Limit::parse($parser, $list);
320+
$state = 6;
321+
} elseif ($token->type === Token::TYPE_KEYWORD) {
322+
$parser->error(__('Unexpected keyword.'), $token);
323+
break;
324+
} else {
325+
$parser->error(__('Unexpected token.'), $token);
326+
break;
327+
}
328+
}
329+
}
330+
331+
if ($state >= 2) {
332+
foreach ($this->from as $from_expr) {
333+
$from_expr->database = $from_expr->table;
334+
$from_expr->table = $from_expr->column;
335+
$from_expr->column = null;
336+
}
337+
}
338+
339+
--$list->idx;
340+
}
99341
}

0 commit comments

Comments
 (0)