├── README.md ├── composer.json └── src └── PHPSQL ├── Creator.php ├── Exception ├── Exception.php ├── InvalidParameter.php ├── UnableToCalculatePosition.php ├── UnableToCreateSQL.php └── UnsupportedFeature.php ├── Expression ├── Token.php └── Type.php ├── Parser.php └── Parser ├── Constants.php ├── Lexer.php ├── Lexer └── Splitter.php ├── PositionCalculator.php └── Utils.php /README.md: -------------------------------------------------------------------------------- 1 | PHP SQL Parser 2 | -------------- 3 | 4 | A fork of https://code.google.com/p/php-sql-parser/ 5 | 6 | A Parser for mysql-ish queries that can represent a query as an array. 7 | 8 | # Goals: 9 | 1. A PSR-0 Compatible implementation 10 | 2. Improvements 11 | 3. Profit!!! 12 | 13 | ## Usage 14 | 15 | ### Use your PSR-0 Compatible Autoloader or the sample one provided in example.php 16 | 17 | 18 | ## Improvements/Feedback. 19 | 20 | Please send them to me, or send a pull request. I will honor every reasonable request, where reasonable usually means elegance, simplicity and bug fixes. Suggestions for improvement are welcome, though you'll see them sooner if you write them. 21 | I will take unit tests as well! 22 | 23 | ## License 24 | 25 | PHPSQLParser is licensed under The BSD 2-Clause License, available online here: http://opensource.org/licenses/bsd-license.php 26 | 27 | /** 28 | * A pure PHP SQL (non validating) parser w/ focus on MySQL dialect of SQL 29 | * 30 | * Copyright (c) 2010-2012, Justin Swanhart 31 | * with contributions by André Rothe 32 | * with contributions by Dan Vande More 33 | * 34 | * All rights reserved. 35 | * 36 | * Redistribution and use in source and binary forms, with or without modification, 37 | * are permitted provided that the following conditions are met: 38 | * 39 | * * Redistributions of source code must retain the above copyright notice, 40 | * this list of conditions and the following disclaimer. 41 | * * Redistributions in binary form must reproduce the above copyright notice, 42 | * this list of conditions and the following disclaimer in the documentation 43 | * and/or other materials provided with the distribution. 44 | * 45 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 46 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 47 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 48 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 49 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 50 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 51 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 52 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 53 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 54 | * DAMAGE. 55 | */ -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "soundintheory/php-sql-parser", 3 | "type": "library", 4 | "description": "SQL parsing tools for PHP", 5 | "license": "BSD-2-Clause", 6 | "require": { 7 | "php": ">=5.3.3" 8 | }, 9 | "autoload": { 10 | "psr-0": { "PHPSQL": "src/" } 11 | }, 12 | "authors": [ 13 | { "name": "Justin Swanhart", "email": "greenlion@gmail.com" }, 14 | { "name": "André Rothe", "email": "phosco@gmx.de" }, 15 | { "name": "Dan Vande More", "homepage": "https://github.com/fimbulvetr", "role": "contributor" }, 16 | { "name": "Andy White", "homepage": "https://github.com/soundintheory", "role": "maintainer" } 17 | ], 18 | "minimum-stability": "stable" 19 | } -------------------------------------------------------------------------------- /src/PHPSQL/Creator.php: -------------------------------------------------------------------------------- 1 | 8 | * with contributions by Dan Vande More 9 | * 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * andgo/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | 34 | namespace PHPSQL; 35 | 36 | class Creator { 37 | 38 | public $created; 39 | 40 | public function __construct($parsed = false) { 41 | if ($parsed) { 42 | $this->create($parsed); 43 | } 44 | } 45 | 46 | public function create($parsed) { 47 | $k = key($parsed); 48 | switch ($k) { 49 | case "UNION": 50 | case "UNION ALL": 51 | throw new \PHPSQL\Exception\UnsupportedFeature($k); 52 | break; 53 | case "SELECT": 54 | $this->created = $this->processSelectStatement($parsed); 55 | break; 56 | case "INSERT": 57 | $this->created = $this->processInsertStatement($parsed); 58 | break; 59 | case "DELETE": 60 | $this->created = $this->processDeleteStatement($parsed); 61 | break; 62 | case "UPDATE": 63 | $this->created = $this->processUpdateStatement($parsed); 64 | break; 65 | default: 66 | throw new \PHPSQL\Exception\UnsupportedFeature($k); 67 | break; 68 | } 69 | return $this->created; 70 | } 71 | 72 | protected function processSelectStatement($parsed) { 73 | $sql = $this->processSELECT($parsed['SELECT']) . " " . $this->processFROM($parsed['FROM']); 74 | if (isset($parsed['WHERE'])) { 75 | $sql .= " " . $this->processWHERE($parsed['WHERE']); 76 | } 77 | if (isset($parsed['GROUP'])) { 78 | $sql .= " " . $this->processGROUP($parsed['GROUP']); 79 | } 80 | if (isset($parsed['ORDER'])) { 81 | $sql .= " " . $this->processORDER($parsed['ORDER']); 82 | } 83 | if (isset($parsed['LIMIT'])) { 84 | $sql .= " " . $this->processLIMIT($parsed['LIMIT']); 85 | } 86 | return $sql; 87 | } 88 | 89 | protected function processInsertStatement($parsed) { 90 | return $this->processINSERT($parsed['INSERT']) . " " . $this->processVALUES($parsed['VALUES']); 91 | # TODO: subquery? 92 | } 93 | 94 | protected function processDeleteStatement($parsed) { 95 | return $this->processDELETE($parsed['DELETE']) . " " . $this->processFROM($parsed['FROM']) . " " 96 | . $this->processWHERE($parsed['WHERE']); 97 | } 98 | 99 | protected function processUpdateStatement($parsed) { 100 | $sql = $this->processUPDATE($parsed['UPDATE']) . " " . $this->processSET($parsed['SET']); 101 | if (isset($parsed['WHERE'])) { 102 | $sql .= " " . $this->processWHERE($parsed['WHERE']); 103 | } 104 | return $sql; 105 | } 106 | 107 | protected function processDELETE($parsed) { 108 | $sql = "DELETE"; 109 | foreach ($parsed['TABLES'] as $k => $v) { 110 | $sql .= $v . ","; 111 | } 112 | return substr($sql, 0, -1); 113 | } 114 | 115 | protected function processSELECT($parsed) { 116 | $sql = ""; 117 | foreach ($parsed as $k => $v) { 118 | $len = strlen($sql); 119 | $sql .= $this->processColRef($v); 120 | $sql .= $this->processSelectExpression($v); 121 | $sql .= $this->processFunction($v); 122 | $sql .= $this->processConstant($v); 123 | 124 | if ($len == strlen($sql)) { 125 | throw new \PHPSQL\Exception\UnableToCreateSQL('SELECT', $k, $v, 'expr_type'); 126 | } 127 | 128 | $sql .= ","; 129 | } 130 | $sql = substr($sql, 0, -1); 131 | return "SELECT " . $sql; 132 | } 133 | 134 | protected function processFROM($parsed) { 135 | $sql = ""; 136 | foreach ($parsed as $k => $v) { 137 | $len = strlen($sql); 138 | $sql .= $this->processTable($v, $k); 139 | $sql .= $this->processTableExpression($v, $k); 140 | $sql .= $this->processSubquery($v, $k); 141 | 142 | if ($len == strlen($sql)) { 143 | throw new \PHPSQL\Exception\UnableToCreateSQL('FROM', $k, $v, 'expr_type'); 144 | } 145 | 146 | $sql .= " "; 147 | } 148 | return "FROM " . substr($sql, 0, -1); 149 | } 150 | 151 | protected function processORDER($parsed) { 152 | $sql = ""; 153 | foreach ($parsed as $k => $v) { 154 | $len = strlen($sql); 155 | $sql .= $this->processOrderByAlias($v); 156 | $sql .= $this->processColRef($v); 157 | 158 | if ($len == strlen($sql)) { 159 | throw new \PHPSQL\Exception\UnableToCreateSQL('ORDER', $k, $v, 'expr_type'); 160 | } 161 | 162 | $sql .= ","; 163 | } 164 | $sql = substr($sql, 0, -1); 165 | return "ORDER BY " . $sql; 166 | } 167 | 168 | protected function processLIMIT($parsed) { 169 | $sql = ($parsed['offset'] ? $parsed['offset'] . ", " : "") . $parsed['rowcount']; 170 | if ($sql === "") { 171 | throw new \PHPSQL\Exception\UnableToCreateSQL('LIMIT', 'rowcount', $parsed, 'rowcount'); 172 | } 173 | return "LIMIT " . $sql; 174 | } 175 | 176 | protected function processGROUP($parsed) { 177 | $sql = ""; 178 | foreach ($parsed as $k => $v) { 179 | $len = strlen($sql); 180 | $sql .= $this->processColRef($v); 181 | 182 | if ($len == strlen($sql)) { 183 | throw new \PHPSQL\Exception\UnableToCreateSQL('GROUP', $k, $v, 'expr_type'); 184 | } 185 | 186 | $sql .= ","; 187 | } 188 | $sql = substr($sql, 0, -1); 189 | return "GROUP BY " . $sql; 190 | } 191 | 192 | protected function processRecord($parsed) { 193 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::RECORD) { 194 | return ""; 195 | } 196 | $sql = ""; 197 | foreach ($parsed['data'] as $k => $v) { 198 | $len = strlen($sql); 199 | $sql .= $this->processConstant($v); 200 | $sql .= $this->processFunction($v); 201 | $sql .= $this->processOperator($v); 202 | 203 | if ($len == strlen($sql)) { 204 | throw new \PHPSQL\Exception\UnableToCreateSQL(\PHPSQL\Expression\Type::RECORD, $k, $v, 'expr_type'); 205 | } 206 | 207 | $sql .= ","; 208 | } 209 | $sql = substr($sql, 0, -1); 210 | return "(" . $sql . ")"; 211 | 212 | } 213 | 214 | protected function processVALUES($parsed) { 215 | $sql = ""; 216 | foreach ($parsed as $k => $v) { 217 | $len = strlen($sql); 218 | $sql .= $this->processRecord($v); 219 | 220 | if ($len == strlen($sql)) { 221 | throw new \PHPSQL\Exception\UnableToCreateSQL('VALUES', $k, $v, 'expr_type'); 222 | } 223 | 224 | $sql .= ","; 225 | } 226 | $sql = substr($sql, 0, -1); 227 | return "VALUES " . $sql; 228 | } 229 | 230 | protected function processINSERT($parsed) { 231 | $sql = "INSERT INTO " . $parsed['table']; 232 | 233 | if ($parsed['columns'] === false) { 234 | return $sql; 235 | } 236 | 237 | $columns = ""; 238 | foreach ($parsed['columns'] as $k => $v) { 239 | $len = strlen($columns); 240 | $columns .= $this->processColRef($v); 241 | 242 | if ($len == strlen($columns)) { 243 | throw new \PHPSQL\Exception\UnableToCreateSQL('INSERT[columns]', $k, $v, 'expr_type'); 244 | } 245 | 246 | $columns .= ","; 247 | } 248 | 249 | if ($columns !== "") { 250 | $columns = " (" . substr($columns, 0, -1) . ")"; 251 | } 252 | 253 | $sql .= $columns; 254 | return $sql; 255 | } 256 | 257 | protected function processUPDATE($parsed) { 258 | return "UPDATE " . $parsed[0]['table']; 259 | } 260 | 261 | protected function processSetExpression($parsed) { 262 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::EXPRESSION) { 263 | return ""; 264 | } 265 | $sql = ""; 266 | foreach ($parsed['sub_tree'] as $k => $v) { 267 | $len = strlen($sql); 268 | $sql .= $this->processColRef($v); 269 | $sql .= $this->processConstant($v); 270 | $sql .= $this->processOperator($v); 271 | $sql .= $this->processFunction($v); 272 | 273 | if ($len == strlen($sql)) { 274 | throw new \PHPSQL\Exception\UnableToCreateSQL('SET expression subtree', $k, $v, 'expr_type'); 275 | } 276 | 277 | $sql .= " "; 278 | } 279 | 280 | $sql = substr($sql, 0, -1); 281 | return $sql; 282 | } 283 | 284 | protected function processSET($parsed) { 285 | $sql = ""; 286 | foreach ($parsed as $k => $v) { 287 | $len = strlen($sql); 288 | $sql .= $this->processSetExpression($v); 289 | 290 | if ($len == strlen($sql)) { 291 | throw new \PHPSQL\Exception\UnableToCreateSQL('SET', $k, $v, 'expr_type'); 292 | } 293 | 294 | $sql .= ","; 295 | } 296 | return "SET " . substr($sql, 0, -1); 297 | } 298 | 299 | protected function processWHERE($parsed) { 300 | $sql = "WHERE "; 301 | foreach ($parsed as $k => $v) { 302 | $len = strlen($sql); 303 | 304 | $sql .= $this->processOperator($v); 305 | $sql .= $this->processConstant($v); 306 | $sql .= $this->processColRef($v); 307 | $sql .= $this->processSubquery($v); 308 | $sql .= $this->processInList($v); 309 | $sql .= $this->processFunction($v); 310 | $sql .= $this->processWhereExpression($v); 311 | $sql .= $this->processWhereBracketExpression($v); 312 | 313 | if (strlen($sql) == $len) { 314 | throw new \PHPSQL\Exception\UnableToCreateSQL('WHERE', $k, $v, 'expr_type'); 315 | } 316 | 317 | $sql .= " "; 318 | } 319 | return substr($sql, 0, -1); 320 | } 321 | 322 | protected function processWhereExpression($parsed) { 323 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::EXPRESSION) { 324 | return ""; 325 | } 326 | $sql = ""; 327 | foreach ($parsed['sub_tree'] as $k => $v) { 328 | $len = strlen($sql); 329 | $sql .= $this->processColRef($v); 330 | $sql .= $this->processConstant($v); 331 | $sql .= $this->processOperator($v); 332 | $sql .= $this->processInList($v); 333 | $sql .= $this->processFunction($v); 334 | $sql .= $this->processWhereExpression($v); 335 | $sql .= $this->processWhereBracketExpression($v); 336 | 337 | if ($len == strlen($sql)) { 338 | throw new \PHPSQL\Exception\UnableToCreateSQL('WHERE expression subtree', $k, $v, 'expr_type'); 339 | } 340 | 341 | $sql .= " "; 342 | } 343 | 344 | $sql = substr($sql, 0, -1); 345 | return $sql; 346 | } 347 | 348 | protected function processWhereBracketExpression($parsed) { 349 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::BRACKET_EXPRESSION) { 350 | return ""; 351 | } 352 | $sql = ""; 353 | foreach ($parsed['sub_tree'] as $k => $v) { 354 | $len = strlen($sql); 355 | $sql .= $this->processColRef($v); 356 | $sql .= $this->processConstant($v); 357 | $sql .= $this->processOperator($v); 358 | $sql .= $this->processInList($v); 359 | $sql .= $this->processFunction($v); 360 | $sql .= $this->processWhereExpression($v); 361 | $sql .= $this->processWhereBracketExpression($v); 362 | 363 | if ($len == strlen($sql)) { 364 | throw new \PHPSQL\Exception\UnableToCreateSQL('WHERE expression subtree', $k, $v, 'expr_type'); 365 | } 366 | 367 | $sql .= " "; 368 | } 369 | 370 | $sql = "(" . substr($sql, 0, -1) . ")"; 371 | return $sql; 372 | } 373 | 374 | protected function processOrderByAlias($parsed) { 375 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::ALIAS) { 376 | return ""; 377 | } 378 | return $parsed['base_expr'] . $this->processDirection($parsed['direction']); 379 | } 380 | 381 | protected function processLimitRowCount($key, $value) { 382 | if ($key != 'rowcount') { 383 | return ""; 384 | } 385 | return $value; 386 | } 387 | 388 | protected function processLimitOffset($key, $value) { 389 | if ($key !== 'offset') { 390 | return ""; 391 | } 392 | return $value; 393 | } 394 | 395 | protected function processFunction($parsed) { 396 | if (($parsed['expr_type'] !== \PHPSQL\Expression\Type::AGGREGATE_FUNCTION) 397 | && ($parsed['expr_type'] !== \PHPSQL\Expression\Type::SIMPLE_FUNCTION)) { 398 | return ""; 399 | } 400 | 401 | if ($parsed['sub_tree'] === false) { 402 | return $parsed['base_expr'] . "()"; 403 | } 404 | 405 | $sql = ""; 406 | foreach ($parsed['sub_tree'] as $k => $v) { 407 | $len = strlen($sql); 408 | $sql .= $this->processFunction($v); 409 | $sql .= $this->processConstant($v); 410 | $sql .= $this->processColRef($v); 411 | $sql .= $this->processReserved($v); 412 | 413 | if ($len == strlen($sql)) { 414 | throw new \PHPSQL\Exception\UnableToCreateSQL('function subtree', $k, $v, 'expr_type'); 415 | } 416 | 417 | $sql .= ($this->isReserved($v) ? " " : ","); 418 | } 419 | return $parsed['base_expr'] . "(" . substr($sql, 0, -1) . ")"; 420 | } 421 | 422 | protected function processSelectExpression($parsed) { 423 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::EXPRESSION) { 424 | return ""; 425 | } 426 | $sql = $this->processSubTree($parsed, " "); 427 | $sql .= $this->processAlias($parsed['alias']); 428 | return $sql; 429 | } 430 | 431 | protected function processSelectBracketExpression($parsed) { 432 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::BRACKET_EXPRESSION) { 433 | return ""; 434 | } 435 | $sql = $this->processSubTree($parsed, " "); 436 | $sql = "(" . $sql . ")"; 437 | return $sql; 438 | } 439 | 440 | protected function processSubTree($parsed, $delim = " ") { 441 | if ($parsed['sub_tree'] === '') { 442 | return ""; 443 | } 444 | $sql = ""; 445 | foreach ($parsed['sub_tree'] as $k => $v) { 446 | $len = strlen($sql); 447 | $sql .= $this->processFunction($v); 448 | $sql .= $this->processOperator($v); 449 | $sql .= $this->processConstant($v); 450 | $sql .= $this->processSubQuery($v); 451 | $sql .= $this->processSelectBracketExpression($v); 452 | 453 | if ($len == strlen($sql)) { 454 | throw new \PHPSQL\Exception\UnableToCreateSQL('expression subtree', $k, $v, 'expr_type'); 455 | } 456 | 457 | $sql .= $delim; 458 | } 459 | return substr($sql, 0, -1); 460 | } 461 | 462 | protected function processRefClause($parsed) { 463 | if ($parsed === false) { 464 | return ""; 465 | } 466 | 467 | $sql = ""; 468 | foreach ($parsed as $k => $v) { 469 | $len = strlen($sql); 470 | $sql .= $this->processColRef($v); 471 | $sql .= $this->processOperator($v); 472 | $sql .= $this->processConstant($v); 473 | 474 | if ($len == strlen($sql)) { 475 | throw new \PHPSQL\Exception\UnableToCreateSQL('expression ref_clause', $k, $v, 'expr_type'); 476 | } 477 | 478 | $sql .= " "; 479 | } 480 | return "(" . substr($sql, 0, -1) . ")"; 481 | } 482 | 483 | protected function processAlias($parsed) { 484 | if ($parsed === false) { 485 | return ""; 486 | } 487 | $sql = ""; 488 | if ($parsed['as']) { 489 | $sql .= " as"; 490 | } 491 | $sql .= " " . $parsed['name']; 492 | return $sql; 493 | } 494 | 495 | protected function processJoin($parsed) { 496 | if ($parsed === 'CROSS') { 497 | return ","; 498 | } 499 | if ($parsed === 'JOIN') { 500 | return "INNER JOIN"; 501 | } 502 | if ($parsed === 'LEFT') { 503 | return "LEFT JOIN"; 504 | } 505 | if ($parsed === 'RIGHT') { 506 | return "RIGHT JOIN"; 507 | } 508 | // TODO: add more 509 | throw new \PHPSQL\Exception\UnsupportedFeature($parsed); 510 | } 511 | 512 | protected function processRefType($parsed) { 513 | if ($parsed === false) { 514 | return ""; 515 | } 516 | if ($parsed === 'ON') { 517 | return " ON "; 518 | } 519 | if ($parsed === 'USING') { 520 | return " USING "; 521 | } 522 | // TODO: add more 523 | throw new \PHPSQL\Exception\UnsupportedFeature($parsed); 524 | } 525 | 526 | protected function processTable($parsed, $index) { 527 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::TABLE) { 528 | return ""; 529 | } 530 | 531 | $sql = $parsed['table']; 532 | $sql .= $this->processAlias($parsed['alias']); 533 | 534 | if ($index !== 0) { 535 | $sql = $this->processJoin($parsed['join_type']) . " " . $sql; 536 | $sql .= $this->processRefType($parsed['ref_type']); 537 | $sql .= $this->processRefClause($parsed['ref_clause']); 538 | } 539 | return $sql; 540 | } 541 | 542 | protected function processTableExpression($parsed, $index) { 543 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::TABLE_EXPRESSION) { 544 | return ""; 545 | } 546 | $sql = substr($this->processFROM($parsed['sub_tree']), 5); // remove FROM keyword 547 | $sql = "(" . $sql . ")"; 548 | $sql .= $this->processAlias($parsed['alias']); 549 | 550 | if ($index !== 0) { 551 | $sql = $this->processJoin($parsed['join_type']) . " " . $sql; 552 | $sql .= $this->processRefType($parsed['ref_type']); 553 | $sql .= $this->processRefClause($parsed['ref_clause']); 554 | } 555 | return $sql; 556 | } 557 | 558 | protected function processSubQuery($parsed, $index = 0) { 559 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::SUBQUERY) { 560 | return ""; 561 | } 562 | 563 | $sql = $this->processSelectStatement($parsed['sub_tree']); 564 | $sql = "(" . $sql . ")"; 565 | 566 | if (isset($parsed['alias'])) { 567 | $sql .= $this->processAlias($parsed['alias']); 568 | } 569 | 570 | if ($index !== 0) { 571 | $sql = $this->processJoin($parsed['join_type']) . " " . $sql; 572 | $sql .= $this->processRefType($parsed['ref_type']); 573 | $sql .= $this->processRefClause($parsed['ref_clause']); 574 | } 575 | return $sql; 576 | } 577 | 578 | protected function processOperator($parsed) { 579 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::OPERATOR) { 580 | return ""; 581 | } 582 | return $parsed['base_expr']; 583 | } 584 | 585 | protected function processColRef($parsed) { 586 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::COLREF) { 587 | return ""; 588 | } 589 | $sql = $parsed['base_expr']; 590 | if (isset($parsed['alias'])) { 591 | $sql .= $this->processAlias($parsed['alias']); 592 | } 593 | if (isset($parsed['direction'])) { 594 | $sql .= $this->processDirection($parsed['direction']); 595 | } 596 | return $sql; 597 | } 598 | 599 | protected function processDirection($parsed) { 600 | $sql = ($parsed ? " " . $parsed : ""); 601 | return $sql; 602 | } 603 | 604 | protected function processReserved($parsed) { 605 | if (!$this->isReserved($parsed)) { 606 | return ""; 607 | } 608 | return $parsed['base_expr']; 609 | } 610 | 611 | protected function isReserved($parsed) { 612 | return ($parsed['expr_type'] === \PHPSQL\Expression\Type::RESERVED); 613 | } 614 | 615 | protected function processConstant($parsed) { 616 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::CONSTANT) { 617 | return ""; 618 | } 619 | return $parsed['base_expr']; 620 | } 621 | 622 | protected function processInList($parsed) { 623 | if ($parsed['expr_type'] !== \PHPSQL\Expression\Type::IN_LIST) { 624 | return ""; 625 | } 626 | $sql = $this->processSubTree($parsed, ","); 627 | return "(" . $sql . ")"; 628 | } 629 | 630 | } 631 | -------------------------------------------------------------------------------- /src/PHPSQL/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | 7 | * with contributions by Dan Vande More 8 | * 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without modification, 12 | * are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright notice, 17 | * this list of conditions and the following disclaimer in the documentation 18 | * and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 23 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 26 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 29 | * DAMAGE. 30 | */ 31 | 32 | namespace PHPSQL\Exception; 33 | 34 | class InvalidParameter extends \InvalidArgumentException { 35 | 36 | protected $argument; 37 | 38 | public function __construct($argument) { 39 | $this->argument = $argument; 40 | parent::__construct("no SQL string to parse: \n" . $argument, 10); 41 | } 42 | 43 | public function getArgument() { 44 | return $this->argument; 45 | } 46 | } -------------------------------------------------------------------------------- /src/PHPSQL/Exception/UnableToCalculatePosition.php: -------------------------------------------------------------------------------- 1 | 7 | * with contributions by Dan Vande More 8 | * 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without modification, 12 | * are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright notice, 17 | * this list of conditions and the following disclaimer in the documentation 18 | * and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 23 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 26 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 29 | * DAMAGE. 30 | */ 31 | 32 | namespace PHPSQL\Exception; 33 | 34 | class UnableToCalculatePosition extends \PHPSQL\Exception\Exception { 35 | 36 | protected $needle; 37 | protected $haystack; 38 | 39 | public function __construct($needle, $haystack) { 40 | $this->needle = $needle; 41 | $this->haystack = $haystack; 42 | parent::__construct("cannot calculate position of " . $needle . " within " . $haystack, 5); 43 | } 44 | 45 | public function getNeedle() { 46 | return $this->needle; 47 | } 48 | 49 | public function getHaystack() { 50 | return $this->haystack; 51 | } 52 | } -------------------------------------------------------------------------------- /src/PHPSQL/Exception/UnableToCreateSQL.php: -------------------------------------------------------------------------------- 1 | 7 | * with contributions by Dan Vande More 8 | * 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without modification, 12 | * are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright notice, 17 | * this list of conditions and the following disclaimer in the documentation 18 | * and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 23 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 26 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 29 | * DAMAGE. 30 | */ 31 | 32 | namespace PHPSQL\Exception; 33 | 34 | class UnableToCreateSQL extends \PHPSQL\Exception\Exception { 35 | 36 | protected $part; 37 | protected $partkey; 38 | protected $entry; 39 | protected $entrykey; 40 | 41 | public function __construct($part, $partkey, $entry, $entrykey) { 42 | $this->part = $part; 43 | $this->partkey = $partkey; 44 | $this->entry = $entry; 45 | $this->entrykey = $entrykey; 46 | parent::__construct("unknown " . $entrykey . " in " . $part . "[" . $partkey . "] " . $entry[$entrykey], 15); 47 | } 48 | 49 | public function getEntry() { 50 | return $this->entry; 51 | } 52 | 53 | public function getEntryKey() { 54 | return $this->entrykey; 55 | } 56 | 57 | public function getSQLPart() { 58 | return $this->part; 59 | } 60 | 61 | public function getSQLPartKey() { 62 | return $this->partkey; 63 | } 64 | } -------------------------------------------------------------------------------- /src/PHPSQL/Exception/UnsupportedFeature.php: -------------------------------------------------------------------------------- 1 | 7 | * with contributions by Dan Vande More 8 | * 9 | * All rights reserved. 10 | * 11 | * Redistribution and use in source and binary forms, with or without modification, 12 | * are permitted provided that the following conditions are met: 13 | * 14 | * * Redistributions of source code must retain the above copyright notice, 15 | * this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright notice, 17 | * this list of conditions and the following disclaimer in the documentation 18 | * and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 21 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 23 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 25 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 26 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 29 | * DAMAGE. 30 | */ 31 | 32 | namespace PHPSQL\Exception; 33 | 34 | class UnsupportedFeature extends \PHPSQL\Exception\Exception { 35 | 36 | protected $key; 37 | 38 | public function __construct($key) { 39 | $this->key = $key; 40 | parent::__construct($key . " not implemented.", 20); 41 | } 42 | 43 | public function getKey() { 44 | return $this->key; 45 | } 46 | } -------------------------------------------------------------------------------- /src/PHPSQL/Expression/Token.php: -------------------------------------------------------------------------------- 1 | 10 | * with contributions by Dan Vande More 11 | * 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without modification, 15 | * are permitted provided that the following conditions are met: 16 | * 17 | * * Redistributions of source code must retain the above copyright notice, 18 | * this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above copyright notice, 20 | * this list of conditions and the following disclaimer in the documentation 21 | * and/or other materials provided with the distribution. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 24 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 26 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 29 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 32 | * DAMAGE. 33 | */ 34 | 35 | namespace PHPSQL\Expression; 36 | 37 | class Token { 38 | 39 | private $subTree; 40 | private $expression; 41 | private $key; 42 | private $token; 43 | private $tokenType; 44 | private $trim; 45 | private $upper; 46 | 47 | public function __construct($key = "", $token = "") { 48 | $this->subTree = false; 49 | $this->expression = ""; 50 | $this->key = $key; 51 | $this->token = $token; 52 | $this->tokenType = false; 53 | $this->trim = trim($token); 54 | $this->upper = strtoupper($this->trim); 55 | } 56 | 57 | # TODO: we could replace it with a constructor new \PHPSQL\Expression\Token(this, "*") 58 | public function addToken($string) { 59 | $this->token .= $string; 60 | } 61 | 62 | public function isEnclosedWithinParenthesis() { 63 | return ($this->upper[0] === '(' && substr($this->upper, -1) === ')'); 64 | } 65 | 66 | public function setSubTree($tree) { 67 | $this->subTree = $tree; 68 | } 69 | 70 | public function getSubTree() { 71 | return $this->subTree; 72 | } 73 | 74 | public function getUpper($idx = false) { 75 | return $idx !== false ? $this->upper[$idx] : $this->upper; 76 | } 77 | 78 | public function getTrim($idx = false) { 79 | return $idx !== false ? $this->trim[$idx] : $this->trim; 80 | } 81 | 82 | public function getToken($idx = false) { 83 | return $idx !== false ? $this->token[$idx] : $this->token; 84 | } 85 | 86 | public function setTokenType($type) { 87 | $this->tokenType = $type; 88 | } 89 | 90 | public function endsWith($needle) { 91 | $length = strlen($needle); 92 | if ($length == 0) { 93 | return true; 94 | } 95 | 96 | $start = $length * -1; 97 | return (substr($this->token, $start) === $needle); 98 | } 99 | 100 | public function isWhitespaceToken() { 101 | return ($this->trim === ""); 102 | } 103 | 104 | public function isCommaToken() { 105 | return ($this->trim === ","); 106 | } 107 | 108 | public function isVariableToken() { 109 | return $this->upper[0] === '@'; 110 | } 111 | 112 | public function isSubQueryToken() { 113 | return preg_match("/^\\(\\s*SELECT/i", $this->trim); 114 | } 115 | 116 | public function isExpression() { 117 | return $this->tokenType === \PHPSQL\Expression\Type::EXPRESSION; 118 | } 119 | 120 | public function isBracketExpression() { 121 | return $this->tokenType === \PHPSQL\Expression\Type::BRACKET_EXPRESSION; 122 | } 123 | 124 | public function isOperator() { 125 | return $this->tokenType === \PHPSQL\Expression\Type::OPERATOR; 126 | } 127 | 128 | public function isInList() { 129 | return $this->tokenType === \PHPSQL\Expression\Type::IN_LIST; 130 | } 131 | 132 | public function isFunction() { 133 | return $this->tokenType === \PHPSQL\Expression\Type::SIMPLE_FUNCTION; 134 | } 135 | 136 | public function isUnspecified() { 137 | return ($this->tokenType === false); 138 | } 139 | 140 | public function isAggregateFunction() { 141 | return $this->tokenType === \PHPSQL\Expression\Type::AGGREGATE_FUNCTION; 142 | } 143 | 144 | public function isColumnReference() { 145 | return $this->tokenType === \PHPSQL\Expression\Type::COLREF; 146 | } 147 | 148 | public function isConstant() { 149 | return $this->tokenType === \PHPSQL\Expression\Type::CONSTANT; 150 | } 151 | 152 | public function isSign() { 153 | return $this->tokenType === \PHPSQL\Expression\Type::SIGN; 154 | } 155 | 156 | public function isSubQuery() { 157 | return $this->tokenType === \PHPSQL\Expression\Type::SUBQUERY; 158 | } 159 | 160 | public function toArray() { 161 | return array('expr_type' => $this->tokenType, 'base_expr' => $this->token, 'sub_tree' => $this->subTree); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/PHPSQL/Expression/Type.php: -------------------------------------------------------------------------------- 1 | 10 | * with contributions by Dan Vande More 11 | * 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without modification, 15 | * are permitted provided that the following conditions are met: 16 | * 17 | * * Redistributions of source code must retain the above copyright notice, 18 | * this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above copyright notice, 20 | * this list of conditions and the following disclaimer in the documentation 21 | * and/or other materials provided with the distribution. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 24 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 26 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 29 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 32 | * DAMAGE. 33 | */ 34 | 35 | namespace PHPSQL\Expression; 36 | 37 | class Type { 38 | 39 | const USER_VARIABLE = "user_variable"; 40 | const SESSION_VARIABLE = "session_variable"; 41 | const GLOBAL_VARIABLE = "global_variable"; 42 | const LOCAL_VARIABLE = "local_variable"; 43 | 44 | const COLREF = "colref"; 45 | const RESERVED = "reserved"; 46 | const CONSTANT = "const"; 47 | 48 | const AGGREGATE_FUNCTION = "aggregate_function"; 49 | const SIMPLE_FUNCTION = "function"; 50 | 51 | const EXPRESSION = "expression"; 52 | const BRACKET_EXPRESSION = "bracket_expression"; 53 | const TABLE_EXPRESSION = "table_expression"; 54 | 55 | const SUBQUERY = "subquery"; 56 | const IN_LIST = "in-list"; 57 | const OPERATOR = "operator"; 58 | const SIGN = "sign"; 59 | const RECORD = "record"; 60 | 61 | const MATCH_ARGUMENTS = "match-arguments"; 62 | const MATCH_MODE = "match-mode"; 63 | 64 | const ALIAS = "alias"; 65 | const POSITION = "pos"; 66 | 67 | const TEMPORARY_TABLE = "temporary_table"; 68 | const TABLE = "table"; 69 | const VIEW = "view"; 70 | const DATABASE = "database"; 71 | const SCHEMA = "schema"; 72 | } 73 | -------------------------------------------------------------------------------- /src/PHPSQL/Parser.php: -------------------------------------------------------------------------------- 1 | 10 | * with contributions by Dan Vande More 11 | * 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without modification, 15 | * are permitted provided that the following conditions are met: 16 | * 17 | * * Redistributions of source code must retain the above copyright notice, 18 | * this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above copyright notice, 20 | * this list of conditions and the following disclaimer in the documentation 21 | * and/or other materials provided with the distribution. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 24 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 26 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 29 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 32 | * DAMAGE. 33 | */ 34 | 35 | namespace PHPSQL; 36 | 37 | /** 38 | * This class implements the parser functionality. 39 | * @author greenlion@gmail.com 40 | * @author arothe@phosco.info 41 | */ 42 | class Parser extends \PHPSQL\Parser\Utils { 43 | 44 | private $lexer; 45 | public $parsed; 46 | 47 | public function __construct($sql = false, $calcPositions = false) { 48 | $this->lexer = new \PHPSQL\Parser\Lexer(); 49 | if ($sql) { 50 | $this->parse($sql, $calcPositions); 51 | } 52 | } 53 | 54 | public function parse($sql, $calcPositions = false) { 55 | #lex the SQL statement 56 | $inputArray = $this->splitSQLIntoTokens($sql); 57 | 58 | #This is the highest level lexical analysis. This is the part of the 59 | #code which finds UNION and UNION ALL query parts 60 | $queries = $this->processUnion($inputArray); 61 | 62 | # If there was no UNION or UNION ALL in the query, then the query is 63 | # stored at $queries[0]. 64 | if (!$this->isUnion($queries)) { 65 | $queries = $this->processSQL($queries[0]); 66 | } 67 | 68 | # calc the positions of some important tokens 69 | if ($calcPositions) { 70 | $calculator = new \PHPSQL\Parser\PositionCalculator(); 71 | $queries = $calculator->setPositionsWithinSQL($sql, $queries); 72 | } 73 | 74 | # store the parsed queries 75 | $this->parsed = $queries; 76 | return $this->parsed; 77 | } 78 | 79 | private function processUnion($inputArray) { 80 | $outputArray = array(); 81 | 82 | #sometimes the parser needs to skip ahead until a particular 83 | #token is found 84 | $skipUntilToken = false; 85 | 86 | #This is the last type of union used (UNION or UNION ALL) 87 | #indicates a) presence of at least one union in this query 88 | # b) the type of union if this is the first or last query 89 | $unionType = false; 90 | 91 | #Sometimes a "query" consists of more than one query (like a UNION query) 92 | #this array holds all the queries 93 | $queries = array(); 94 | 95 | foreach ($inputArray as $key => $token) { 96 | $trim = trim($token); 97 | 98 | # overread all tokens till that given token 99 | if ($skipUntilToken) { 100 | if ($trim === "") { 101 | continue; # read the next token 102 | } 103 | if (strtoupper($trim) === $skipUntilToken) { 104 | $skipUntilToken = false; 105 | continue; # read the next token 106 | } 107 | } 108 | 109 | if (strtoupper($trim) !== "UNION") { 110 | $outputArray[] = $token; # here we get empty tokens, if we remove these, we get problems in parse_sql() 111 | continue; 112 | } 113 | 114 | $unionType = "UNION"; 115 | 116 | # we are looking for an ALL token right after UNION 117 | for ($i = $key + 1; $i < count($inputArray); ++$i) { 118 | if (trim($inputArray[$i]) === "") { 119 | continue; 120 | } 121 | if (strtoupper($inputArray[$i]) !== "ALL") { 122 | break; 123 | } 124 | # the other for-loop should overread till "ALL" 125 | $skipUntilToken = "ALL"; 126 | $unionType = "UNION ALL"; 127 | } 128 | 129 | # store the tokens related to the unionType 130 | $queries[$unionType][] = $outputArray; 131 | $outputArray = array(); 132 | } 133 | 134 | # the query tokens after the last UNION or UNION ALL 135 | # or we don't have an UNION/UNION ALL 136 | if (!empty($outputArray)) { 137 | if ($unionType) { 138 | $queries[$unionType][] = $outputArray; 139 | } else { 140 | $queries[] = $outputArray; 141 | } 142 | } 143 | 144 | return $this->processMySQLUnion($queries); 145 | } 146 | 147 | /** MySQL supports a special form of UNION: 148 | * (select ...) 149 | * union 150 | * (select ...) 151 | * 152 | * This function handles this query syntax. Only one such subquery 153 | * is supported in each UNION block. (select)(select)union(select) is not legal. 154 | * The extra queries will be silently ignored. 155 | */ 156 | private function processMySQLUnion($queries) { 157 | $unionTypes = array('UNION', 'UNION ALL'); 158 | foreach ($unionTypes as $unionType) { 159 | 160 | if (empty($queries[$unionType])) { 161 | continue; 162 | } 163 | 164 | foreach ($queries[$unionType] as $key => $tokenList) { 165 | foreach ($tokenList as $z => $token) { 166 | $token = trim($token); 167 | if ($token === "") { 168 | continue; 169 | } 170 | 171 | # starts with "(select" 172 | if (preg_match("/^\\(\\s*select\\s*/i", $token)) { 173 | $queries[$unionType][$key] = $this->parse($this->removeParenthesisFromStart($token)); 174 | break; 175 | } 176 | 177 | $queries[$unionType][$key] = $this->processSQL($queries[$unionType][$key]); 178 | break; 179 | } 180 | } 181 | } 182 | # it can be parsed or not 183 | return $queries; 184 | } 185 | 186 | private function isUnion($queries) { 187 | $unionTypes = array('UNION', 'UNION ALL'); 188 | foreach ($unionTypes as $unionType) { 189 | if (!empty($queries[$unionType])) { 190 | return true; 191 | } 192 | } 193 | return false; 194 | } 195 | 196 | #this function splits up a SQL statement into easy to "parse" 197 | #tokens for the SQL processor 198 | private function splitSQLIntoTokens($sql) { 199 | return $this->lexer->split($sql); 200 | } 201 | 202 | /* This function breaks up the SQL statement into logical sections. 203 | Some sections are then further handled by specialized functions. 204 | */ 205 | private function processSQL(&$tokens) { 206 | $prev_category = ""; 207 | $token_category = ""; 208 | $skip_next = false; 209 | $out = false; 210 | 211 | $tokenCount = count($tokens); 212 | for ($tokenNumber = 0; $tokenNumber < $tokenCount; ++$tokenNumber) { 213 | 214 | $token = $tokens[$tokenNumber]; 215 | $trim = trim($token); # this removes also \n and \t! 216 | 217 | # if it starts with an "(", it should follow a SELECT 218 | if ($trim !== "" && $trim[0] === "(" && $token_category === "") { 219 | $token_category = 'SELECT'; 220 | } 221 | 222 | /* If it isn't obvious, when $skip_next is set, then we ignore the next real 223 | token, that is we ignore whitespace. 224 | */ 225 | if ($skip_next) { 226 | if ($trim === "") { 227 | if ($token_category !== "") { # is this correct?? 228 | $out[$token_category][] = $token; 229 | } 230 | continue; 231 | } 232 | #to skip the token we replace it with whitespace 233 | $trim = ""; 234 | $token = ""; 235 | $skip_next = false; 236 | } 237 | 238 | $upper = strtoupper($trim); 239 | switch ($upper) { 240 | 241 | /* Tokens that get their own sections. These keywords have subclauses. */ 242 | case 'SELECT': 243 | case 'ORDER': 244 | case 'LIMIT': 245 | case 'SET': 246 | case 'DUPLICATE': 247 | case 'VALUES': 248 | case 'GROUP': 249 | case 'HAVING': 250 | case 'WHERE': 251 | case 'RENAME': 252 | case 'CALL': 253 | case 'PROCEDURE': 254 | case 'FUNCTION': 255 | case 'SERVER': 256 | case 'LOGFILE': 257 | case 'DEFINER': 258 | case 'RETURNS': 259 | case 'TABLESPACE': 260 | case 'TRIGGER': 261 | case 'DO': 262 | case 'PLUGIN': 263 | case 'FROM': 264 | case 'FLUSH': 265 | case 'KILL': 266 | case 'RESET': 267 | case 'START': 268 | case 'STOP': 269 | case 'PURGE': 270 | case 'EXECUTE': 271 | case 'PREPARE': 272 | case 'DEALLOCATE': 273 | if ($trim === 'DEALLOCATE') { 274 | $skip_next = true; 275 | } 276 | /* this FROM is different from FROM in other DML (not join related) */ 277 | if ($token_category === 'PREPARE' && $upper === 'FROM') { 278 | continue 2; 279 | } 280 | 281 | $token_category = $upper; 282 | break; 283 | 284 | case 'DATABASE': 285 | case 'SCHEMA': 286 | if ($prev_category === 'DROP') { 287 | continue; 288 | } 289 | $token_category = $upper; 290 | break; 291 | 292 | case 'EVENT': 293 | # issue 71 294 | if ($prev_category === 'DROP' || $prev_category === 'ALTER' || $prev_category === 'CREATE') { 295 | $token_category = $upper; 296 | } 297 | break; 298 | 299 | case 'DATA': 300 | # prevent wrong handling of DATA as keyword 301 | if ($prev_category === 'LOAD') { 302 | $token_category = $upper; 303 | } 304 | break; 305 | 306 | case 'INTO': 307 | # prevent wrong handling of CACHE within LOAD INDEX INTO CACHE... 308 | if ($prev_category === 'LOAD') { 309 | $out[$prev_category][] = $upper; 310 | continue 2; 311 | } 312 | $token_category = $upper; 313 | break; 314 | 315 | case 'USER': 316 | # prevent wrong processing as keyword 317 | if ($prev_category === 'CREATE' || $prev_category === 'RENAME' || $prev_category === 'DROP') { 318 | $token_category = $upper; 319 | } 320 | break; 321 | 322 | case 'VIEW': 323 | # prevent wrong processing as keyword 324 | if ($prev_category === 'CREATE' || $prev_category === 'ALTER' || $prev_category === 'DROP') { 325 | $token_category = $upper; 326 | } 327 | break; 328 | 329 | /* These tokens get their own section, but have no subclauses. 330 | These tokens identify the statement but have no specific subclauses of their own. */ 331 | case 'DELETE': 332 | case 'ALTER': 333 | case 'INSERT': 334 | case 'REPLACE': 335 | case 'TRUNCATE': 336 | case 'CREATE': 337 | case 'OPTIMIZE': 338 | case 'GRANT': 339 | case 'REVOKE': 340 | case 'SHOW': 341 | case 'HANDLER': 342 | case 'LOAD': 343 | case 'ROLLBACK': 344 | case 'SAVEPOINT': 345 | case 'UNLOCK': 346 | case 'INSTALL': 347 | case 'UNINSTALL': 348 | case 'ANALZYE': 349 | case 'BACKUP': 350 | case 'CHECK': 351 | case 'CHECKSUM': 352 | case 'REPAIR': 353 | case 'RESTORE': 354 | case 'DESCRIBE': 355 | case 'EXPLAIN': 356 | case 'USE': 357 | case 'HELP': 358 | $token_category = $upper; /* set the category in case these get subclauses 359 | in a future version of MySQL */ 360 | $out[$upper][0] = $upper; 361 | continue 2; 362 | break; 363 | 364 | case 'CACHE': 365 | if ($prev_category === "" || $prev_category === 'RESET' || $prev_category === 'FLUSH' 366 | || $prev_category === 'LOAD') { 367 | $token_category = $upper; 368 | continue 2; 369 | } 370 | break; 371 | 372 | /* This is either LOCK TABLES or SELECT ... LOCK IN SHARE MODE*/ 373 | case 'LOCK': 374 | if ($token_category === "") { 375 | $token_category = $upper; 376 | $out[$upper][0] = $upper; 377 | } else { 378 | $trim = 'LOCK IN SHARE MODE'; 379 | $skip_next = true; 380 | $out['OPTIONS'][] = $trim; 381 | } 382 | continue 2; 383 | break; 384 | 385 | case 'USING': /* USING in FROM clause is different from USING w/ prepared statement*/ 386 | if ($token_category === 'EXECUTE') { 387 | $token_category = $upper; 388 | continue 2; 389 | } 390 | if ($token_category === 'FROM' && !empty($out['DELETE'])) { 391 | $token_category = $upper; 392 | continue 2; 393 | } 394 | break; 395 | 396 | /* DROP TABLE is different from ALTER TABLE DROP ... */ 397 | case 'DROP': 398 | if ($token_category !== 'ALTER') { 399 | $token_category = $upper; 400 | continue 2; 401 | } 402 | break; 403 | 404 | case 'FOR': 405 | $skip_next = true; 406 | $out['OPTIONS'][] = 'FOR UPDATE'; 407 | continue 2; 408 | break; 409 | 410 | case 'UPDATE': 411 | if ($token_category === "") { 412 | $token_category = $upper; 413 | continue 2; 414 | 415 | } 416 | if ($token_category === 'DUPLICATE') { 417 | continue 2; 418 | } 419 | break; 420 | 421 | case 'START': 422 | $trim = "BEGIN"; 423 | $out[$upper][0] = $upper; 424 | $skip_next = true; 425 | break; 426 | 427 | /* These tokens are ignored. */ 428 | case 'BY': 429 | case 'ALL': 430 | case 'SHARE': 431 | case 'MODE': 432 | case 'TO': 433 | case ';': 434 | continue 2; 435 | break; 436 | 437 | case 'KEY': 438 | if ($token_category === 'DUPLICATE') { 439 | continue 2; 440 | } 441 | break; 442 | 443 | /* These tokens set particular options for the statement. They never stand alone.*/ 444 | case 'DISTINCTROW': 445 | $trim = 'DISTINCT'; 446 | case 'DISTINCT': 447 | case 'HIGH_PRIORITY': 448 | case 'LOW_PRIORITY': 449 | case 'DELAYED': 450 | case 'IGNORE': 451 | case 'FORCE': 452 | case 'STRAIGHT_JOIN': 453 | case 'SQL_SMALL_RESULT': 454 | case 'SQL_BIG_RESULT': 455 | case 'QUICK': 456 | case 'SQL_BUFFER_RESULT': 457 | case 'SQL_CACHE': 458 | case 'SQL_NO_CACHE': 459 | case 'SQL_CALC_FOUND_ROWS': 460 | $out['OPTIONS'][] = $upper; 461 | continue 2; 462 | break; 463 | 464 | case 'WITH': 465 | if ($token_category === 'GROUP') { 466 | $skip_next = true; 467 | $out['OPTIONS'][] = 'WITH ROLLUP'; 468 | continue 2; 469 | } 470 | break; 471 | 472 | case 'AS': 473 | break; 474 | 475 | case '': 476 | case ',': 477 | case ';': 478 | break; 479 | 480 | default: 481 | break; 482 | } 483 | 484 | # remove obsolete category after union (empty category because of 485 | # empty token before select) 486 | if ($token_category !== "" && ($prev_category === $token_category)) { 487 | $out[$token_category][] = $token; 488 | } 489 | 490 | $prev_category = $token_category; 491 | } 492 | 493 | return $this->processSQLParts($out); 494 | } 495 | 496 | private function processSQLParts($out) { 497 | if (!$out) { 498 | return false; 499 | } 500 | if (!empty($out['SELECT'])) { 501 | $out['SELECT'] = $this->process_select($out['SELECT']); 502 | } 503 | if (!empty($out['FROM'])) { 504 | $out['FROM'] = $this->process_from($out['FROM']); 505 | } 506 | if (!empty($out['USING'])) { 507 | $out['USING'] = $this->process_from($out['USING']); 508 | } 509 | if (!empty($out['UPDATE'])) { 510 | $out['UPDATE'] = $this->processUpdate($out['UPDATE']); 511 | } 512 | if (!empty($out['GROUP'])) { 513 | # set empty array if we have partial SQL statement 514 | $out['GROUP'] = $this->process_group($out['GROUP'], isset($out['SELECT']) ? $out['SELECT'] : array()); 515 | } 516 | if (!empty($out['ORDER'])) { 517 | # set empty array if we have partial SQL statement 518 | $out['ORDER'] = $this->process_order($out['ORDER'], isset($out['SELECT']) ? $out['SELECT'] : array()); 519 | } 520 | if (!empty($out['LIMIT'])) { 521 | $out['LIMIT'] = $this->process_limit($out['LIMIT']); 522 | } 523 | if (!empty($out['WHERE'])) { 524 | $out['WHERE'] = $this->process_expr_list($out['WHERE']); 525 | } 526 | if (!empty($out['HAVING'])) { 527 | $out['HAVING'] = $this->process_expr_list($out['HAVING']); 528 | } 529 | if (!empty($out['SET'])) { 530 | $out['SET'] = $this->process_set_list($out['SET'], isset($out['UPDATE'])); 531 | } 532 | if (!empty($out['DUPLICATE'])) { 533 | $out['ON DUPLICATE KEY UPDATE'] = $this->process_set_list($out['DUPLICATE']); 534 | unset($out['DUPLICATE']); 535 | } 536 | if (!empty($out['INSERT'])) { 537 | $out = $this->processInsert($out); 538 | } 539 | if (!empty($out['REPLACE'])) { 540 | $out = $this->processReplace($out); 541 | } 542 | if (!empty($out['DELETE'])) { 543 | $out = $this->process_delete($out); 544 | } 545 | if (!empty($out['VALUES'])) { 546 | $out = $this->process_values($out); 547 | } 548 | if (!empty($out['INTO'])) { 549 | $out = $this->processInto($out); 550 | } 551 | if (!empty($out['DROP'])) { 552 | $out['DROP'] = $this->processDrop($out['DROP']); 553 | } 554 | return $out; 555 | } 556 | 557 | /** 558 | * A SET list is simply a list of key = value expressions separated by comma (,). 559 | * This function produces a list of the key/value expressions. 560 | */ 561 | private function getAssignment($base_expr) { 562 | $assignment = $this->process_expr_list($this->splitSQLIntoTokens($base_expr)); 563 | return array('expr_type' => \PHPSQL\Expression\Type::EXPRESSION, 'base_expr' => trim($base_expr), 564 | 'sub_tree' => $assignment); 565 | } 566 | 567 | private function getVariableType($expression) { 568 | // $expression must contain only upper-case characters 569 | if ($expression[1] !== "@") { 570 | return \PHPSQL\Expression\Type::USER_VARIABLE; 571 | } 572 | 573 | $type = substr($expression, 2, strpos($expression, ".", 2)); 574 | 575 | switch ($type) { 576 | case 'GLOBAL': 577 | $type = \PHPSQL\Expression\Type::GLOBAL_VARIABLE; 578 | break; 579 | case 'LOCAL': 580 | $type = \PHPSQL\Expression\Type::LOCAL_VARIABLE; 581 | break; 582 | case 'SESSION': 583 | default: 584 | $type = \PHPSQL\Expression\Type::SESSION_VARIABLE; 585 | break; 586 | } 587 | return $type; 588 | } 589 | 590 | /** 591 | * It can be UPDATE SET or SET alone 592 | */ 593 | private function process_set_list($tokens, $isUpdate) { 594 | $result = array(); 595 | $baseExpr = ""; 596 | $assignment = false; 597 | $varType = false; 598 | 599 | foreach ($tokens as $token) { 600 | $upper = strtoupper(trim($token)); 601 | 602 | switch ($upper) { 603 | case 'LOCAL': 604 | case 'SESSION': 605 | case 'GLOBAL': 606 | if (!$isUpdate) { 607 | $varType = $this->getVariableType("@@" . $upper . "."); 608 | $baseExpr = ""; 609 | continue 2; 610 | } 611 | break; 612 | 613 | case ',': 614 | $assignment = $this->getAssignment($baseExpr); 615 | if (!$isUpdate && $varType !== false) { 616 | $assignment['sub_tree'][0]['expr_type'] = $varType; 617 | } 618 | $result[] = $assignment; 619 | $baseExpr = ""; 620 | $varType = false; 621 | continue 2; 622 | 623 | default: 624 | } 625 | $baseExpr .= $token; 626 | } 627 | 628 | if (trim($baseExpr) !== "") { 629 | $assignment = $this->getAssignment($baseExpr); 630 | if (!$isUpdate && $varType !== false) { 631 | $assignment['sub_tree'][0]['expr_type'] = $varType; 632 | } 633 | $result[] = $assignment; 634 | } 635 | 636 | return $result; 637 | } 638 | 639 | /** 640 | * This function processes the LIMIT section. 641 | * start,end are set. If only end is provided in the query 642 | * then start is set to 0. 643 | */ 644 | private function process_limit($tokens) { 645 | $rowcount = ""; 646 | $offset = ""; 647 | 648 | $comma = -1; 649 | $exchange = false; 650 | 651 | for ($i = 0; $i < count($tokens); ++$i) { 652 | $trim = trim($tokens[$i]); 653 | if ($trim === ",") { 654 | $comma = $i; 655 | break; 656 | } 657 | if ($trim === "OFFSET") { 658 | $comma = $i; 659 | $exchange = true; 660 | break; 661 | } 662 | } 663 | 664 | for ($i = 0; $i < $comma; ++$i) { 665 | if ($exchange) { 666 | $rowcount .= $tokens[$i]; 667 | } else { 668 | $offset .= $tokens[$i]; 669 | } 670 | } 671 | 672 | for ($i = $comma + 1; $i < count($tokens); ++$i) { 673 | if ($exchange) { 674 | $offset .= $tokens[$i]; 675 | } else { 676 | $rowcount .= $tokens[$i]; 677 | } 678 | } 679 | 680 | return array('offset' => trim($offset), 'rowcount' => trim($rowcount)); 681 | } 682 | 683 | /** 684 | * This function processes the SELECT section. It splits the clauses at the commas. 685 | * Each clause is then processed by process_select_expr() and the results are added to 686 | * the expression list. 687 | * 688 | * Finally, at the end, the epxression list is returned. 689 | */ 690 | private function process_select(&$tokens) { 691 | $expression = ""; 692 | $expressionList = array(); 693 | foreach ($tokens as $token) { 694 | if ($this->isCommaToken($token)) { 695 | $expressionList[] = $this->process_select_expr(trim($expression)); 696 | $expression = ""; 697 | } else { 698 | $expression .= $token; 699 | } 700 | } 701 | if ($expression) { 702 | $expressionList[] = $this->process_select_expr(trim($expression)); 703 | } 704 | return $expressionList; 705 | } 706 | 707 | private function isCommaToken($token) { 708 | return (trim($token) === ","); 709 | } 710 | 711 | private function isWhitespaceToken($token) { 712 | return (trim($token) === ""); 713 | } 714 | 715 | private function isCommentToken($token) { 716 | return isset($token[0]) && isset($token[1]) 717 | && (($token[0] === '-' && $token[1] === '-') || ($token[0] === '/' && $token[1] === '*')); 718 | } 719 | 720 | private function isColumnReference($out) { 721 | return (isset($out['expr_type']) && $out['expr_type'] === \PHPSQL\Expression\Type::COLREF); 722 | } 723 | 724 | private function isReserved($out) { 725 | return (isset($out['expr_type']) && $out['expr_type'] === \PHPSQL\Expression\Type::RESERVED); 726 | } 727 | 728 | private function isConstant($out) { 729 | return (isset($out['expr_type']) && $out['expr_type'] === \PHPSQL\Expression\Type::CONSTANT); 730 | } 731 | 732 | private function isAggregateFunction($out) { 733 | return (isset($out['expr_type']) && $out['expr_type'] === \PHPSQL\Expression\Type::AGGREGATE_FUNCTION); 734 | } 735 | 736 | private function isFunction($out) { 737 | return (isset($out['expr_type']) && $out['expr_type'] === \PHPSQL\Expression\Type::SIMPLE_FUNCTION); 738 | } 739 | 740 | private function isExpression($out) { 741 | return (isset($out['expr_type']) && $out['expr_type'] === \PHPSQL\Expression\Type::EXPRESSION); 742 | } 743 | 744 | private function isBracketExpression($out) { 745 | return (isset($out['expr_type']) && $out['expr_type'] === \PHPSQL\Expression\Type::BRACKET_EXPRESSION); 746 | } 747 | 748 | private function isSubQuery($out) { 749 | return (isset($out['expr_type']) && $out['expr_type'] === \PHPSQL\Expression\Type::SUBQUERY); 750 | } 751 | 752 | /** 753 | * This fuction processes each SELECT clause. We determine what (if any) alias 754 | * is provided, and we set the type of expression. 755 | */ 756 | private function process_select_expr($expression) { 757 | 758 | $tokens = $this->splitSQLIntoTokens($expression); 759 | $token_count = count($tokens); 760 | 761 | /* Determine if there is an explicit alias after the AS clause. 762 | If AS is found, then the next non-whitespace token is captured as the alias. 763 | The tokens after (and including) the AS are removed. 764 | */ 765 | $base_expr = ""; 766 | $stripped = array(); 767 | $capture = false; 768 | $alias = false; 769 | $processed = false; 770 | 771 | for ($i = 0; $i < $token_count; ++$i) { 772 | $token = $tokens[$i]; 773 | $upper = strtoupper($token); 774 | 775 | if ($upper === 'AS') { 776 | $alias = array('as' => true, "name" => "", "base_expr" => $token); 777 | $tokens[$i] = ""; 778 | $capture = true; 779 | continue; 780 | } 781 | 782 | if (!$this->isWhitespaceToken($upper)) { 783 | $stripped[] = $token; 784 | } 785 | 786 | // we have an explicit AS, next one can be the alias 787 | // but also a comment! 788 | if ($capture) { 789 | if (!$this->isWhitespaceToken($upper) && !$this->isCommentToken($upper)) { 790 | $alias['name'] .= $token; 791 | array_pop($stripped); 792 | } 793 | $alias['base_expr'] .= $token; 794 | $tokens[$i] = ""; 795 | continue; 796 | } 797 | 798 | $base_expr .= $token; 799 | } 800 | 801 | $stripped = $this->process_expr_list($stripped); 802 | 803 | # TODO: the last part can also be a comment, don't use array_pop 804 | 805 | # we remove the last token, if it is a colref, 806 | # it can be an alias without an AS 807 | $last = array_pop($stripped); 808 | if (!$alias && $this->isColumnReference($last)) { 809 | 810 | # TODO: it can be a comment, don't use array_pop 811 | 812 | # check the token before the colref 813 | $prev = array_pop($stripped); 814 | 815 | if ($this->isReserved($prev) || $this->isConstant($prev) || $this->isAggregateFunction($prev) 816 | || $this->isFunction($prev) || $this->isExpression($prev) || $this->isSubQuery($prev) 817 | || $this->isColumnReference($prev) || $this->isBracketExpression($prev)) { 818 | 819 | $alias = array('as' => false, 'name' => trim($last['base_expr']), 820 | 'base_expr' => trim($last['base_expr'])); 821 | #remove the last token 822 | array_pop($tokens); 823 | $base_expr = join("", $tokens); 824 | } 825 | } 826 | 827 | if (!$alias) { 828 | $base_expr = join("", $tokens); 829 | } else { 830 | /* remove escape from the alias */ 831 | $alias['name'] = $this->revokeEscaping(trim($alias['name'])); 832 | $alias['base_expr'] = trim($alias['base_expr']); 833 | } 834 | 835 | # TODO: this is always done with $stripped, how we do it twice? 836 | $processed = $this->process_expr_list($tokens); 837 | 838 | # if there is only one part, we copy the expr_type 839 | # in all other cases we use "expression" as global type 840 | $type = \PHPSQL\Expression\Type::EXPRESSION; 841 | if (count($processed) === 1) { 842 | if (!$this->isSubQuery($processed[0])) { 843 | $type = $processed[0]['expr_type']; 844 | $base_expr = $processed[0]['base_expr']; 845 | $processed = $processed[0]['sub_tree']; // it can be FALSE 846 | } 847 | } 848 | 849 | return array('expr_type' => $type, 'alias' => $alias, 'base_expr' => trim($base_expr), 850 | 'sub_tree' => $processed); 851 | } 852 | 853 | /** 854 | * This method handles the FROM clause. 855 | */ 856 | private function process_from(&$tokens) { 857 | 858 | $parseInfo = $this->initParseInfoForFrom(); 859 | $expr = array(); 860 | 861 | $skip_next = false; 862 | $i = 0; 863 | 864 | foreach ($tokens as $token) { 865 | $upper = strtoupper(trim($token)); 866 | 867 | if ($skip_next && $token !== "") { 868 | $parseInfo['token_count']++; 869 | $skip_next = false; 870 | continue; 871 | } else { 872 | if ($skip_next) { 873 | continue; 874 | } 875 | } 876 | 877 | switch ($upper) { 878 | case 'OUTER': 879 | case 'LEFT': 880 | case 'RIGHT': 881 | case 'NATURAL': 882 | case 'CROSS': 883 | case ',': 884 | case 'JOIN': 885 | case 'INNER': 886 | break; 887 | 888 | default: 889 | $parseInfo['expression'] .= $token; 890 | if ($parseInfo['ref_type'] !== false) { # all after ON / USING 891 | $parseInfo['ref_expr'] .= $token; 892 | } 893 | break; 894 | } 895 | 896 | switch ($upper) { 897 | case 'AS': 898 | $parseInfo['alias'] = array('as' => true, 'name' => "", 'base_expr' => $token); 899 | $parseInfo['token_count']++; 900 | $n = 1; 901 | $str = ""; 902 | while ($str == "") { 903 | $parseInfo['alias']['base_expr'] .= ($tokens[$i + $n] === "" ? " " : $tokens[$i + $n]); 904 | $str = trim($tokens[$i + $n]); 905 | ++$n; 906 | } 907 | $parseInfo['alias']['name'] = $str; 908 | $parseInfo['alias']['base_expr'] = trim($parseInfo['alias']['base_expr']); 909 | continue; 910 | 911 | case 'INDEX': 912 | if ($token_category == 'CREATE') { 913 | $token_category = $upper; 914 | continue 2; 915 | } 916 | 917 | break; 918 | 919 | case 'USING': 920 | case 'ON': 921 | $parseInfo['ref_type'] = $upper; 922 | $parseInfo['ref_expr'] = ""; 923 | 924 | case 'CROSS': 925 | case 'USE': 926 | case 'FORCE': 927 | case 'IGNORE': 928 | case 'INNER': 929 | case 'OUTER': 930 | $parseInfo['token_count']++; 931 | continue; 932 | break; 933 | 934 | case 'FOR': 935 | $parseInfo['token_count']++; 936 | $skip_next = true; 937 | continue; 938 | break; 939 | 940 | case 'LEFT': 941 | case 'RIGHT': 942 | case 'STRAIGHT_JOIN': 943 | $parseInfo['next_join_type'] = $upper; 944 | break; 945 | 946 | case ',': 947 | $parseInfo['next_join_type'] = 'CROSS'; 948 | 949 | case 'JOIN': 950 | if ($parseInfo['subquery']) { 951 | $parseInfo['sub_tree'] = $this->parse($this->removeParenthesisFromStart($parseInfo['subquery'])); 952 | $parseInfo['expression'] = $parseInfo['subquery']; 953 | } 954 | 955 | $expr[] = $this->processFromExpression($parseInfo); 956 | $parseInfo = $this->initParseInfoForFrom($parseInfo); 957 | break; 958 | 959 | default: 960 | if ($upper === "") { 961 | continue; # ends the switch statement! 962 | } 963 | 964 | if ($parseInfo['token_count'] === 0) { 965 | if ($parseInfo['table'] === "") { 966 | $parseInfo['table'] = $token; 967 | } 968 | } else if ($parseInfo['token_count'] === 1) { 969 | $parseInfo['alias'] = array('as' => false, 'name' => trim($token), 'base_expr' => trim($token)); 970 | } 971 | $parseInfo['token_count']++; 972 | break; 973 | } 974 | ++$i; 975 | } 976 | 977 | $expr[] = $this->processFromExpression($parseInfo); 978 | return $expr; 979 | } 980 | 981 | private function initParseInfoForFrom($parseInfo = false) { 982 | # first init 983 | if ($parseInfo === false) { 984 | $parseInfo = array('join_type' => "", 'saved_join_type' => "JOIN"); 985 | } 986 | # loop init 987 | return array('expression' => "", 'token_count' => 0, 'table' => "", 'alias' => false, 'join_type' => "", 988 | 'next_join_type' => "", 'saved_join_type' => $parseInfo['saved_join_type'], 989 | 'ref_type' => false, 'ref_expr' => false, 'base_expr' => false, 'sub_tree' => false, 990 | 'subquery' => ""); 991 | } 992 | 993 | private function processFromExpression(&$parseInfo) { 994 | 995 | $res = array(); 996 | 997 | # exchange the join types (join_type is save now, saved_join_type holds the next one) 998 | $parseInfo['join_type'] = $parseInfo['saved_join_type']; # initialized with JOIN 999 | $parseInfo['saved_join_type'] = ($parseInfo['next_join_type'] ? $parseInfo['next_join_type'] : 'JOIN'); 1000 | 1001 | # we have a reg_expr, so we have to parse it 1002 | if ($parseInfo['ref_expr'] !== false) { 1003 | $unparsed = $this->splitSQLIntoTokens($this->removeParenthesisFromStart($parseInfo['ref_expr'])); 1004 | 1005 | // here we can get a comma separated list 1006 | foreach ($unparsed as $k => $v) { 1007 | if ($this->isCommaToken($v)) { 1008 | $unparsed[$k] = ""; 1009 | } 1010 | } 1011 | $parseInfo['ref_expr'] = $this->process_expr_list($unparsed); 1012 | } 1013 | 1014 | # there is an expression, we have to parse it 1015 | if (substr(trim($parseInfo['table']), 0, 1) == '(') { 1016 | $parseInfo['expression'] = $this->removeParenthesisFromStart($parseInfo['table']); 1017 | 1018 | if (preg_match("/^\\s*select/i", $parseInfo['expression'])) { 1019 | $parseInfo['sub_tree'] = $this->parse($parseInfo['expression']); 1020 | $res['expr_type'] = \PHPSQL\Expression\Type::SUBQUERY; 1021 | } else { 1022 | $tmp = $this->splitSQLIntoTokens($parseInfo['expression']); 1023 | $parseInfo['sub_tree'] = $this->process_from($tmp); 1024 | $res['expr_type'] = \PHPSQL\Expression\Type::TABLE_EXPRESSION; 1025 | } 1026 | } else { 1027 | $res['expr_type'] = \PHPSQL\Expression\Type::TABLE; 1028 | $res['table'] = $parseInfo['table']; 1029 | } 1030 | 1031 | $res['alias'] = $parseInfo['alias']; 1032 | $res['join_type'] = $parseInfo['join_type']; 1033 | $res['ref_type'] = $parseInfo['ref_type']; 1034 | $res['ref_clause'] = $parseInfo['ref_expr']; 1035 | $res['base_expr'] = trim($parseInfo['expression']); 1036 | $res['sub_tree'] = $parseInfo['sub_tree']; 1037 | return $res; 1038 | } 1039 | 1040 | private function processOrderExpression(&$parseInfo, $select) { 1041 | $parseInfo['expr'] = trim($parseInfo['expr']); 1042 | 1043 | if ($parseInfo['expr'] === "") { 1044 | return false; 1045 | } 1046 | 1047 | $parseInfo['expr'] = trim($this->revokeEscaping($parseInfo['expr'])); 1048 | 1049 | if (is_numeric($parseInfo['expr'])) { 1050 | $parseInfo['type'] = \PHPSQL\Expression\Type::POSITION; 1051 | } else { 1052 | #search to see if the expression matches an alias 1053 | foreach ($select as $clause) { 1054 | if (!$clause['alias']) { 1055 | continue; 1056 | } 1057 | if ($clause['alias']['name'] === $parseInfo['expr']) { 1058 | $parseInfo['type'] = \PHPSQL\Expression\Type::ALIAS; 1059 | } 1060 | } 1061 | } 1062 | 1063 | if ($parseInfo['type'] === \PHPSQL\Expression\Type::EXPRESSION) { 1064 | $expr = $this->process_select_expr($parseInfo['expr']); 1065 | $expr['direction'] = $parseInfo['dir']; 1066 | unset($expr['alias']); 1067 | return $expr; 1068 | } 1069 | 1070 | return array('expr_type' => $parseInfo['type'], 'base_expr' => $parseInfo['expr'], 1071 | 'direction' => $parseInfo['dir']); 1072 | } 1073 | 1074 | private function initParseInfoForOrder() { 1075 | return array('expr' => "", 'dir' => "ASC", 'type' => \PHPSQL\Expression\Type::EXPRESSION); 1076 | } 1077 | 1078 | /** 1079 | * This method handles the ORDER BY clause 1080 | */ 1081 | private function process_order($tokens, $select) { 1082 | $out = array(); 1083 | $parseInfo = $this->initParseInfoForOrder(); 1084 | 1085 | if (!$tokens) { 1086 | return false; 1087 | } 1088 | 1089 | foreach ($tokens as $token) { 1090 | $upper = strtoupper(trim($token)); 1091 | switch ($upper) { 1092 | case ',': 1093 | $out[] = $this->processOrderExpression($parseInfo, $select); 1094 | $parseInfo = $this->initParseInfoForOrder(); 1095 | break; 1096 | 1097 | case 'DESC': 1098 | $parseInfo['dir'] = "DESC"; 1099 | break; 1100 | 1101 | case 'ASC': 1102 | $parseInfo['dir'] = "ASC"; 1103 | break; 1104 | 1105 | default: 1106 | $parseInfo['expr'] .= $token; 1107 | 1108 | } 1109 | } 1110 | 1111 | $out[] = $this->processOrderExpression($parseInfo, $select); 1112 | return $out; 1113 | } 1114 | 1115 | /** 1116 | * This method handles the GROUP BY clause. 1117 | */ 1118 | private function process_group($tokens, $select) { 1119 | $out = array(); 1120 | $parseInfo = $this->initParseInfoForOrder(); 1121 | 1122 | if (!$tokens) { 1123 | return false; 1124 | } 1125 | 1126 | foreach ($tokens as $token) { 1127 | $trim = strtoupper(trim($token)); 1128 | switch ($trim) { 1129 | case ',': 1130 | $parsed = $this->processOrderExpression($parseInfo, $select); 1131 | unset($parsed['direction']); 1132 | 1133 | $out[] = $parsed; 1134 | $parseInfo = $this->initParseInfoForOrder(); 1135 | break; 1136 | default: 1137 | $parseInfo['expr'] .= $token; 1138 | 1139 | } 1140 | } 1141 | 1142 | $parsed = $this->processOrderExpression($parseInfo, $select); 1143 | unset($parsed['direction']); 1144 | $out[] = $parsed; 1145 | 1146 | return $out; 1147 | } 1148 | 1149 | /** 1150 | * Some sections are just lists of expressions, like the WHERE and HAVING clauses. 1151 | * This function processes these sections. Recursive. 1152 | */ 1153 | private function process_expr_list($tokens) { 1154 | 1155 | $resultList = array(); 1156 | $skip_next = false; 1157 | $prev = new \PHPSQL\Expression\Token(); 1158 | 1159 | foreach ($tokens as $k => $v) { 1160 | 1161 | $curr = new \PHPSQL\Expression\Token($k, $v); 1162 | 1163 | if ($curr->isWhitespaceToken()) { 1164 | continue; 1165 | } 1166 | 1167 | if ($skip_next) { 1168 | # skip the next non-whitespace token 1169 | $skip_next = false; 1170 | continue; 1171 | } 1172 | 1173 | /* is it a subquery?*/ 1174 | if ($curr->isSubQueryToken()) { 1175 | 1176 | $curr->setSubTree($this->parse($this->removeParenthesisFromStart($curr->getTrim()))); 1177 | $curr->setTokenType(\PHPSQL\Expression\Type::SUBQUERY); 1178 | 1179 | } elseif ($curr->isEnclosedWithinParenthesis()) { 1180 | /* is it an in-list? */ 1181 | 1182 | $localTokenList = $this->splitSQLIntoTokens($this->removeParenthesisFromStart($curr->getTrim())); 1183 | 1184 | if ($prev->getUpper() === 'IN') { 1185 | 1186 | foreach ($localTokenList as $k => $v) { 1187 | $tmpToken = new \PHPSQL\Expression\Token($k, $v); 1188 | if ($tmpToken->isCommaToken()) { 1189 | unset($localTokenList[$k]); 1190 | } 1191 | } 1192 | 1193 | $localTokenList = array_values($localTokenList); 1194 | $curr->setSubTree($this->process_expr_list($localTokenList)); 1195 | $curr->setTokenType(\PHPSQL\Expression\Type::IN_LIST); 1196 | 1197 | } elseif ($prev->getUpper() === 'AGAINST') { 1198 | 1199 | $match_mode = false; 1200 | foreach ($localTokenList as $k => $v) { 1201 | 1202 | $tmpToken = new \PHPSQL\Expression\Token($k, $v); 1203 | switch ($tmpToken->getUpper()) { 1204 | case 'WITH': 1205 | $match_mode = 'WITH QUERY EXPANSION'; 1206 | break; 1207 | case 'IN': 1208 | $match_mode = 'IN BOOLEAN MODE'; 1209 | break; 1210 | 1211 | default: 1212 | } 1213 | 1214 | if ($match_mode !== false) { 1215 | unset($localTokenList[$k]); 1216 | } 1217 | } 1218 | 1219 | $tmpToken = $this->process_expr_list($localTokenList); 1220 | 1221 | if ($match_mode !== false) { 1222 | $match_mode = new \PHPSQL\Expression\Token(0, $match_mode); 1223 | $match_mode->setTokenType(\PHPSQL\Expression\Type::MATCH_MODE); 1224 | $tmpToken[] = $match_mode->toArray(); 1225 | } 1226 | 1227 | $curr->setSubTree($tmpToken); 1228 | $curr->setTokenType(\PHPSQL\Expression\Type::MATCH_ARGUMENTS); 1229 | $prev->setTokenType(\PHPSQL\Expression\Type::SIMPLE_FUNCTION); 1230 | 1231 | } elseif ($prev->isColumnReference() || $prev->isFunction() || $prev->isAggregateFunction()) { 1232 | 1233 | # if we have a colref followed by a parenthesis pair, 1234 | # it isn't a colref, it is a user-function 1235 | foreach ($localTokenList as $k => $v) { 1236 | $tmpToken = new \PHPSQL\Expression\Token($k, $v); 1237 | if ($tmpToken->isCommaToken()) { 1238 | unset($localTokenList[$k]); 1239 | } 1240 | } 1241 | 1242 | $localTokenList = array_values($localTokenList); 1243 | $curr->setSubTree($this->process_expr_list($localTokenList)); 1244 | 1245 | $prev->setSubTree($curr->getSubTree()); 1246 | if ($prev->isColumnReference()) { 1247 | $prev->setTokenType(\PHPSQL\Expression\Type::SIMPLE_FUNCTION); 1248 | } 1249 | 1250 | array_pop($resultList); 1251 | $curr = $prev; 1252 | } 1253 | 1254 | # we have parenthesis, but it seems to be an expression 1255 | if ($curr->isUnspecified()) { 1256 | $curr->setSubTree($this->process_expr_list($localTokenList)); 1257 | $curr->setTokenType(\PHPSQL\Expression\Type::BRACKET_EXPRESSION); 1258 | } 1259 | 1260 | } elseif ($curr->isVariableToken()) { 1261 | // a variable 1262 | $curr->setTokenType($this->getVariableType($curr->getUpper())); 1263 | $curr->setSubTree(false); 1264 | 1265 | } else { 1266 | /* it is either an operator, a colref or a constant */ 1267 | switch ($curr->getUpper()) { 1268 | 1269 | case '*': 1270 | $curr->setSubTree(false); #no subtree 1271 | 1272 | # single or first element of expression list -> all-column-alias 1273 | if (empty($resultList)) { 1274 | $curr->setTokenType(\PHPSQL\Expression\Type::COLREF); 1275 | break; 1276 | } 1277 | 1278 | # if the last token is colref, const or expression 1279 | # then * is an operator 1280 | # but if the previous colref ends with a dot, the * is the all-columns-alias 1281 | if (!$prev->isColumnReference() && !$prev->isConstant() && !$prev->isExpression() 1282 | && !$prev->isBracketExpression()) { 1283 | $curr->setTokenType(\PHPSQL\Expression\Type::COLREF); 1284 | break; 1285 | } 1286 | 1287 | if ($prev->isColumnReference() && $prev->endsWith(".")) { 1288 | $prev->addToken('*'); # tablealias dot * 1289 | continue 2; # skip the current token 1290 | } 1291 | 1292 | $curr->setTokenType(\PHPSQL\Expression\Type::OPERATOR); 1293 | break; 1294 | 1295 | case 'AND': 1296 | case '&&': 1297 | case 'BETWEEN': 1298 | case 'AND': 1299 | case 'BINARY': 1300 | case '&': 1301 | case '~': 1302 | case '|': 1303 | case '^': 1304 | case 'DIV': 1305 | case '/': 1306 | case '<=>': 1307 | case '=': 1308 | case '>=': 1309 | case '>': 1310 | case 'IS': 1311 | case 'NOT': 1312 | case '<<': 1313 | case '<=': 1314 | case '<': 1315 | case 'LIKE': 1316 | case '%': 1317 | case '!=': 1318 | case '<>': 1319 | case 'REGEXP': 1320 | case '!': 1321 | case '||': 1322 | case 'OR': 1323 | case '>>': 1324 | case 'RLIKE': 1325 | case 'SOUNDS': 1326 | case 'XOR': 1327 | case 'IN': 1328 | $curr->setSubTree(false); 1329 | $curr->setTokenType(\PHPSQL\Expression\Type::OPERATOR); 1330 | break; 1331 | 1332 | case 'NULL': 1333 | $curr->setSubTree(false); 1334 | $curr->setTokenType(\PHPSQL\Expression\Type::CONSTANT); 1335 | break; 1336 | 1337 | case '-': 1338 | case '+': 1339 | // differ between preceding sign and operator 1340 | $curr->setSubTree(false); 1341 | 1342 | if ($prev->isColumnReference() || $prev->isFunction() || $prev->isAggregateFunction() 1343 | || $prev->isConstant() || $prev->isSubQuery() || $prev->isExpression() 1344 | || $prev->isBracketExpression()) { 1345 | $curr->setTokenType(\PHPSQL\Expression\Type::OPERATOR); 1346 | } else { 1347 | $curr->setTokenType(\PHPSQL\Expression\Type::SIGN); 1348 | } 1349 | break; 1350 | 1351 | default: 1352 | $curr->setSubTree(false); 1353 | 1354 | switch ($curr->getToken(0)) { 1355 | case "'": 1356 | case '"': 1357 | # it is a string literal 1358 | $curr->setTokenType(\PHPSQL\Expression\Type::CONSTANT); 1359 | break; 1360 | case '`': 1361 | # it is an escaped colum name 1362 | $curr->setTokenType(\PHPSQL\Expression\Type::COLREF); 1363 | break; 1364 | 1365 | default: 1366 | if (is_numeric($curr->getToken())) { 1367 | 1368 | if ($prev->isSign()) { 1369 | $prev->addToken($curr->getToken()); # it is a negative numeric constant 1370 | $prev->setTokenType(\PHPSQL\Expression\Type::CONSTANT); 1371 | continue 3; 1372 | # skip current token 1373 | } else { 1374 | $curr->setTokenType(\PHPSQL\Expression\Type::CONSTANT); 1375 | } 1376 | 1377 | } else { 1378 | $curr->setTokenType(\PHPSQL\Expression\Type::COLREF); 1379 | } 1380 | break; 1381 | } 1382 | } 1383 | } 1384 | 1385 | /* is a reserved word? */ 1386 | if (!$curr->isOperator() && !$curr->isInList() && !$curr->isFunction() && !$curr->isAggregateFunction() 1387 | && in_array($curr->getUpper(), parent::$reserved)) { 1388 | 1389 | if (in_array($curr->getUpper(), parent::$aggregateFunctions)) { 1390 | $curr->setTokenType(\PHPSQL\Expression\Type::AGGREGATE_FUNCTION); 1391 | 1392 | } elseif ($curr->getUpper() === 'NULL') { 1393 | // it is a reserved word, but we would like to set it as constant 1394 | $curr->setTokenType(\PHPSQL\Expression\Type::CONSTANT); 1395 | 1396 | } else { 1397 | if (in_array($curr->getUpper(), parent::$parameterizedFunctions)) { 1398 | // issue 60: check functions with parameters 1399 | // -> colref (we check parameters later) 1400 | // -> if there is no parameter, we leave the colref 1401 | $curr->setTokenType(\PHPSQL\Expression\Type::COLREF); 1402 | } elseif (in_array($curr->getUpper(), parent::$functions)) { 1403 | $curr->setTokenType(\PHPSQL\Expression\Type::SIMPLE_FUNCTION); 1404 | } else { 1405 | $curr->setTokenType(\PHPSQL\Expression\Type::RESERVED); 1406 | } 1407 | } 1408 | } 1409 | 1410 | if ($curr->isUnspecified()) { 1411 | $curr->setTokenType(\PHPSQL\Expression\Type::EXPRESSION); 1412 | $curr->setSubTree($this->process_expr_list($this->splitSQLIntoTokens($curr->getTrim()))); 1413 | } 1414 | 1415 | $resultList[] = $curr; 1416 | $prev = $curr; 1417 | 1418 | } // end of for-loop 1419 | 1420 | return $this->toArray($resultList); 1421 | } 1422 | 1423 | /** 1424 | * This method processes UPDATE statements 1425 | */ 1426 | private function processUpdate($tokenList) { 1427 | return $this->process_from($tokenList); 1428 | } 1429 | 1430 | /** 1431 | * This method handles DELETE statements. 1432 | */ 1433 | private function process_delete($tokens) { 1434 | $tables = array(); 1435 | $del = $tokens['DELETE']; 1436 | 1437 | foreach ($tokens['DELETE'] as $expression) { 1438 | if ($expression !== 'DELETE' && trim($expression, ' .*') !== "" && !$this->isCommaToken($expression)) { 1439 | $tables[] = trim($expression, '.* '); 1440 | } 1441 | } 1442 | 1443 | if (empty($tables)) { 1444 | foreach ($tokens['FROM'] as $table) { 1445 | $tables[] = $table['table']; 1446 | } 1447 | } 1448 | 1449 | $tokens['DELETE'] = array('TABLES' => $tables); 1450 | return $tokens; 1451 | } 1452 | 1453 | /** 1454 | * This method handles REPLACE statements. 1455 | */ 1456 | private function processReplace($tokenList) { 1457 | return $this->processInsert($tokenList, 'REPLACE'); 1458 | } 1459 | 1460 | /** 1461 | * This method handles INSERT statements. 1462 | */ 1463 | private function processInsert($tokenList) { 1464 | return $this->processInsertOrReplace($tokenList, 'INSERT'); 1465 | } 1466 | 1467 | /** 1468 | * This method handles INSERT and REPLACE statements. 1469 | */ 1470 | private function processInsertOrReplace($tokenList, $token_category) { 1471 | $table = ""; 1472 | $cols = array(); 1473 | 1474 | $into = $tokenList['INTO']; 1475 | foreach ($into as $token) { 1476 | if ($this->isWhitespaceToken($token)) 1477 | continue; 1478 | if ($table === "") { 1479 | $table = $token; 1480 | } elseif (empty($cols)) { 1481 | $cols[] = $token; 1482 | } 1483 | } 1484 | 1485 | if (empty($cols)) { 1486 | $cols = false; 1487 | } else { 1488 | $columns = explode(",", $this->removeParenthesisFromStart($cols[0])); 1489 | $cols = array(); 1490 | foreach ($columns as $k => $v) { 1491 | $cols[] = array('expr_type' => \PHPSQL\Expression\Type::COLREF, 'base_expr' => trim($v)); 1492 | } 1493 | } 1494 | 1495 | unset($tokenList['INTO']); 1496 | $tokenList[$token_category][0] = array('table' => $table, 'columns' => $cols, 'base_expr' => $table); 1497 | return $tokenList; 1498 | } 1499 | 1500 | private function process_record($unparsed) { 1501 | 1502 | $unparsed = $this->removeParenthesisFromStart($unparsed); 1503 | $values = $this->splitSQLIntoTokens($unparsed); 1504 | 1505 | foreach ($values as $k => $v) { 1506 | if ($this->isCommaToken($v)) { 1507 | $values[$k] = ""; 1508 | } 1509 | } 1510 | return $this->process_expr_list($values); 1511 | } 1512 | 1513 | /** 1514 | * This method handles VALUES parts (from INSERT) 1515 | */ 1516 | private function process_values($tokens) { 1517 | 1518 | $unparsed = ""; 1519 | foreach ($tokens['VALUES'] as $k => $v) { 1520 | if ($this->isWhitespaceToken($v)) { 1521 | continue; 1522 | } 1523 | $unparsed .= $v; 1524 | } 1525 | 1526 | $values = $this->splitSQLIntoTokens($unparsed); 1527 | 1528 | $parsed = array(); 1529 | foreach ($values as $k => $v) { 1530 | if ($this->isCommaToken($v)) { 1531 | unset($values[$k]); 1532 | } else { 1533 | $values[$k] = array('expr_type' => \PHPSQL\Expression\Type::RECORD, 'base_expr' => $v, 1534 | 'data' => $this->process_record($v)); 1535 | } 1536 | } 1537 | 1538 | $tokens['VALUES'] = array_values($values); 1539 | return $tokens; 1540 | } 1541 | 1542 | /** 1543 | * TODO: This is a dummy function, we cannot parse INTO as part of SELECT 1544 | * at the moment 1545 | */ 1546 | private function processInto($tokenList) { 1547 | $unparsed = $tokenList['INTO']; 1548 | foreach ($unparsed as $k => $token) { 1549 | if ($this->isWhitespaceToken($token) || $this->isCommaToken($token)) { 1550 | unset($unparsed[$k]); 1551 | } 1552 | } 1553 | $tokenList['INTO'] = array_values($unparsed); 1554 | return $tokenList; 1555 | } 1556 | 1557 | private function processDrop($tokenList) { 1558 | 1559 | $skip = false; 1560 | $warning = true; 1561 | $base_expr = ""; 1562 | $expr_type = false; 1563 | $option = false; 1564 | $resultList = array(); 1565 | 1566 | foreach ($tokenList as $k => $v) { 1567 | $token = new \PHPSQL\Expression\Token($k, $v); 1568 | 1569 | if ($token->isWhitespaceToken()) { 1570 | continue; 1571 | } 1572 | 1573 | if ($skip === true) { 1574 | $skip = false; 1575 | continue; 1576 | } 1577 | 1578 | switch ($token->getUpper()) { 1579 | case 'VIEW': 1580 | case 'SCHEMA': 1581 | case 'DATABASE': 1582 | case 'TABLE': 1583 | $expr_type = strtolower($token->getTrim()); 1584 | break; 1585 | 1586 | case 'IF': 1587 | $warning = false; 1588 | $skip = true; 1589 | break; 1590 | 1591 | case 'TEMPORARY': 1592 | $expr_type = \PHPSQL\Expression\Type::TEMPORARY_TABLE; 1593 | $skip = true; 1594 | break; 1595 | 1596 | case 'RESTRICT': 1597 | case 'CASCADE': 1598 | $option = $token->getUpper(); 1599 | break; 1600 | 1601 | case ',': 1602 | $resultList[] = array('expr_type' => $expr_type, 'base_expr' => $base_expr); 1603 | $base_expr = ""; 1604 | break; 1605 | 1606 | default: 1607 | $base_expr .= $token->getToken(); 1608 | } 1609 | } 1610 | 1611 | if ($base_expr !== "") { 1612 | $resultList[] = array('expr_type' => $expr_type, 'base_expr' => $base_expr); 1613 | } 1614 | 1615 | return array('option' => $option, 'warning' => $warning, 'object_list' => $resultList); 1616 | } 1617 | } -------------------------------------------------------------------------------- /src/PHPSQL/Parser/Constants.php: -------------------------------------------------------------------------------- 1 | 9 | * with contributions by Dan Vande More 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | 34 | namespace PHPSQL\Parser; 35 | 36 | class Constants { 37 | 38 | protected static $reserved = array('ABS', 'ACOS', 'ADDDATE', 'ADDTIME', 'AES_ENCRYPT', 'AES_DECRYPT', 'AGAINST', 39 | 'ASCII', 'ASIN', 'ATAN', 'AVG', 'BENCHMARK', 'BIN', 'BIT_AND', 'BIT_OR', 40 | 'BITCOUNT', 'BITLENGTH', 'CAST', 'CEILING', 'CHAR', 'CHAR_LENGTH', 41 | 'CHARACTER_LENGTH', 'CHARSET', 'COALESCE', 'COERCIBILITY', 'COLLATION', 42 | 'COMPRESS', 'CONCAT', 'CONCAT_WS', 'CONNECTION_ID', 'CONV', 'CONVERT', 43 | 'CONVERT_TZ', 'COS', 'COT', 'COUNT', 'CRC32', 'CURDATE', 'CURRENT_USER', 44 | 'CURRVAL', 'CURTIME', 'DATABASE', 'DATETIME', 'DATE_ADD', 'DATE_DIFF', 45 | 'DATE_FORMAT', 'DATE_SUB', 'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 46 | 'DAYOFYEAR', 'DECODE', 'DEFAULT', 'DEGREES', 'DES_DECRYPT', 'DES_ENCRYPT', 47 | 'ELT', 'ENCODE', 'ENCRYPT', 'EXP', 'EXPORT_SET', 'EXTRACT', 'FIELD', 48 | 'FIND_IN_SET', 'FLOOR', 'FORMAT', 'FOUND_ROWS', 'FROM_DAYS', 'FROM_UNIXTIME', 49 | 'GET_FORMAT', 'GET_LOCK', 'GROUP_CONCAT', 'GREATEST', 'HEX', 'HOUR', 'IF', 50 | 'IFNULL', 'IN', 'INET_ATON', 'INET_NTOA', 'INSERT', 'INSTR', 'INTERVAL', 51 | 'IS_FREE_LOCK', 'IS_USED_LOCK', 'LAST_DAY', 'LAST_INSERT_ID', 'LCASE', 'LEAST', 52 | 'LEFT', 'LENGTH', 'LN', 'LOAD_FILE', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCATE', 53 | 'LOG', 'LOG2', 'LOG10', 'LOWER', 'LPAD', 'LTRIM', 'MAKE_SET', 'MAKEDATE', 54 | 'MAKETIME', 'MASTER_POS_WAIT', 'MATCH', 'MAX', 'MD5', 'MICROSECOND', 'MID', 55 | 'MIN', 'MINUTE', 'MOD', 'MONTH', 'MONTHNAME', 'NEXTVAL', 'NOW', 'NULLIF', 'OCT', 56 | 'OCTET_LENGTH', 'OLD_PASSWORD', 'ORD', 'PASSWORD', 'PERIOD_ADD', 'PERIOD_DIFF', 57 | 'PI', 'POSITION', 'POW', 'POWER', 'QUARTER', 'QUOTE', 'RADIANS', 'RAND', 58 | 'RELEASE_LOCK', 'REPEAT', 'REPLACE', 'REVERSE', 'RIGHT', 'ROUND', 'ROW_COUNT', 59 | 'RPAD', 'RTRIM', 'SEC_TO_TIME', 'SECOND', 'SESSION_USER', 'SHA', 'SHA1', 'SIGN', 60 | 'SOUNDEX', 'SPACE', 'SQRT', 'STD', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP', 61 | 'STRCMP', 'STR_TO_DATE', 'SUBDATE', 'SUBSTRING', 'SUBSTRING_INDEX', 'SUBTIME', 62 | 'SUM', 'SYSDATE', 'SYSTEM_USER', 'TAN', 'TIME', 'TIMEDIFF', 'TIMESTAMP', 63 | 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TIME_FORMAT', 'TIME_TO_SEC', 'TO_DAYS', 64 | 'TRIM', 'TRUNCATE', 'UCASE', 'UNCOMPRESS', 'UNCOMPRESSED_LENGTH', 'UNHEX', 65 | 'UNIX_TIMESTAMP', 'UPPER', 'USER', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 66 | 'UUID', 'VAR_POP', 'VAR_SAMP', 'VARIANCE', 'VERSION', 'WEEK', 'WEEKDAY', 67 | 'WEEKOFYEAR', 'YEAR', 'YEARWEEK', 'ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 68 | 'ASC', 'ASENSITIVE', 'AUTO_INCREMENT', 'BDB', 'BEFORE', 'BERKELEYDB', 'BETWEEN', 69 | 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CHANGE', 70 | 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'COLUMNS', 'CONDITION', 71 | 'CONNECTION', 'CONSTRAINT', 'CONTINUE', 'CREATE', 'CROSS', 'CURRENT_DATE', 72 | 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURSOR', 'DATABASE', 'DATABASES', 73 | 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 74 | 'DECLARE', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 75 | 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'ELSE', 'ELSEIF', 'END', 76 | 'ENCLOSED', 'ESCAPED', 'EXISTS', 'EXIT', 'EXPLAIN', 'FALSE', 'FETCH', 'FIELDS', 77 | 'FLOAT', 'FOR', 'FORCE', 'FOREIGN', 'FOUND', 'FRAC_SECOND', 'FROM', 'FULLTEXT', 78 | 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 79 | 'HOUR_SECOND', 'IF', 'IGNORE', 'IN', 'INDEX', 'INFILE', 'INNER', 'INNODB', 80 | 'INOUT', 'INSENSITIVE', 'INSERT', 'INT', 'INTEGER', 'INTERVAL', 'INTO', 81 | 'IO_THREAD', 'IS', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 82 | 'LEFT', 'LIKE', 'LIMIT', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 83 | 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_SERVER_ID', 84 | 'MATCH', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 85 | 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'NATURAL', 'NOT', 86 | 'NO_WRITE_TO_BINLOG', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTION', 87 | 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'PRECISION', 'PRIMARY', 88 | 'PRIVILEGES', 'PROCEDURE', 'PURGE', 'READ', 'REAL', 'REFERENCES', 'REGEXP', 89 | 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESTRICT', 'RETURN', 'REVOKE', 90 | 'RIGHT', 'RLIKE', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 91 | 'SET', 'SHOW', 'SMALLINT', 'SOME', 'SONAME', 'SPATIAL', 'SPECIFIC', 'SQL', 92 | 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SQL_BIG_RESULT', 93 | 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SQL_TSI_DAY', 'SQL_TSI_FRAC_SECOND', 94 | 'SQL_TSI_HOUR', 'SQL_TSI_MINUTE', 'SQL_TSI_MONTH', 'SQL_TSI_QUARTER', 95 | 'SQL_TSI_SECOND', 'SQL_TSI_WEEK', 'SQL_TSI_YEAR', 'SSL', 'STARTING', 96 | 'STRAIGHT_JOIN', 'STRIPED', 'TABLE', 'TABLES', 'TERMINATED', 'THEN', 97 | 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 98 | 'TRAILING', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 99 | 'USAGE', 'USE', 'USER_RESOURCES', 'USING', 'UTC_DATE', 'UTC_TIME', 100 | 'UTC_TIMESTAMP', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 101 | 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL'); 102 | 103 | protected static $parameterizedFunctions = array('ABS', 'ACOS', 'ADDDATE', 'ADDTIME', 'AES_ENCRYPT', 'AES_DECRYPT', 104 | 'AGAINST', 'ASCII', 'ASIN', 'ATAN', 'AVG', 'BENCHMARK', 'BIN', 105 | 'BIT_AND', 'BIT_OR', 'BITCOUNT', 'BITLENGTH', 'CAST', 'CEILING', 106 | 'CHAR', 'CHAR_LENGTH', 'CHARACTER_LENGTH', 'CHARSET', 'COALESCE', 107 | 'COERCIBILITY', 'COLLATION', 'COMPRESS', 'CONCAT', 'CONCAT_WS', 108 | 'CONV', 'CONVERT', 'CONVERT_TZ', 'COS', 'COT', 'COUNT', 'CRC32', 109 | 'CURRVAL', 'DATE_ADD', 'DATE_DIFF', 'DATE_FORMAT', 'DATE_SUB', 110 | 'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 'DAYOFYEAR', 111 | 'DECODE', 'DEFAULT', 'DEGREES', 'DES_DECRYPT', 'DES_ENCRYPT', 112 | 'ELT', 'ENCODE', 'ENCRYPT', 'EXP', 'EXPORT_SET', 'EXTRACT', 113 | 'FIELD', 'FIND_IN_SET', 'FLOOR', 'FORMAT', 'FROM_DAYS', 114 | 'FROM_UNIXTIME', 'GET_FORMAT', 'GET_LOCK', 'GROUP_CONCAT', 115 | 'GREATEST', 'HEX', 'HOUR', 'IF', 'IFNULL', 'IN', 'INET_ATON', 116 | 'INET_NTOA', 'INSERT', 'INSTR', 'INTERVAL', 'IS_FREE_LOCK', 117 | 'IS_USED_LOCK', 'LAST_DAY', 'LCASE', 'LEAST', 'LEFT', 'LENGTH', 118 | 'LN', 'LOAD_FILE', 'LOCATE', 'LOG', 'LOG2', 'LOG10', 'LOWER', 119 | 'LPAD', 'LTRIM', 'MAKE_SET', 'MAKEDATE', 'MAKETIME', 120 | 'MASTER_POS_WAIT', 'MATCH', 'MAX', 'MD5', 'MICROSECOND', 'MID', 121 | 'MIN', 'MINUTE', 'MOD', 'MONTH', 'MONTHNAME', 'NEXTVAL', 'NULLIF', 122 | 'OCT', 'OCTET_LENGTH', 'OLD_PASSWORD', 'ORD', 'PASSWORD', 123 | 'PERIOD_ADD', 'PERIOD_DIFF', 'PI', 'POSITION', 'POW', 'POWER', 124 | 'QUARTER', 'QUOTE', 'RADIANS', 'RELEASE_LOCK', 'REPEAT', 125 | 'REPLACE', 'REVERSE', 'RIGHT', 'ROUND', 'RPAD', 'RTRIM', 126 | 'SEC_TO_TIME', 'SECOND', 'SHA', 'SHA1', 'SIGN', 'SOUNDEX', 127 | 'SPACE', 'SQRT', 'STD', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP', 128 | 'STRCMP', 'STR_TO_DATE', 'SUBDATE', 'SUBSTRING', 129 | 'SUBSTRING_INDEX', 'SUBTIME', 'SUM', 'TAN', 'TIME', 'TIMEDIFF', 130 | 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TIME_FORMAT', 131 | 'TIME_TO_SEC', 'TO_DAYS', 'TRIM', 'TRUNCATE', 'UCASE', 132 | 'UNCOMPRESS', 'UNCOMPRESSED_LENGTH', 'UNHEX', 'UPPER', 'VAR_POP', 133 | 'VAR_SAMP', 'VARIANCE', 'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'YEAR', 134 | 'YEARWEEK'); 135 | 136 | protected static $functions = array('ABS', 'ACOS', 'ADDDATE', 'ADDTIME', 'AES_ENCRYPT', 'AES_DECRYPT', 'AGAINST', 137 | 'ASCII', 'ASIN', 'ATAN', 'AVG', 'BENCHMARK', 'BIN', 'BIT_AND', 'BIT_OR', 138 | 'BITCOUNT', 'BITLENGTH', 'CAST', 'CEILING', 'CHAR', 'CHAR_LENGTH', 139 | 'CHARACTER_LENGTH', 'CHARSET', 'COALESCE', 'COERCIBILITY', 'COLLATION', 140 | 'COMPRESS', 'CONCAT', 'CONCAT_WS', 'CONNECTION_ID', 'CONV', 'CONVERT', 141 | 'CONVERT_TZ', 'COS', 'COT', 'COUNT', 'CRC32', 'CURDATE', 'CURRENT_USER', 142 | 'CURRVAL', 'CURTIME', 'DATABASE', 'DATE_ADD', 'DATE_DIFF', 'DATE_FORMAT', 143 | 'DATE_SUB', 'DAY', 'DAYNAME', 'DAYOFMONTH', 'DAYOFWEEK', 'DAYOFYEAR', 'DECODE', 144 | 'DEFAULT', 'DEGREES', 'DES_DECRYPT', 'DES_ENCRYPT', 'ELT', 'ENCODE', 'ENCRYPT', 145 | 'EXP', 'EXPORT_SET', 'EXTRACT', 'FIELD', 'FIND_IN_SET', 'FLOOR', 'FORMAT', 146 | 'FOUND_ROWS', 'FROM_DAYS', 'FROM_UNIXTIME', 'GET_FORMAT', 'GET_LOCK', 147 | 'GROUP_CONCAT', 'GREATEST', 'HEX', 'HOUR', 'IF', 'IFNULL', 'IN', 'INET_ATON', 148 | 'INET_NTOA', 'INSERT', 'INSTR', 'INTERVAL', 'IS_FREE_LOCK', 'IS_USED_LOCK', 149 | 'LAST_DAY', 'LAST_INSERT_ID', 'LCASE', 'LEAST', 'LEFT', 'LENGTH', 'LN', 150 | 'LOAD_FILE', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCATE', 'LOG', 'LOG2', 'LOG10', 151 | 'LOWER', 'LPAD', 'LTRIM', 'MAKE_SET', 'MAKEDATE', 'MAKETIME', 152 | 'MASTER_POS_WAIT', 'MATCH', 'MAX', 'MD5', 'MICROSECOND', 'MID', 'MIN', 153 | 'MINUTE', 'MOD', 'MONTH', 'MONTHNAME', 'NEXTVAL', 'NOW', 'NULLIF', 'OCT', 154 | 'OCTET_LENGTH', 'OLD_PASSWORD', 'ORD', 'PASSWORD', 'PERIOD_ADD', 'PERIOD_DIFF', 155 | 'PI', 'POSITION', 'POW', 'POWER', 'QUARTER', 'QUOTE', 'RADIANS', 'RAND', 156 | 'RELEASE_LOCK', 'REPEAT', 'REPLACE', 'REVERSE', 'RIGHT', 'ROUND', 'ROW_COUNT', 157 | 'RPAD', 'RTRIM', 'SEC_TO_TIME', 'SECOND', 'SESSION_USER', 'SHA', 'SHA1', 158 | 'SIGN', 'SOUNDEX', 'SPACE', 'SQRT', 'STD', 'STDDEV', 'STDDEV_POP', 159 | 'STDDEV_SAMP', 'STRCMP', 'STR_TO_DATE', 'SUBDATE', 'SUBSTRING', 160 | 'SUBSTRING_INDEX', 'SUBTIME', 'SUM', 'SYSDATE', 'SYSTEM_USER', 'TAN', 'TIME', 161 | 'TIMEDIFF', 'TIMESTAMP', 'TIMESTAMPADD', 'TIMESTAMPDIFF', 'TIME_FORMAT', 162 | 'TIME_TO_SEC', 'TO_DAYS', 'TRIM', 'TRUNCATE', 'UCASE', 'UNCOMPRESS', 163 | 'UNCOMPRESSED_LENGTH', 'UNHEX', 'UNIX_TIMESTAMP', 'UPPER', 'USER', 'UTC_DATE', 164 | 'UTC_TIME', 'UTC_TIMESTAMP', 'UUID', 'VAR_POP', 'VAR_SAMP', 'VARIANCE', 165 | 'VERSION', 'WEEK', 'WEEKDAY', 'WEEKOFYEAR', 'YEAR', 'YEARWEEK'); 166 | 167 | protected static $aggregateFunctions = array('AVG', 'SUM', 'COUNT', 'MIN', 'MAX', 'STDDEV', 'STDDEV_SAMP', 168 | 'STDDEV_POP', 'VARIANCE', 'VAR_SAMP', 'VAR_POP', 'GROUP_CONCAT', 169 | 'BIT_AND', 'BIT_OR', 'BIT_XOR'); 170 | } 171 | -------------------------------------------------------------------------------- /src/PHPSQL/Parser/Lexer.php: -------------------------------------------------------------------------------- 1 | 9 | * with contributions by Dan Vande More 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | 34 | namespace PHPSQL\Parser; 35 | 36 | /** 37 | * This class splits the SQL string into little parts, which the parser can 38 | * use to build the result array. 39 | * 40 | * @author arothe 41 | * 42 | */ 43 | class Lexer extends \PHPSQL\Parser\Utils { 44 | 45 | private $splitters; 46 | 47 | public function __construct() { 48 | $this->splitters = new \PHPSQL\Parser\Lexer\Splitter(); 49 | } 50 | 51 | public function split($sql) { 52 | if (!is_string($sql)) { 53 | throw new \PHPSQL\Exception\InvalidParameter($sql); 54 | } 55 | 56 | $tokens = array(); 57 | $token = ""; 58 | 59 | $splitLen = $this->splitters->getMaxLengthOfSplitter(); 60 | $found = false; 61 | $len = strlen($sql); 62 | $pos = 0; 63 | 64 | while ($pos < $len) { 65 | 66 | for ($i = $splitLen; $i > 0; $i--) { 67 | $substr = substr($sql, $pos, $i); 68 | if ($this->splitters->isSplitter($substr)) { 69 | 70 | if ($token !== "") { 71 | $tokens[] = $token; 72 | } 73 | 74 | $tokens[] = $substr; 75 | $pos += $i; 76 | $token = ""; 77 | 78 | continue 2; 79 | } 80 | } 81 | 82 | $token .= $sql[$pos]; 83 | $pos++; 84 | } 85 | 86 | if ($token !== "") { 87 | $tokens[] = $token; 88 | } 89 | 90 | $tokens = $this->concatEscapeSequences($tokens); 91 | $tokens = $this->balanceBackticks($tokens); 92 | $tokens = $this->concatColReferences($tokens); 93 | $tokens = $this->balanceParenthesis($tokens); 94 | $tokens = $this->balanceMultilineComments($tokens); 95 | $tokens = $this->concatInlineComments($tokens); 96 | $tokens = $this->concatUserDefinedVariables($tokens); 97 | return $tokens; 98 | } 99 | 100 | private function concatUserDefinedVariables($tokens) { 101 | $i = 0; 102 | $cnt = count($tokens); 103 | $userdef = false; 104 | 105 | while ($i < $cnt) { 106 | 107 | if (!isset($tokens[$i])) { 108 | $i++; 109 | continue; 110 | } 111 | 112 | $token = $tokens[$i]; 113 | 114 | if ($userdef !== false) { 115 | $tokens[$userdef] .= $token; 116 | unset($tokens[$i]); 117 | if ($token !== "@") { 118 | $userdef = false; 119 | } 120 | } 121 | 122 | if ($userdef === false && $token === "@") { 123 | $userdef = $i; 124 | } 125 | 126 | $i++; 127 | } 128 | 129 | return array_values($tokens); 130 | } 131 | 132 | private function concatInlineComments($tokens) { 133 | 134 | $i = 0; 135 | $cnt = count($tokens); 136 | $comment = false; 137 | 138 | while ($i < $cnt) { 139 | 140 | if (!isset($tokens[$i])) { 141 | $i++; 142 | continue; 143 | } 144 | 145 | $token = $tokens[$i]; 146 | 147 | if ($comment !== false) { 148 | if ($token === "\n" || $token === "\r\n") { 149 | $comment = false; 150 | } else { 151 | unset($tokens[$i]); 152 | $tokens[$comment] .= $token; 153 | } 154 | } 155 | 156 | if (($comment === false) && ($token === "-")) { 157 | if (isset($tokens[$i + 1]) && $tokens[$i + 1] === "-") { 158 | $comment = $i; 159 | $tokens[$i] = "--"; 160 | $i++; 161 | unset($tokens[$i]); 162 | continue; 163 | } 164 | } 165 | 166 | $i++; 167 | } 168 | 169 | return array_values($tokens); 170 | } 171 | 172 | private function balanceMultilineComments($tokens) { 173 | 174 | $i = 0; 175 | $cnt = count($tokens); 176 | $comment = false; 177 | 178 | while ($i < $cnt) { 179 | 180 | if (!isset($tokens[$i])) { 181 | $i++; 182 | continue; 183 | } 184 | 185 | $token = $tokens[$i]; 186 | 187 | if ($comment !== false) { 188 | unset($tokens[$i]); 189 | $tokens[$comment] .= $token; 190 | if ($token === "*" && isset($tokens[$i + 1]) && $tokens[$i + 1] === "/") { 191 | unset($tokens[$i + 1]); 192 | $tokens[$comment] .= "/"; 193 | $comment = false; 194 | } 195 | } 196 | 197 | if (($comment === false) && ($token === "/")) { 198 | if (isset($tokens[$i + 1]) && $tokens[$i + 1] === "*") { 199 | $comment = $i; 200 | $tokens[$i] = "/*"; 201 | $i++; 202 | unset($tokens[$i]); 203 | continue; 204 | } 205 | } 206 | 207 | $i++; 208 | } 209 | return array_values($tokens); 210 | } 211 | 212 | private function isBacktick($token) { 213 | return ($token === "'" || $token === "\"" || $token === "`"); 214 | } 215 | 216 | private function balanceBackticks($tokens) { 217 | $i = 0; 218 | $cnt = count($tokens); 219 | while ($i < $cnt) { 220 | 221 | if (!isset($tokens[$i])) { 222 | $i++; 223 | continue; 224 | } 225 | 226 | $token = $tokens[$i]; 227 | 228 | if ($this->isBacktick($token)) { 229 | $tokens = $this->balanceCharacter($tokens, $i, $token); 230 | } 231 | 232 | $i++; 233 | } 234 | 235 | return $tokens; 236 | } 237 | 238 | # backticks are not balanced within one token, so we have 239 | # to re-combine some tokens 240 | private function balanceCharacter($tokens, $idx, $char) { 241 | 242 | $token_count = count($tokens); 243 | $i = $idx + 1; 244 | while ($i < $token_count) { 245 | 246 | if (!isset($tokens[$i])) { 247 | $i++; 248 | continue; 249 | } 250 | 251 | $token = $tokens[$i]; 252 | $tokens[$idx] .= $token; 253 | unset($tokens[$i]); 254 | 255 | if ($token === $char) { 256 | break; 257 | } 258 | 259 | $i++; 260 | } 261 | return array_values($tokens); 262 | } 263 | 264 | /* 265 | * does the token ends with dot? 266 | * concat it with the next token 267 | * 268 | * does the token starts with a dot? 269 | * concat it with the previous token 270 | */ 271 | private function concatColReferences($tokens) { 272 | 273 | $cnt = count($tokens); 274 | $i = 0; 275 | while ($i < $cnt) { 276 | 277 | if (!isset($tokens[$i])) { 278 | $i++; 279 | continue; 280 | } 281 | 282 | if ($tokens[$i][0] === ".") { 283 | 284 | // concat the previous tokens, till the token has been changed 285 | $k = $i - 1; 286 | $len = strlen($tokens[$i]); 287 | while (($k >= 0) && ($len == strlen($tokens[$i]))) { 288 | if (!isset($tokens[$k])) { # FIXME: this can be wrong if we have schema . table . column 289 | $k--; 290 | continue; 291 | } 292 | $tokens[$i] = $tokens[$k] . $tokens[$i]; 293 | unset($tokens[$k]); 294 | $k--; 295 | } 296 | } 297 | 298 | if ($this->endsWith($tokens[$i], '.')) { 299 | 300 | // concat the next tokens, till the token has been changed 301 | $k = $i + 1; 302 | $len = strlen($tokens[$i]); 303 | while (($k < $cnt) && ($len == strlen($tokens[$i]))) { 304 | if (!isset($tokens[$k])) { 305 | $k++; 306 | continue; 307 | } 308 | $tokens[$i] .= $tokens[$k]; 309 | unset($tokens[$k]); 310 | $k++; 311 | } 312 | } 313 | 314 | $i++; 315 | } 316 | 317 | return array_values($tokens); 318 | } 319 | 320 | private function concatEscapeSequences($tokens) { 321 | $tokenCount = count($tokens); 322 | $i = 0; 323 | while ($i < $tokenCount) { 324 | 325 | if ($this->endsWith($tokens[$i], "\\")) { 326 | $i++; 327 | if (isset($tokens[$i])) { 328 | $tokens[$i - 1] .= $tokens[$i]; 329 | unset($tokens[$i]); 330 | } 331 | } 332 | $i++; 333 | } 334 | return array_values($tokens); 335 | } 336 | 337 | private function balanceParenthesis($tokens) { 338 | $token_count = count($tokens); 339 | $i = 0; 340 | while ($i < $token_count) { 341 | if ($tokens[$i] !== '(') { 342 | $i++; 343 | continue; 344 | } 345 | $count = 1; 346 | for ($n = $i + 1; $n < $token_count; $n++) { 347 | $token = $tokens[$n]; 348 | if ($token === '(') { 349 | $count++; 350 | } 351 | if ($token === ')') { 352 | $count--; 353 | } 354 | $tokens[$i] .= $token; 355 | unset($tokens[$n]); 356 | if ($count === 0) { 357 | $n++; 358 | break; 359 | } 360 | } 361 | $i = $n; 362 | } 363 | return array_values($tokens); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/PHPSQL/Parser/Lexer/Splitter.php: -------------------------------------------------------------------------------- 1 | 10 | * with contributions by Dan Vande More 11 | * 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without modification, 15 | * are permitted provided that the following conditions are met: 16 | * 17 | * * Redistributions of source code must retain the above copyright notice, 18 | * this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above copyright notice, 20 | * this list of conditions and the following disclaimer in the documentation 21 | * and/or other materials provided with the distribution. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 24 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 26 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 29 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 32 | * DAMAGE. 33 | */ 34 | 35 | namespace PHPSQL\Parser\Lexer; 36 | 37 | class Splitter { 38 | 39 | private static $splitters = array("\r\n", "!=", ">=", "<=", "<>", ":=", "\\", "&&", ">", "<", "|", "=", "^", "(", 40 | ")", "\t", "\n", "'", "\"", "`", ",", "@", " ", "+", "-", "*", "/", ";"); 41 | private $tokenSize; 42 | private $hashSet; 43 | 44 | public function __construct() { 45 | $this->tokenSize = strlen(self::$splitters[0]); # should be the largest one 46 | $this->hashSet = array_flip(self::$splitters); 47 | } 48 | 49 | public function getMaxLengthOfSplitter() { 50 | return $this->tokenSize; 51 | } 52 | 53 | public function isSplitter($token) { 54 | return isset($this->hashSet[$token]); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/PHPSQL/Parser/PositionCalculator.php: -------------------------------------------------------------------------------- 1 | 10 | * with contributions by Dan Vande More 11 | * 12 | * All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without modification, 15 | * are permitted provided that the following conditions are met: 16 | * 17 | * * Redistributions of source code must retain the above copyright notice, 18 | * this list of conditions and the following disclaimer. 19 | * * Redistributions in binary form must reproduce the above copyright notice, 20 | * this list of conditions and the following disclaimer in the documentation 21 | * and/or other materials provided with the distribution. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 24 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 26 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 28 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 29 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 32 | * DAMAGE. 33 | */ 34 | 35 | namespace PHPSQL\Parser; 36 | 37 | /** 38 | * 39 | * This class calculates the positions 40 | * of base_expr within the original SQL statement. 41 | * 42 | * @author arothe 43 | * 44 | */ 45 | class PositionCalculator extends \PHPSQL\Parser\Utils { 46 | 47 | private static $allowedOnOperator = array("\t", "\n", "\r", " ", ",", "(", ")", "_", "'", "\""); 48 | private static $allowedOnOther = array("\t", "\n", "\r", " ", ",", "(", ")", "<", ">", "*", "+", "-", "/", "|", 49 | "&", "=", "!", ";"); 50 | 51 | private function printPos($text, $sql, $charPos, $key, $parsed, $backtracking) { 52 | if (!isset($_ENV['DEBUG'])) { 53 | return; 54 | } 55 | 56 | $spaces = ""; 57 | $caller = debug_backtrace(); 58 | $i = 1; 59 | while ($caller[$i]['function'] === 'lookForBaseExpression') { 60 | $spaces .= " "; 61 | $i++; 62 | } 63 | $holdem = substr($sql, 0, $charPos) . "^" . substr($sql, $charPos); 64 | echo $spaces . $text . " key:" . $key . " parsed:" . $parsed . " back:" . serialize($backtracking) . " " 65 | . $holdem . "\n"; 66 | } 67 | 68 | public function setPositionsWithinSQL($sql, $parsed) { 69 | $charPos = 0; 70 | $backtracking = array(); 71 | $this->lookForBaseExpression($sql, $charPos, $parsed, 0, $backtracking); 72 | return $parsed; 73 | } 74 | 75 | private function findPositionWithinString($sql, $value, $expr_type) { 76 | 77 | $offset = 0; 78 | $ok = false; 79 | $pos = false; 80 | while (true) { 81 | 82 | $pos = strpos($sql, $value, $offset); 83 | if ($pos === false) { 84 | break; 85 | } 86 | 87 | $before = ""; 88 | if ($pos > 0) { 89 | $before = $sql[$pos - 1]; 90 | } 91 | 92 | $after = ""; 93 | if (isset($sql[$pos + strlen($value)])) { 94 | $after = $sql[$pos + strlen($value)]; 95 | } 96 | 97 | # if we have an operator, it should be surrounded by 98 | # whitespace, comma, parenthesis, digit or letter, end_of_string 99 | # an operator should not be surrounded by another operator 100 | 101 | if ($expr_type === 'operator') { 102 | 103 | $ok = ($before === "" || in_array($before, self::$allowedOnOperator, true)) 104 | || (strtolower($before) >= 'a' && strtolower($before) <= 'z') 105 | || ($before >= '0' && $before <= '9'); 106 | $ok = $ok 107 | && ($after === "" || in_array($after, self::$allowedOnOperator, true) 108 | || (strtolower($after) >= 'a' && strtolower($after) <= 'z') 109 | || ($after >= '0' && $after <= '9') || ($after === '?') || ($after === '@')); 110 | 111 | if (!$ok) { 112 | $offset = $pos + 1; 113 | continue; 114 | } 115 | 116 | break; 117 | } 118 | 119 | # in all other cases we accept 120 | # whitespace, comma, operators, parenthesis and end_of_string 121 | 122 | $ok = ($before === "" || in_array($before, self::$allowedOnOther, true)); 123 | $ok = $ok && ($after === "" || in_array($after, self::$allowedOnOther, true)); 124 | 125 | if ($ok) { 126 | break; 127 | } 128 | 129 | $offset = $pos + 1; 130 | } 131 | 132 | return $pos; 133 | } 134 | 135 | private function lookForBaseExpression($sql, &$charPos, &$parsed, $key, &$backtracking) { 136 | if (!is_numeric($key)) { 137 | if (($key === 'UNION' || $key === 'UNION ALL') 138 | || ($key === 'expr_type' && $parsed === \PHPSQL\Expression\Type::EXPRESSION) 139 | || ($key === 'expr_type' && $parsed === \PHPSQL\Expression\Type::SUBQUERY) 140 | || ($key === 'expr_type' && $parsed === \PHPSQL\Expression\Type::BRACKET_EXPRESSION) 141 | || ($key === 'expr_type' && $parsed === \PHPSQL\Expression\Type::TABLE_EXPRESSION) 142 | || ($key === 'expr_type' && $parsed === \PHPSQL\Expression\Type::RECORD) 143 | || ($key === 'expr_type' && $parsed === \PHPSQL\Expression\Type::IN_LIST) 144 | || ($key === 'expr_type' && $parsed === \PHPSQL\Expression\Type::MATCH_ARGUMENTS) 145 | || ($key === 'alias' && $parsed !== false)) { 146 | # we hold the current position and come back after the next base_expr 147 | # we do this, because the next base_expr contains the complete expression/subquery/record 148 | # and we have to look into it too 149 | $backtracking[] = $charPos; 150 | 151 | } elseif (($key === 'ref_clause' || $key === 'columns') && $parsed !== false) { 152 | # we hold the current position and come back after n base_expr(s) 153 | # there is an array of sub-elements before (!) the base_expr clause of the current element 154 | # so we go through the sub-elements and must come at the end 155 | $backtracking[] = $charPos; 156 | for ($i = 1; $i < count($parsed); $i++) { 157 | $backtracking[] = false; # backtracking only after n base_expr! 158 | } 159 | } elseif ($key === 'sub_tree' && $parsed !== false) { 160 | # we prevent wrong backtracking on subtrees (too much array_pop()) 161 | # there is an array of sub-elements after(!) the base_expr clause of the current element 162 | # so we go through the sub-elements and must not come back at the end 163 | for ($i = 1; $i < count($parsed); $i++) { 164 | $backtracking[] = false; 165 | } 166 | } else { 167 | # move the current pos after the keyword 168 | # SELECT, WHERE, INSERT etc. 169 | if (in_array($key, parent::$reserved)) { 170 | $charPos = stripos($sql, $key, $charPos); 171 | $charPos += strlen($key); 172 | } 173 | } 174 | } 175 | 176 | if (!is_array($parsed)) { 177 | return; 178 | } 179 | 180 | foreach ($parsed as $key => $value) { 181 | if ($key === 'base_expr') { 182 | 183 | #$this->printPos("0", $sql, $charPos, $key, $value, $backtracking); 184 | 185 | $subject = substr($sql, $charPos); 186 | $pos = $this->findPositionWithinString($subject, $value, 187 | isset($parsed['expr_type']) ? $parsed['expr_type'] : 'alias'); 188 | if ($pos === false) { 189 | throw new \PHPSQL\Exception\UnableToCalculatePosition($value, $subject); 190 | } 191 | 192 | $parsed['position'] = $charPos + $pos; 193 | $charPos += $pos + strlen($value); 194 | 195 | #$this->printPos("1", $sql, $charPos, $key, $value, $backtracking); 196 | 197 | $oldPos = array_pop($backtracking); 198 | if (isset($oldPos) && $oldPos !== false) { 199 | $charPos = $oldPos; 200 | } 201 | 202 | #$this->printPos("2", $sql, $charPos, $key, $value, $backtracking); 203 | 204 | } else { 205 | $this->lookForBaseExpression($sql, $charPos, $parsed[$key], $key, $backtracking); 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/PHPSQL/Parser/Utils.php: -------------------------------------------------------------------------------- 1 | 9 | * with contributions by Dan Vande More 10 | * 11 | * All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 16 | * * Redistributions of source code must retain the above copyright notice, 17 | * this list of conditions and the following disclaimer. 18 | * * Redistributions in binary form must reproduce the above copyright notice, 19 | * this list of conditions and the following disclaimer in the documentation 20 | * and/or other materials provided with the distribution. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 23 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 25 | * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 28 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 31 | * DAMAGE. 32 | */ 33 | 34 | namespace PHPSQL\Parser; 35 | 36 | /** 37 | * This class implements some helper functions. 38 | * @author arothe 39 | * 40 | */ 41 | class Utils extends \PHPSQL\Parser\Constants { 42 | 43 | /** 44 | * Ends the given string $haystack with the string $needle? 45 | * @param string $haystack 46 | * @param string $needle 47 | * @return bool $in 48 | */ 49 | protected function endsWith($haystack, $needle) { 50 | $length = strlen($needle); 51 | if ($length == 0) { 52 | return true; 53 | } 54 | 55 | $start = $length * -1; 56 | return (substr($haystack, $start) === $needle); 57 | } 58 | 59 | /** 60 | * Revokes the escaping characters from an expression 61 | */ 62 | protected function revokeEscaping($sql) { 63 | $result = trim($sql); 64 | if (($result[0] === '`') && ($result[strlen($result) - 1] === '`')) { 65 | $result = substr($result, 1, -1); 66 | } 67 | return str_replace('``', '`', $result); 68 | } 69 | 70 | /** 71 | * This method removes parenthesis from start of the given string. 72 | * It removes also the associated closing parenthesis. 73 | */ 74 | protected function removeParenthesisFromStart($token) { 75 | 76 | $parenthesisRemoved = 0; 77 | 78 | $trim = trim($token); 79 | if ($trim !== "" && $trim[0] === "(") { // remove only one parenthesis pair now! 80 | $parenthesisRemoved++; 81 | $trim[0] = " "; 82 | $trim = trim($trim); 83 | } 84 | 85 | $parenthesis = $parenthesisRemoved; 86 | $i = 0; 87 | $string = 0; 88 | while ($i < strlen($trim)) { 89 | 90 | if ($trim[$i] === "\\") { 91 | $i += 2; # an escape character, the next character is irrelevant 92 | continue; 93 | } 94 | 95 | if ($trim[$i] === "'" || $trim[$i] === '"') { 96 | $string++; 97 | } 98 | 99 | if (($string % 2 === 0) && ($trim[$i] === "(")) { 100 | $parenthesis++; 101 | } 102 | 103 | if (($string % 2 === 0) && ($trim[$i] === ")")) { 104 | if ($parenthesis == $parenthesisRemoved) { 105 | $trim[$i] = " "; 106 | $parenthesisRemoved--; 107 | } 108 | $parenthesis--; 109 | } 110 | $i++; 111 | } 112 | return trim($trim); 113 | } 114 | 115 | public function getLastOf($array) { 116 | // $array is a copy of the original array, so we can change it without sideeffects 117 | if (!is_array($array)) { 118 | return false; 119 | } 120 | return array_pop($array); 121 | } 122 | 123 | /** 124 | * translates an array of objects into an associative array 125 | */ 126 | public function toArray($tokenList) { 127 | $expr = array(); 128 | foreach ($tokenList as $token) { 129 | /** 130 | * @var $token \PHPSQL\Expression\Token 131 | */ 132 | $expr[] = $token->toArray(); 133 | } 134 | return (empty($expr) ? false : $expr); 135 | } 136 | } 137 | --------------------------------------------------------------------------------