Your IP : 3.15.144.5


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/perfmon/classes/general/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/perfmon/classes/general/query.php

<?php

class CPerfQueryJoin
{
	public $left_table = '';
	public $left_column = '';
	public $left_const = '';
	public $right_table = '';
	public $right_column = '';
	public $right_const = '';

	protected function _parse($sql)
	{
		$match = [];
		if (preg_match('/^([`"\[\]]?[a-zA-Z0-9_]+[`"\[\]]?)\.(.+)$/', $sql, $match))
		{
			$table = $match[1];
			$column = $match[2];
			$const = '';
		}
		else
		{
			$table = '';
			$column = '';
			$const = $sql;
		}

		return [$table, $column, $const];
	}

	public function parse_left($sql)
	{
		[$this->left_table, $this->left_column, $this->left_const] = $this->_parse($sql);
	}

	public function parse_right($sql)
	{
		[$this->right_table, $this->right_column, $this->right_const] = $this->_parse($sql);
	}
}

class CPerfQueryWhere
{
	public $table_aliases_regex = '';
	public $equation_regex = '';
	public $sql = '';
	public $simplified_sql = '';
	public $joins = [];

	public function __construct($table_aliases_regex)
	{
		$this->table_aliases_regex = $table_aliases_regex;
		$this->equation_regex = '(?:' . $this->table_aliases_regex . "\\.[`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1}|[0-9]+|'[^']*') (?:=|<|>|> =|< =|IS) (?:" . $this->table_aliases_regex . "\\.[`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1}|[0-9]+|'[^']*'|NULL)";
	}

	public function parse($sql)
	{
		//Transform and simplify sql
		//
		//Remove balanced braces around equals
		$sql = $this->_remove_braces(CPerfQuery::removeSpaces($sql));

		//Replace "expr1 = <const1> or expr1 = <const2> or expr1 = <const3> ..."
		//with "expr1 in (<const1>, ...)"
		$new_sql = preg_replace_callback('/\\( (' . $this->equation_regex . '(?: OR ' . $this->equation_regex . ')+) \\)/i', [$this, '_or2in'], CPerfQuery::removeSpaces($sql));
		if ($new_sql !== null)
		{
			$sql = CPerfQuery::removeSpaces($new_sql);
		}

		//Replace IN with no more than 5 values to equal
		$sql = preg_replace("/ IN\s*\(\s*(\d+|'[^']*')(\s*,\s*(\d+|'[^']*')\s*){0,5}\s*\)/i", " = \\1 ", $sql);

		//Remove complex inner syntax
		while (preg_match('/\\([^()]*\\)/', $sql))
		{
			$sql = preg_replace('/\\([^()]*\\)/', '', $sql);
		}

		$this->simplified_sql = $sql;

		foreach (preg_split('/ and /i', $sql) as $str)
		{
			$match = [];
			if (preg_match('/(' . $this->table_aliases_regex . '\\.[`"\\[\\]]{0,1}[a-zA-Z0-9_]+[`"\\[\\]]{0,1}) = (' . $this->table_aliases_regex . '\\.[`"\\[\\]]{0,1}[a-zA-Z0-9_]+[`"\\[\\]]{0,1})/', $str, $match))
			{
				$join = new CPerfQueryJoin;
				$join->parse_left($match[1]);
				$join->parse_right($match[2]);
				$this->joins[] = $join;
			}
			elseif (preg_match('/(' . $this->table_aliases_regex . "\\.[`\"\\[\\]]{0,1}[a-zA-Z0-9_]+[`\"\\[\\]]{0,1}) = ([0-9]+|'.+')/", $str, $match))
			{
				$join = new CPerfQueryJoin;
				$join->parse_left($match[1]);
				$join->parse_right($match[2]);
				$this->joins[] = $join;
			}
		}

		return !empty($this->joins);
	}

