phpmyadmin /
sql-parser
| 1 | <?php |
||||
| 2 | |||||
| 3 | declare(strict_types=1); |
||||
| 4 | |||||
| 5 | namespace PhpMyAdmin\SqlParser\Statements; |
||||
| 6 | |||||
| 7 | use PhpMyAdmin\SqlParser\Components\ArrayObj; |
||||
| 8 | use PhpMyAdmin\SqlParser\Components\Condition; |
||||
| 9 | use PhpMyAdmin\SqlParser\Components\Expression; |
||||
| 10 | use PhpMyAdmin\SqlParser\Components\JoinKeyword; |
||||
| 11 | use PhpMyAdmin\SqlParser\Components\Limit; |
||||
| 12 | use PhpMyAdmin\SqlParser\Components\OrderKeyword; |
||||
| 13 | use PhpMyAdmin\SqlParser\Parser; |
||||
| 14 | use PhpMyAdmin\SqlParser\Parsers\Conditions; |
||||
| 15 | use PhpMyAdmin\SqlParser\Parsers\ExpressionArray; |
||||
| 16 | use PhpMyAdmin\SqlParser\Parsers\Expressions; |
||||
| 17 | use PhpMyAdmin\SqlParser\Parsers\JoinKeywords; |
||||
| 18 | use PhpMyAdmin\SqlParser\Parsers\Limits; |
||||
| 19 | use PhpMyAdmin\SqlParser\Parsers\OptionsArrays; |
||||
| 20 | use PhpMyAdmin\SqlParser\Parsers\OrderKeywords; |
||||
| 21 | use PhpMyAdmin\SqlParser\Statement; |
||||
| 22 | use PhpMyAdmin\SqlParser\TokensList; |
||||
| 23 | use PhpMyAdmin\SqlParser\TokenType; |
||||
| 24 | |||||
| 25 | use function stripos; |
||||
| 26 | use function strlen; |
||||
| 27 | |||||
| 28 | /** |
||||
| 29 | * `DELETE` statement. |
||||
| 30 | * |
||||
| 31 | * DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name |
||||
| 32 | * [PARTITION (partition_name,...)] |
||||
| 33 | * [WHERE where_condition] |
||||
| 34 | * [ORDER BY ...] |
||||
| 35 | * [LIMIT row_count] |
||||
| 36 | * |
||||
| 37 | * Multi-table syntax |
||||
| 38 | * |
||||
| 39 | * DELETE [LOW_PRIORITY] [QUICK] [IGNORE] |
||||
| 40 | * tbl_name[.*] [, tbl_name[.*]] ... |
||||
| 41 | * FROM table_references |
||||
| 42 | * [WHERE where_condition] |
||||
| 43 | * |
||||
| 44 | * OR |
||||
| 45 | * |
||||
| 46 | * DELETE [LOW_PRIORITY] [QUICK] [IGNORE] |
||||
| 47 | * FROM tbl_name[.*] [, tbl_name[.*]] ... |
||||
| 48 | * USING table_references |
||||
| 49 | * [WHERE where_condition] |
||||
| 50 | */ |
||||
| 51 | class DeleteStatement extends Statement |
||||
| 52 | { |
||||
| 53 | /** |
||||
| 54 | * Options for `DELETE` statements. |
||||
| 55 | * |
||||
| 56 | * @var array<string, int|array<int, int|string>> |
||||
| 57 | * @psalm-var array<string, (positive-int|array{positive-int, ('var'|'var='|'expr'|'expr=')})> |
||||
| 58 | */ |
||||
| 59 | public static array $statementOptions = [ |
||||
| 60 | 'LOW_PRIORITY' => 1, |
||||
| 61 | 'QUICK' => 2, |
||||
| 62 | 'IGNORE' => 3, |
||||
| 63 | ]; |
||||
| 64 | |||||
| 65 | /** |
||||
| 66 | * The clauses of this statement, in order. |
||||
| 67 | * |
||||
| 68 | * @see Statement::$clauses |
||||
| 69 | * |
||||
| 70 | * @var array<string, array{non-empty-string, int-mask-of<self::ADD_*>}> |
||||
|
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||||
| 71 | */ |
||||
| 72 | public static array $clauses = [ |
||||
| 73 | 'DELETE' => [ |
||||
| 74 | 'DELETE', |
||||
| 75 | Statement::ADD_KEYWORD, |
||||
| 76 | ], |
||||
| 77 | // Used for options. |
||||
| 78 | '_OPTIONS' => [ |
||||
| 79 | '_OPTIONS', |
||||
| 80 | Statement::ADD_CLAUSE, |
||||
| 81 | ], |
||||
| 82 | 'FROM' => [ |
||||
| 83 | 'FROM', |
||||
| 84 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
| 85 | ], |
||||
| 86 | 'PARTITION' => [ |
||||
| 87 | 'PARTITION', |
||||
| 88 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
| 89 | ], |
||||
| 90 | 'USING' => [ |
||||
| 91 | 'USING', |
||||
| 92 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
| 93 | ], |
||||
| 94 | 'WHERE' => [ |
||||
| 95 | 'WHERE', |
||||
| 96 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
| 97 | ], |
||||
| 98 | 'ORDER BY' => [ |
||||
| 99 | 'ORDER BY', |
||||
| 100 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
| 101 | ], |
||||
| 102 | 'LIMIT' => [ |
||||
| 103 | 'LIMIT', |
||||
| 104 | Statement::ADD_CLAUSE | Statement::ADD_KEYWORD, |
||||
| 105 | ], |
||||
| 106 | ]; |
||||
| 107 | |||||
| 108 | /** |
||||
| 109 | * Table(s) used as sources for this statement. |
||||
| 110 | * |
||||
| 111 | * @var Expression[]|null |
||||
| 112 | */ |
||||
| 113 | public array|null $from = null; |
||||
| 114 | |||||
| 115 | /** |
||||
| 116 | * Joins. |
||||
| 117 | * |
||||
| 118 | * @var JoinKeyword[]|null |
||||
| 119 | */ |
||||
| 120 | public array|null $join = null; |
||||
| 121 | |||||
| 122 | /** |
||||
| 123 | * Tables used as sources for this statement. |
||||
| 124 | * |
||||
| 125 | * @var Expression[]|null |
||||
| 126 | */ |
||||
| 127 | public array|null $using = null; |
||||
| 128 | |||||
| 129 | /** |
||||
| 130 | * Columns used in this statement. |
||||
| 131 | * |
||||
| 132 | * @var Expression[]|null |
||||
| 133 | */ |
||||
| 134 | public array|null $columns = null; |
||||
| 135 | |||||
| 136 | /** |
||||
| 137 | * Partitions used as source for this statement. |
||||
| 138 | */ |
||||
| 139 | public ArrayObj|null $partition = null; |
||||
| 140 | |||||
| 141 | /** |
||||
| 142 | * Conditions used for filtering each row of the result set. |
||||
| 143 | * |
||||
| 144 | * @var Condition[]|null |
||||
| 145 | */ |
||||
| 146 | public array|null $where = null; |
||||
| 147 | |||||
| 148 | /** |
||||
| 149 | * Specifies the order of the rows in the result set. |
||||
| 150 | * |
||||
| 151 | * @var OrderKeyword[]|null |
||||
| 152 | */ |
||||
| 153 | public array|null $order = null; |
||||
| 154 | |||||
| 155 | /** |
||||
| 156 | * Conditions used for limiting the size of the result set. |
||||
| 157 | */ |
||||
| 158 | public Limit|null $limit = null; |
||||
| 159 | |||||
| 160 | 4 | public function build(): string |
|||
| 161 | { |
||||
| 162 | 4 | $ret = 'DELETE ' . $this->options->build(); |
|||
|
0 ignored issues
–
show
The method
build() does not exist on null.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. Loading history...
|
|||||
| 163 | |||||
| 164 | 4 | if ($this->columns !== null && $this->columns !== []) { |
|||
| 165 | 2 | $ret .= ' ' . Expressions::buildAll($this->columns); |
|||
| 166 | } |
||||
| 167 | |||||
| 168 | 4 | if ($this->from !== null && $this->from !== []) { |
|||
| 169 | 4 | $ret .= ' FROM ' . Expressions::buildAll($this->from); |
|||
| 170 | } |
||||
| 171 | |||||
| 172 | 4 | if ($this->join !== null && $this->join !== []) { |
|||
| 173 | 2 | $ret .= ' ' . JoinKeywords::buildAll($this->join); |
|||
| 174 | } |
||||
| 175 | |||||
| 176 | 4 | if ($this->using !== null && $this->using !== []) { |
|||
| 177 | 2 | $ret .= ' USING ' . Expressions::buildAll($this->using); |
|||
| 178 | } |
||||
| 179 | |||||
| 180 | 4 | if ($this->where !== null && $this->where !== []) { |
|||
| 181 | 4 | $ret .= ' WHERE ' . Conditions::buildAll($this->where); |
|||
| 182 | } |
||||
| 183 | |||||
| 184 | 4 | if ($this->order !== null && $this->order !== []) { |
|||
| 185 | 2 | $ret .= ' ORDER BY ' . OrderKeywords::buildAll($this->order); |
|||
| 186 | } |
||||
| 187 | |||||
| 188 | 4 | if ($this->limit !== null && strlen((string) $this->limit) > 0) { |
|||
| 189 | 2 | $ret .= ' LIMIT ' . $this->limit->build(); |
|||
| 190 | } |
||||
| 191 | |||||
| 192 | 4 | return $ret; |
|||
| 193 | } |
||||
| 194 | |||||
| 195 | /** |
||||
| 196 | * @param Parser $parser the instance that requests parsing |
||||
| 197 | * @param TokensList $list the list of tokens to be parsed |
||||
| 198 | */ |
||||
| 199 | 62 | public function parse(Parser $parser, TokensList $list): void |
|||
| 200 | { |
||||
| 201 | 62 | ++$list->idx; // Skipping `DELETE`. |
|||
| 202 | |||||
| 203 | // parse any options if provided |
||||
| 204 | 62 | $this->options = OptionsArrays::parse($parser, $list, static::$statementOptions); |
|||
| 205 | 62 | ++$list->idx; |
|||
| 206 | |||||
| 207 | /** |
||||
| 208 | * The state of the parser. |
||||
| 209 | * |
||||
| 210 | * Below are the states of the parser. |
||||
| 211 | * |
||||
| 212 | * 0 ---------------------------------[ FROM ]----------------------------------> 2 |
||||
| 213 | * 0 ------------------------------[ table[.*] ]--------------------------------> 1 |
||||
| 214 | * 1 ---------------------------------[ FROM ]----------------------------------> 2 |
||||
| 215 | * 2 --------------------------------[ USING ]----------------------------------> 3 |
||||
| 216 | * 2 --------------------------------[ WHERE ]----------------------------------> 4 |
||||
| 217 | * 2 --------------------------------[ ORDER ]----------------------------------> 5 |
||||
| 218 | * 2 --------------------------------[ LIMIT ]----------------------------------> 6 |
||||
| 219 | */ |
||||
| 220 | 62 | $state = 0; |
|||
| 221 | |||||
| 222 | /** |
||||
| 223 | * If the query is multi-table or not. |
||||
| 224 | */ |
||||
| 225 | 62 | $multiTable = false; |
|||
| 226 | |||||
| 227 | 62 | for (; $list->idx < $list->count; ++$list->idx) { |
|||
| 228 | /** |
||||
| 229 | * Token parsed at this moment. |
||||
| 230 | */ |
||||
| 231 | 62 | $token = $list->tokens[$list->idx]; |
|||
| 232 | |||||
| 233 | // End of statement. |
||||
| 234 | 62 | if ($token->type === TokenType::Delimiter) { |
|||
| 235 | 38 | break; |
|||
| 236 | } |
||||
| 237 | |||||
| 238 | 62 | if ($state === 0) { |
|||
| 239 | 62 | if ($token->type === TokenType::Keyword) { |
|||
| 240 | 52 | if ($token->keyword !== 'FROM') { |
|||
| 241 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
| 242 | 2 | break; |
|||
| 243 | } |
||||
| 244 | |||||
| 245 | 50 | ++$list->idx; // Skip 'FROM' |
|||
| 246 | 50 | $this->from = ExpressionArray::parse($parser, $list); |
|||
| 247 | |||||
| 248 | 50 | $state = 2; |
|||
| 249 | } else { |
||||
| 250 | 12 | $this->columns = ExpressionArray::parse($parser, $list); |
|||
| 251 | 12 | $state = 1; |
|||
| 252 | } |
||||
| 253 | 56 | } elseif ($state === 1) { |
|||
| 254 | 12 | if ($token->type !== TokenType::Keyword) { |
|||
| 255 | 2 | $parser->error('Unexpected token.', $token); |
|||
| 256 | 2 | break; |
|||
| 257 | } |
||||
| 258 | |||||
| 259 | 10 | if ($token->keyword !== 'FROM') { |
|||
| 260 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
| 261 | 2 | break; |
|||
| 262 | } |
||||
| 263 | |||||
| 264 | 8 | ++$list->idx; // Skip 'FROM' |
|||
| 265 | 8 | $this->from = ExpressionArray::parse($parser, $list); |
|||
| 266 | |||||
| 267 | 8 | $state = 2; |
|||
| 268 | 50 | } elseif ($state === 2) { |
|||
| 269 | 50 | if ($token->type === TokenType::Keyword) { |
|||
| 270 | 50 | if (stripos($token->keyword, 'JOIN') !== false) { |
|||
| 271 | 4 | ++$list->idx; |
|||
| 272 | 4 | $this->join = JoinKeywords::parse($parser, $list); |
|||
| 273 | |||||
| 274 | // remain in state = 2 |
||||
| 275 | } else { |
||||
| 276 | 50 | switch ($token->keyword) { |
|||
| 277 | 50 | case 'USING': |
|||
| 278 | 14 | ++$list->idx; // Skip 'USING' |
|||
| 279 | 14 | $this->using = ExpressionArray::parse($parser, $list); |
|||
| 280 | 14 | $state = 3; |
|||
| 281 | |||||
| 282 | 14 | $multiTable = true; |
|||
| 283 | 14 | break; |
|||
| 284 | 38 | case 'WHERE': |
|||
| 285 | 30 | ++$list->idx; // Skip 'WHERE' |
|||
| 286 | 30 | $this->where = Conditions::parse($parser, $list); |
|||
| 287 | 30 | $state = 4; |
|||
| 288 | 30 | break; |
|||
| 289 | 8 | case 'ORDER BY': |
|||
| 290 | 4 | ++$list->idx; // Skip 'ORDER BY' |
|||
| 291 | 4 | $this->order = OrderKeywords::parse($parser, $list); |
|||
| 292 | 4 | $state = 5; |
|||
| 293 | 4 | break; |
|||
| 294 | 4 | case 'LIMIT': |
|||
| 295 | 2 | ++$list->idx; // Skip 'LIMIT' |
|||
| 296 | 2 | $this->limit = Limits::parse($parser, $list); |
|||
| 297 | 2 | $state = 6; |
|||
| 298 | 2 | break; |
|||
| 299 | default: |
||||
| 300 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
| 301 | 2 | break 2; |
|||
| 302 | } |
||||
| 303 | } |
||||
| 304 | } |
||||
| 305 | 34 | } elseif ($state === 3) { |
|||
| 306 | 14 | if ($token->type !== TokenType::Keyword) { |
|||
| 307 | 4 | $parser->error('Unexpected token.', $token); |
|||
| 308 | 4 | break; |
|||
| 309 | } |
||||
| 310 | |||||
| 311 | 10 | if ($token->keyword !== 'WHERE') { |
|||
| 312 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
| 313 | 2 | break; |
|||
| 314 | } |
||||
| 315 | |||||
| 316 | 8 | ++$list->idx; // Skip 'WHERE' |
|||
| 317 | 8 | $this->where = Conditions::parse($parser, $list); |
|||
| 318 | 8 | $state = 4; |
|||
| 319 | 24 | } elseif ($state === 4) { |
|||
| 320 | 22 | if ($multiTable === true && $token->type === TokenType::Keyword) { |
|||
| 321 | 4 | $parser->error('This type of clause is not valid in Multi-table queries.', $token); |
|||
| 322 | 4 | break; |
|||
| 323 | } |
||||
| 324 | |||||
| 325 | 18 | if ($token->type === TokenType::Keyword) { |
|||
| 326 | 18 | switch ($token->keyword) { |
|||
| 327 | 18 | case 'ORDER BY': |
|||
| 328 | 14 | ++$list->idx; // Skip 'ORDER BY' |
|||
| 329 | 14 | $this->order = OrderKeywords::parse($parser, $list); |
|||
| 330 | 14 | $state = 5; |
|||
| 331 | 14 | break; |
|||
| 332 | 4 | case 'LIMIT': |
|||
| 333 | 2 | ++$list->idx; // Skip 'LIMIT' |
|||
| 334 | 2 | $this->limit = Limits::parse($parser, $list); |
|||
| 335 | 2 | $state = 6; |
|||
| 336 | 2 | break; |
|||
| 337 | default: |
||||
| 338 | 2 | $parser->error('Unexpected keyword.', $token); |
|||
| 339 | 2 | break 2; |
|||
| 340 | } |
||||
| 341 | } |
||||
| 342 | 12 | } elseif ($state === 5) { |
|||
| 343 | 12 | if ($token->type === TokenType::Keyword) { |
|||
| 344 | 12 | if ($token->keyword !== 'LIMIT') { |
|||
| 345 | 4 | $parser->error('Unexpected keyword.', $token); |
|||
| 346 | 4 | break; |
|||
| 347 | } |
||||
| 348 | |||||
| 349 | 8 | ++$list->idx; // Skip 'LIMIT' |
|||
| 350 | 8 | $this->limit = Limits::parse($parser, $list); |
|||
| 351 | 8 | $state = 6; |
|||
| 352 | } |
||||
| 353 | } |
||||
| 354 | } |
||||
| 355 | |||||
| 356 | 62 | if ($state >= 2) { |
|||
| 357 | 56 | foreach ($this->from as $fromExpr) { |
|||
| 358 | 56 | $fromExpr->database = $fromExpr->table; |
|||
| 359 | 56 | $fromExpr->table = $fromExpr->column; |
|||
| 360 | 56 | $fromExpr->column = null; |
|||
| 361 | } |
||||
| 362 | } |
||||
| 363 | |||||
| 364 | 62 | --$list->idx; |
|||
| 365 | } |
||||
| 366 | } |
||||
| 367 |