Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/perfmon/classes/general/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/perfmon/classes/general/sql_format.php |
<?php if (!defined('T_KEYWORD')) { define('T_KEYWORD', 400); } if (!defined('T_BAD_CHARACTER')) { define('T_BAD_CHARACTER', 401); } if (!defined('T_CHARACTER')) { define('T_CHARACTER', 402); } class CSqlFormatFormatter { public function format($tokens) { $result = ''; $skipWS = false; foreach ($tokens as $i => $token) { if ($token[1] === ',') { $this->removeTrailingSpaces($result); $result .= ",\x3" . str_repeat("\x2", $token[2]); $skipWS = true; } elseif ( $token[1] === '=' || $token[1] === '-' || $token[1] === '+' || $token[1] === '*' || $token[1] === '/' || $token[1] === '<=' || $token[1] === '>=' ) { $this->removeTrailingSpaces($result); $result .= "\x1" . $token[1] . "\x1"; $skipWS = true; } elseif ( $token[1] === 'INNER' || $token[1] === 'LEFT' || $token[1] === 'SET' || $token[1] === 'AND' || $token[1] === 'OR' ) { $result .= "\x3" . str_repeat("\x2", $token[2]) . $token[1]; } else { if ($skipWS) { $skipWS = false; if ($token[0] === T_WHITESPACE) { continue; } } elseif ($i > 0 && ($tokens[$i - 1][2] <> $token[2])) { $result .= "\x3" . str_repeat("\x2", $token[2]); if ($token[0] === T_WHITESPACE) { continue; } } if ($token[0] === T_WHITESPACE) { $result .= "\x1"; } else { $result .= $token[1]; } } } $result = preg_replace_callback("/( \\([\\x1\\x2\\x3_0-9A-Za-z.0-9_,]+\\) |\\([\\x1\\x2\\x3'x%0-9,]+\\) |\\([\\x1\\x2\\x3]*[a-zA-Z0-9_.]+[\\x1\\x2\\x3]*=[\\x1\\x2\\x3]*[a-zA-Z0-9_.']+[\\x1\\x2\\x3]*\\) )/x", [$this, 'removeSpaces'], $result); $result = str_replace([ "\x1", "\x2", "\x3", ], [ $this->getSpace(), $this->getTab(), $this->getEol(), ], $result); return $result; } public function removeSpaces($match) { $result = preg_replace("/^\\([\\x1\\x2\\x3]+/", '(', $match[0]); $result = preg_replace("/[\\x1\\x2\\x3]+\\)\$/", ')', $result); $result = preg_replace("/,[\\x1\\x2\\x3]+/", ', ', $result); return $result; } public function removeTrailingSpaces(&$str) { $str = rtrim($str, "\x1\x2\x3"); } public function getEol() { return ' '; } public function getSpace() { return ' '; } public function getTab() { return ' '; } } class CSqlFormatText extends CSqlFormatFormatter { public function getEol() { return "\n"; } public function getSpace() { return ' '; } public function getTab() { return "\t"; } } class CSqlTokenizer { private $tokens = null; private $current = 0; public function parse($sql) { $this->tokens = token_get_all('<?' . $sql); array_shift($this->tokens); $this->current = 0; while (isset($this->tokens[$this->current])) { //Remove excessive brackets if ( $this->tokens[$this->current] === '(' && $this->lookForwardFor('(') ) { if ($this->removeBalancedBrackets()) { continue; } } //Remove following spaces if ($this->tokens[$this->current][0] === T_WHITESPACE && $this->tokens[$this->current - 1][0] === T_WHITESPACE) { array_splice($this->tokens, $this->current, 1); continue; } $this->tokens[$this->current] = $this->transform($this->tokens[$this->current]); $this->current++; } //Remove leading spaces while ( isset($this->tokens[0]) && $this->tokens[0][0] === T_WHITESPACE ) { array_splice($this->tokens, 0, 1); } //Remove trailing spaces while ( !empty($this->tokens) && $this->tokens[count($this->tokens) - 1][0] === T_WHITESPACE ) { array_splice($this->tokens, -1, 1); } return $this->tokens; } protected function transform($token) { static $keywords = 'UPDATE|SET|DELETE|SELECT|DISTINCT|INNER|LEFT|OUTER|JOIN|ON|FROM|WHERE|GROUP|BY|IN|EXISTS|HAVING|ORDER|ASC|DESC|LIMIT|AND|OR'; static $functions = 'DATE_FORMAT|UNIX_TIMESTAMP|CONCAT|DATE_ADD|UPPER|LENGTH|IFNULL'; if (isset($token[1])) { $token = [$token[0], $token[1]]; } else { $token = [T_CHARACTER, $token[0]]; } switch ($token[0]) { case T_STRING: if (preg_match("/^(${keywords})\$/i", $token[1])) { $token = [T_KEYWORD, mb_strtoupper($token[1])]; } elseif (preg_match("/^(${functions})\$/i", $token[1])) { $token = [T_FUNCTION, $token[1]]; } break; case T_LOGICAL_AND: case T_LOGICAL_OR: $token = [T_KEYWORD, mb_strtoupper($token[1])]; break; case T_AS: $token = [T_KEYWORD, $token[1]]; break; case T_COMMENT: case T_BAD_CHARACTER: $token = [T_WHITESPACE, ' ']; break; } return $token; } protected function removeBalancedBrackets() { $pos = $this->current; $balance = 0; $hasOp = [false]; while (isset($this->tokens[$pos])) { if ($this->tokens[$pos][0] === '(') { $balance++; $hasOp[$balance] = false; } elseif ($this->tokens[$pos][0] === ')') { $balance--; } elseif ( $this->tokens[$pos][0] === T_LOGICAL_AND || $this->tokens[$pos][0] === T_LOGICAL_OR || $this->tokens[$pos][0] === ',' ) { $hasOp[$balance] = true; } if ($balance === 0) { if (!$hasOp[$balance + 1]) { array_splice($this->tokens, $pos, 1); array_splice($this->tokens, $this->current, 1); return true; } else { return false; } } $pos++; } return false; } protected function lookForwardFor($token) { $pos = $this->current + 1; while (isset($this->tokens[$pos])) { if ($this->tokens[$pos] == $token) { return true; } elseif ($this->tokens[$pos][0] !== T_WHITESPACE) { return false; } $pos++; } return false; } } class CSqlLevel { private $tokens = []; private $balance = 0; private $level = 0; private $current = 0; public function addLevel(array $tokens) { $this->level = []; $this->balance = 0; $this->tokens = $tokens; $this->current = 0; while (isset($this->tokens[$this->current])) { if ($this->tokens[$this->current][1] === '(') { $this->balance++; } elseif ($this->tokens[$this->current][1] === ')') { $this->balance--; } if ($this->tokens[$this->current][0] !== T_WHITESPACE) { $this->changeLevelBefore(); } $this->tokens[$this->current][] = array_sum($this->level); if ($this->tokens[$this->current][0] !== T_WHITESPACE) { $this->changeLevelAfter(); } $this->current++; } return $this->tokens; } public function changeLevelBefore() { if ($this->tokens[$this->current][1] === ')') { $this->level['(']--; $this->level['SELECT_' . ($this->balance + 1)] = 0; $this->level['JOIN_' . ($this->balance + 1)] = 0; } elseif ( $this->tokens[$this->current][1] === 'FROM' || $this->tokens[$this->current][1] === 'LIMIT' ) { $this->level['SELECT_' . $this->balance]--; } elseif ( $this->tokens[$this->current][1] === 'WHERE' || $this->tokens[$this->current][1] === 'GROUP' || $this->tokens[$this->current][1] === 'HAVING' || $this->tokens[$this->current][1] === 'ORDER' ) { $this->level['SELECT_' . $this->balance]--; if ($this->level['JOIN_' . $this->balance] ?? 0) { $this->level['JOIN_' . $this->balance]--; } } elseif ( $this->tokens[$this->current][1] === 'INNER' || $this->tokens[$this->current][1] === 'LEFT' ) { if ($this->level['JOIN_' . $this->balance] > 0) { $this->level['JOIN_' . $this->balance]--; } } } public function changeLevelAfter() { if ($this->tokens[$this->current][1] === '(') { if (isset($this->level['('])) { $this->level['(']++; } else { $this->level['('] = 1; } } elseif ( ( $this->tokens[$this->current][1] === 'SELECT' && !$this->lookForwardFor('DISTINCT') ) || ( $this->tokens[$this->current][1] === 'DISTINCT' ) ) { if (isset($this->level['SELECT_' . $this->balance])) { $this->level['SELECT_' . $this->balance]++; } else { $this->level['SELECT_' . $this->balance] = 1; } } elseif ( $this->tokens[$this->current][1] === 'FROM' || $this->tokens[$this->current][1] === 'WHERE' || $this->tokens[$this->current][1] === 'BY' || $this->tokens[$this->current][1] === 'HAVING' || $this->tokens[$this->current][1] === 'SET' ) { if (isset($this->level['SELECT_' . $this->balance])) { $this->level['SELECT_' . $this->balance]++; } else { $this->level['SELECT_' . $this->balance] = 1; } } elseif ($this->tokens[$this->current][1] === 'ON') { if (isset($this->level['JOIN_' . $this->balance])) { $this->level['JOIN_' . $this->balance]++; } else { $this->level['JOIN_' . $this->balance] = 1; } } } protected function lookForwardFor($token) { $pos = $this->current + 1; while (isset($this->tokens[$pos])) { if ($this->tokens[$pos][1] == $token) { return true; } elseif ($this->tokens[$pos][0] !== T_WHITESPACE) { return false; } $pos++; } return false; } protected function lookBackwardFor($token) { $pos = $this->current - 1; while (isset($this->tokens[$pos])) { if ($this->tokens[$pos][1] == $token) { return true; } elseif ($this->tokens[$pos][0] !== T_WHITESPACE) { return false; } $pos--; } return false; } } class CSqlFormat { /** @var CSqlTokenizer */ private $tokenizer = null; /** @var CSqlLevel */ private $levelizer = null; private $current = null; private $level = 0; private $add = 0; private $result = ''; /** @var CSqlFormatFormatter */ private $formatter = null; public function __construct() { $this->tokenizer = new CSqlTokenizer; $this->levelizer = new CSqlLevel; $this->level = 0; $this->add = 0; $this->current = 0; $this->result = ''; } public static function reformatSql($sql, CSqlFormatFormatter $formatter = null) { if (function_exists('token_get_all')) { $format = new CSqlFormat; $format->setFormatter($formatter ?? new CSqlFormatText); return $format->format($sql); } else { return $sql; } } public function setFormatter(CSqlFormatFormatter $formatter) { $this->formatter = $formatter; } public function format($sql) { $tokens = $this->tokenizer->parse($sql); $tokens = $this->levelizer->addLevel($tokens); return $this->formatter->format($tokens); } }