	//Remove balanced braces around equals
	protected function _remove_braces($sql)
	{
		while (true)
		{
			$new_sql = preg_replace('/\\([ ]*(' . $this->equation_regex . '(?: AND ' . $this->equation_regex . ')*)[ ]*\\)/i', "\\1", $sql);
			if ($new_sql === null)
			{
				break;
			}

			if ($new_sql === $sql)
			{
				$new_sql = preg_replace('/\\( \\( (' . $this->equation_regex . '(?: OR ' . $this->equation_regex . ')*) \\) \\)/i', "( \\1 )", trim($sql));
				if ($new_sql === null)
				{
					break;
				}

				if ($new_sql === $sql)
				{
					break;
				}
			}

			$sql = trim($new_sql);
		}
		return $sql;
	}

	protected function _or2in($or_match)
	{
		$sql = $or_match[0];

		$match = [];
		if (preg_match_all('/(' . $this->table_aliases_regex . "\\.[a-zA-Z0-9_]+|[0-9]+|'[^']*') (?:=) ([0-9]+|'[^']*')/", $or_match[1], $match))
		{
			if (count(array_unique($match[1])) == 1)
			{
				$sql = $match[1][0] . ' IN ( ' . implode(', ', $match[2]) . ' )';
			}
		}

		return $sql;
	}
}

class CPerfQueryTable
{
	public $sql = '';
	public $name = '';
	public $alias = '';
	public $join = '';

	public function parse($sql)
	{
		$sql = CPerfQuery::removeSpaces($sql);

		$match = [];
		if (preg_match('/^([`"\[\]]?[a-z0-9_]+[`"\[\]]?) ([`"\[\]]?[a-z0-9_]+[`"\[\]]?) on (.+)$/i', $sql, $match))
		{
			$this->name = $match[1];
			$this->alias = $match[2];
			$this->join = $match[3];
		}
		if (preg_match('/^([`"\[\]]?[a-zA-Z0-9_]+[`"\[\]]?) ([`"\[\]]?[a-zA-Z0-9_]+[`"\[\]]?)($| )/', $sql, $match))
		{
			$this->name = $match[1];
			$this->alias = $match[2];
		}
		elseif (preg_match('/^([`"\[\]]?[a-zA-Z0-9_]+[`"\[\]]?)$/', $sql, $match))
		{
			$this->name = $match[1];
			$this->alias = $this->name;
		}
		else
		{
			return false;
		}

		$this->sql = $sql;
		return true;
	}
}

class CPerfQueryFrom
{
	public $sql = '';
	/** @var array[]CPerfQueryTable */
	public $tables = [];
	public $joins = [];

	public function parse($sql)
	{
		$sql = CPerfQuery::removeSpaces($sql);

		$match = [];
		if (preg_match('/^select(.*) from (.*?) (where|group|having|order)/is', $sql, $match))
		{
			$this->sql = $match[2];
		}
		elseif (preg_match('/^select(.*) from (.*?)$/is', $sql, $match))
		{
			$this->sql = $match[2];
		}
		else
		{
			$this->sql = '';
		}

		if ($this->sql)
		{
			$arJoinTables = preg_split('/(,|inner\\s+join|left\\s+join)(?=\\s+[`"\\[\\]]{0,1}[a-z0-9_]+[`"\\[\\]]{0,1})/is', $this->sql);
			foreach ($arJoinTables as $str)
			{
				$table = new CPerfQueryTable;
				if ($table->parse($str))
				{
					$this->tables[] = $table;
				}
			}

			if (!$this->tables)
			{
				return false;
			}

			$tables_regex = '(?:' . implode('|', $this->getTableAliases()) . ')';
			/** @var CPerfQueryTable $table */
			foreach ($this->tables as $table)
			{
				$where = new CPerfQueryWhere($tables_regex);
				if ($where->parse($table->join))
				{
					$this->joins = array_merge($this->joins, $where->joins);
				}
			}
		}

		return !empty($this->tables);
	}

	public function getTableAliases()
	{
		$res = [];
		/** @var CPerfQueryTable $table */
		foreach ($this->tables as $table)
		{
			$res[] = $table->alias;
		}
		return $res;
	}
}

class CPerfQuery
{
	public $sql = '';
	public $type = 'unknown';
	public $subqueries = [];
	/** @var CPerfQueryFrom */
	public $from = null;
	/** @var CPerfQueryWhere */
	public $where = null;

