SimulateDmlController.php 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Controllers\Import;
  4. use PhpMyAdmin\Controllers\AbstractController;
  5. use PhpMyAdmin\Import\SimulateDml;
  6. use PhpMyAdmin\Message;
  7. use PhpMyAdmin\ResponseRenderer;
  8. use PhpMyAdmin\SqlParser\Lexer;
  9. use PhpMyAdmin\SqlParser\Parser;
  10. use PhpMyAdmin\SqlParser\Statements\DeleteStatement;
  11. use PhpMyAdmin\SqlParser\Statements\UpdateStatement;
  12. use PhpMyAdmin\SqlParser\Token;
  13. use PhpMyAdmin\SqlParser\TokensList;
  14. use PhpMyAdmin\SqlParser\Utils\Query;
  15. use PhpMyAdmin\Template;
  16. use function __;
  17. use function array_filter;
  18. use function array_values;
  19. use function count;
  20. final class SimulateDmlController extends AbstractController
  21. {
  22. /** @var SimulateDml */
  23. private $simulateDml;
  24. /** @var string */
  25. private $error = '';
  26. /**
  27. * @var list<array<mixed>>
  28. * @psalm-var list<array{
  29. * sql_query: string,
  30. * matched_rows: int,
  31. * matched_rows_url: string,
  32. * }>
  33. */
  34. private $data = [];
  35. public function __construct(
  36. ResponseRenderer $response,
  37. Template $template,
  38. SimulateDml $simulateDml
  39. ) {
  40. parent::__construct($response, $template);
  41. $this->simulateDml = $simulateDml;
  42. }
  43. public function __invoke(): void
  44. {
  45. /** @var string $sqlDelimiter */
  46. $sqlDelimiter = $_POST['sql_delimiter'];
  47. $parser = $this->createParser($GLOBALS['sql_query'], $sqlDelimiter);
  48. $this->process($parser);
  49. if ($this->error) {
  50. $this->response->addJSON('message', Message::rawError($this->error));
  51. $this->response->addJSON('sql_data', false);
  52. return;
  53. }
  54. $this->response->addJSON('sql_data', $this->data);
  55. }
  56. private function createParser(string $query, string $delimiter): Parser
  57. {
  58. $lexer = new Lexer($query, false, $delimiter);
  59. $list = new TokensList(array_values(array_filter(
  60. $lexer->list->tokens,
  61. static function ($token): bool {
  62. return $token->type !== Token::TYPE_COMMENT;
  63. }
  64. )));
  65. return new Parser($list);
  66. }
  67. private function process(Parser $parser): void
  68. {
  69. foreach ($parser->statements as $statement) {
  70. if (
  71. ! $statement instanceof UpdateStatement && ! $statement instanceof DeleteStatement
  72. || ! empty($statement->join)
  73. || count(Query::getTables($statement)) > 1
  74. ) {
  75. $this->error = __('Only single-table UPDATE and DELETE queries can be simulated.');
  76. break;
  77. }
  78. // Get the matched rows for the query.
  79. $result = $this->simulateDml->getMatchedRows($parser, $statement);
  80. $this->error = $this->simulateDml->getError();
  81. if ($this->error !== '') {
  82. break;
  83. }
  84. $this->data[] = $result;
  85. }
  86. }
  87. }