%PDF- %PDF-
| Direktori : /home/bitrix/www/bitrix/modules/perfmon/classes/general/ |
| Current File : /home/bitrix/www/bitrix/modules/perfmon/classes/general/sql_format.php |
<?php
if (!defined("T_KEYWORD"))
define("T_KEYWORD", 400);
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 ($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", array($this, "removeSpaces"), $result);
$result = str_replace(array(
"\x1",
"\x2",
"\x3",
), array(
$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;
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 = array($token[0], $token[1]);
else
$token = array(T_CHARACTER, $token[0]);
switch ($token[0])
{
case T_STRING:
if (preg_match("/^($keywords)\$/i", $token[1]))
$token = array(T_KEYWORD, strtoupper($token[1]));
elseif (preg_match("/^($functions)\$/i", $token[1]))
$token = array(T_FUNCTION, $token[1]);
break;
case T_LOGICAL_AND:
case T_LOGICAL_OR:
$token = array(T_KEYWORD, strtoupper($token[1]));
break;
case T_AS:
$token = array(T_KEYWORD, $token[1]);
break;
case T_COMMENT:
case T_BAD_CHARACTER:
$token = array(T_WHITESPACE, " ");
break;
}
return $token;
}
protected function removeBalancedBrackets()
{
$pos = $this->current;
$balance = 0;
$hasOp = array(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 = array();
private $balance = 0;
private $level = 0;
private $current = 0;
function addLevel(array $tokens)
{
$this->level = array();
$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] === "(")
{
$this->level["("]++;
}
elseif (
(
$this->tokens[$this->current][1] === "SELECT"
&& !$this->lookForwardFor("DISTINCT")
) || (
$this->tokens[$this->current][1] === "DISTINCT"
)
)
{
$this->level["SELECT_".$this->balance]++;
}
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"
)
{
$this->level["SELECT_".$this->balance]++;
}
elseif ($this->tokens[$this->current][1] === "ON")
{
$this->level["JOIN_".$this->balance]++;
}
}
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? $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);
}
}