	public static function transform2select($sql)
	{
		$match = [];
		if (preg_match("#^\\s*insert\\s+into\\s+(.+?)(\\(|)\\s*(\\s*select.*)\\s*\\2\\s*(\$|ON\\s+DUPLICATE\\s+KEY\\s+UPDATE)#is", $sql, $match))
		{
			$result = $match[3];
		}
		elseif (preg_match('#^\\s*DELETE\\s+#i', $sql))
		{
			$result = preg_replace('#^\\s*(DELETE.*?FROM)#is', 'select * from', $sql);
		}
		elseif (preg_match('#^\\s*SELECT\\s+#i', $sql))
		{
			$result = $sql;
		}
		else
		{
			$result = '';
		}

		return $result;
	}

	public static function removeSpaces($str)
	{
		return trim(preg_replace("/[ \t\n\r]+/", ' ', $str), " \t\n\r");
	}

	public function parse($sql)
	{
		$this->sql = preg_replace('/([()=])/', " \\1 ", $sql);
		$this->sql = CPerfQuery::removeSpaces($this->sql);

		$match = [];
		if (preg_match('/^(select) /i', $this->sql, $match))
		{
			$this->type = mb_strtolower($match[1]);
		}
		else
		{
			$this->type = 'unknown';
		}

		if ($this->type === 'select')
		{
			//0 TODO replace literals with placeholders
			//1 remove subqueries from sql
			if (!$this->parse_subqueries())
			{
				return false;
			}
			//2 parse from
			$this->from = new CPerfQueryFrom;
			if (!$this->from->parse($this->sql))
			{
				return false;
			}

			$tables_regex = '(?:' . implode('|', $this->from->getTableAliases()) . ')';
			$this->where = new CPerfQueryWhere($tables_regex);
			if (preg_match('/ where (.+?)($| group | having | order )/i', $this->sql, $match))
			{
				$this->where->parse($match[1]);
			}

			return true;
		}
		else
		{
			return false;
		}
	}

	public function parse_subqueries()
	{
		$this->subqueries = [];

		$ar = preg_split('/(\\(\\s*select|\\(|\\))/is', $this->sql, -1, PREG_SPLIT_DELIM_CAPTURE);
		$subq = 0;
		$braces = 0;
		foreach ($ar as $i => $str)
		{
			if ($str === ')')
			{
				$braces--;
			}
			elseif (mb_substr($str, 0, 1) === '(')
			{
				$braces++;
			}

			if ($subq == 0)
			{
				if (preg_match('/^\\(\\s*select/is', $str))
				{
					$this->subqueries[] = mb_substr($str, 1);
					$subq++;
					unset($ar[$i]);
				}
			}
			elseif ($braces == 0)
			{
				$subq--;
				unset($ar[$i]);
			}
			else
			{
				$this->subqueries[count($this->subqueries) - 1] .= $str;
				unset($ar[$i]);
			}
		}

		$this->sql = implode('', $ar);
		return true;
	}

	public function cmp($table, $alias)
	{
		if ($table === $alias)
		{
			return true;
		}
		elseif ($table === '`' . $alias . '`')
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	public function table_joins($table_alias)
	{
		//Lookup table by its alias
		$suggest_table = null;
		/** @var CPerfQueryTable $table */
		foreach ($this->from->tables as $table)
		{
			if ($this->cmp($table->alias, $table_alias))
			{
				$suggest_table = $table;
			}
		}
		if (!isset($suggest_table))
		{
			return [];
		}

		$arTableJoins = [
			'WHERE' => []
		];
		//1 iteration gather inter tables joins
		foreach ($this->from->joins as $join)
		{
			if ($this->cmp($join->left_table, $table_alias) && $join->right_table !== '')
			{
				if (!isset($arTableJoins[$join->right_table]))
				{
					$arTableJoins[$join->right_table] = [];
				}
				$arTableJoins[$join->right_table][] = $join->left_column;
			}
			elseif ($this->cmp($join->right_table, $table_alias) && $join->left_table !== '')
			{
				if (!isset($arTableJoins[$join->left_table]))
				{
					$arTableJoins[$join->left_table] = [];
				}
				$arTableJoins[$join->left_table][] = $join->right_column;
			}
		}
		//2 iteration gather inter tables joins from where
		foreach ($this->where->joins as $join)
		{
			if ($this->cmp($join->left_table, $table_alias) && $join->right_table !== '')
			{
				if (!isset($arTableJoins[$join->right_table]))
				{
					$arTableJoins[$join->right_table] = [];
				}
				$arTableJoins[$join->right_table][] = $join->left_column;
			}
			elseif ($this->cmp($join->right_table, $table_alias) && $join->left_table !== '')
			{
				if (!isset($arTableJoins[$join->left_table]))
				{
					$arTableJoins[$join->left_table] = [];
				}
				$arTableJoins[$join->left_table][] = $join->right_column;
			}
		}
		//3 iteration add constant filters from joins
		foreach ($this->from->joins as $join)
		{
			if ($this->cmp($join->left_table, $table_alias) && $join->right_table === '')
			{
				foreach ($arTableJoins as $i => $arColumns)
				{
					$arTableJoins[$i][] = $join->left_column;
				}
			}
			elseif ($this->cmp($join->right_table, $table_alias) && $join->left_table === '')
			{
				foreach ($arTableJoins as $i => $arColumns)
				{
					$arTableJoins[$i][] = $join->right_column;
				}
			}
		}
		//4 iteration add constant filters from where
		foreach ($this->where->joins as $join)
		{
			if ($this->cmp($join->left_table, $table_alias) && $join->right_table === '')
			{
				foreach ($arTableJoins as $i => $arColumns)
				{
					$arTableJoins[$i][] = $join->left_column;
				}
			}
			elseif ($this->cmp($join->right_table, $table_alias) && $join->left_table === '')
			{
				foreach ($arTableJoins as $i => $arColumns)
				{
					$arTableJoins[$i][] = $join->right_column;
				}
			}
		}

		if (empty($arTableJoins['WHERE']))
		{
			unset($arTableJoins['WHERE']);
		}

		return $arTableJoins;
	}

	public function suggest_index($table_alias)
	{
		global $DB;

		$suggest_table = null;
		/** @var CPerfQueryTable $table */
		foreach ($this->from->tables as $table)
		{
			if ($this->cmp($table->alias, $table_alias))
			{
				$suggest_table = $table;
			}
		}
		if (!isset($suggest_table))
		{
			return false;
		}

		$arTableJoins = $this->table_joins($table_alias);

		//Next read indexes already have
		$arSuggest = [];
		if (!empty($arTableJoins))
		{
			if (!$DB->TableExists($suggest_table->name))
			{
				return false;
			}

			$table = new CPerfomanceTable;
			$arIndexes = $table->GetIndexes($suggest_table->name);
			foreach ($arIndexes as $index_name => $arColumns)
			{
				$arIndexes[$index_name] = implode(',', $arColumns);
			}

			//Test our suggestion against existing indexes
			foreach ($arTableJoins as $arColumns)
			{
				$index_found = '';
				$arColumns = $this->_adjust_columns($arColumns);
				//Take all possible combinations of columns
				$arCombosToTest = $this->array_power_set($arColumns);

				foreach ($arCombosToTest as $arComboColumns)
				{
					if (!empty($arComboColumns))
					{
						$index2test = implode(',', $arComboColumns);
						//Try to find out if index already exists
						foreach ($arIndexes as $index_name => $index_columns)
						{
							if (mb_substr($index_columns, 0, mb_strlen($index2test)) === $index2test)
							{
								if (
									$index_found === ''
									|| count(explode(',', $index_found)) < count(explode(',', $index2test))
								)
								{
									$index_found = $index2test;
								}
							}
						}
					}
				}
				//
				if (!$index_found)
				{
					sort($arColumns);
					$arSuggest[] = $suggest_table->alias . ':' . $suggest_table->name . ':' . implode(',', $arColumns);
				}
			}
		}

		if (!empty($arSuggest))
		{
			return $arSuggest;
		}
		else
		{
			return false;
		}
	}

	public function array_power_set($array)
	{
		$results = [[]];
		foreach ($array as $element)
		{
			foreach ($results as $combination)
			{
				$results[] = array_merge([$element], $combination);
			}
		}
		return $results;
	}

	protected function _adjust_columns($arColumns)
	{
		$arColumns = array_unique($arColumns);
		while (mb_strlen(implode(',', $arColumns)) > 250)
		{
			//TODO: add brains here
			//1 exclude blobs and clobs
			//2 etc.
			array_pop($arColumns);
		}
		return $arColumns;
	}

	public function has_where($table_alias = false)
	{
		if ($table_alias === false)
		{
			return !empty($this->where->joins);
		}

		foreach ($this->where->joins as $join)
		{
			if ($this->cmp($join->left_table, $table_alias))
			{
				return true;
			}
			elseif ($this->cmp($join->right_table, $table_alias))
			{
				return true;
			}
		}

		return false;
	}

	public function find_value($table_name, $column_name)
	{
		//Lookup table by its name
		/** @var CPerfQueryTable $table */
		foreach ($this->from->tables as $table)
		{
			if ($table->name === $table_name)
			{
				$table_alias = $table->alias;

				foreach ($this->where->joins as $join)
				{
					if (
						$join->left_table === $table_alias
						&& $join->left_column === $column_name
						&& $join->right_const !== ''
					)
					{
						return $join->right_const;
					}
					elseif (
						$join->right_table === $table_alias
						&& $join->right_column === $column_name
						&& $join->left_const !== ''
					)
					{
						return $join->left_const;
					}
				}

				foreach ($this->from->joins as $join)
				{
					if (
						$join->left_table === $table_alias
						&& $join->left_column === $column_name
						&& $join->right_const !== ''
					)
					{
						return $join->right_const;
					}
					elseif (
						$join->right_table === $table_alias
						&& $join->right_column === $column_name
						&& $join->left_const !== ''
					)
					{
						return $join->left_const;
					}
				}
			}
		}

		return '';
	}

	public function find_join($table_name, $column_name)
	{
		//Lookup table by its name
		$suggest_table = null;
		/** @var CPerfQueryTable $table */
		foreach ($this->from->tables as $table)
		{
			if ($table->name === $table_name)
			{
				$suggest_table = $table;
			}
		}

		if (!isset($suggest_table))
		{
			return '';
		}
		$table_alias = $suggest_table->alias;

		foreach ($this->where->joins as $join)
		{
			if (
				$join->left_table === $table_alias
				&& $join->left_column === $column_name
				&& $join->right_table !== ''
			)
			{
				return $join->right_table . '.' . $join->right_column;
			}
			elseif (
				$join->right_table === $table_alias
				&& $join->right_column === $column_name
				&& $join->left_table !== ''
			)
			{
				return $join->left_table . '.' . $join->left_column;
			}
		}

		foreach ($this->from->joins as $join)
		{
			if (
				$join->left_table === $table_alias
				&& $join->left_column === $column_name
				&& $join->right_table !== ''
			)
			{
				return $join->right_table . '.' . $join->right_column;
			}
			elseif (
				$join->right_table === $table_alias
				&& $join->right_column === $column_name
				&& $join->left_table !== ''
			)
			{
				return $join->left_table . '.' . $join->left_column;
			}
		}

		return '';
	}

	public static function remove_literals($sql)
	{
		return preg_replace('/(
				"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"                           # match double quoted string
				|
				\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'                       # match single quoted string
				|
				(?s:\\/\\*.*?\\*\\/)                                     # multi line comments
				|
				\\/\\/.*?\\n                                             # single line comments
				|
				(?<![A-Za-z_])([0-9]+\\.[0-9]+|[0-9]+)(?![A-Za-z_])                       # an number
				|
				(?i:\\sIN\\s*\\(\\s*[0-9.]+(?:\\s*,\\s*[0-9.])*\\s*\\))  # in (1, 2, 3)
				|
				(?i:\\sIN\\s*\\(\\s*[\'].+?[\'](?:\\s*,\\s*[\'].+?[\'])*\\s*\\))  # in (\'a\', \'b\', \'c\')
			)/x', '', $sql);
	}
}