znt/lib/sqlparser/sqlparser.class.php

680 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require __DIR__ . '/vendor/autoload.php';
class sqlparser
{
/**
* Parser object of SqlParser.
*
* @var object
* @access public
*/
public $parser;
/**
* Statements of parser.
*
* @var array
* @access public
*/
public $statements;
/**
* First statement.
*
* @var object
* @access public
*/
public $statement;
/**
* Count statements.
*
* @var int
* @access public
*/
public $statementsCount = 0;
/**
* Is first statemnt select type.
*
* @var bool
* @access public
*/
public $isSelect = false;
/**
* DAO
*
* @var object
* @access public
*/
public $dao = null;
/**
* Origin tables.
*
* @var array
* @access public
*/
public $originTables = array();
/**
* Columns.
*
* @var array
* @access public
*/
public $columns = array();
/**
* Tables.
*
* @var array
* @access public
*/
public $tables = array();
public function __construct($query = '')
{
$query = $this->skipLineBreak($query);
if(empty($query)) return;
$this->parser = new PhpMyAdmin\SqlParser\Parser($query);
$this->statements = $this->parser->statements;
$this->statementsCount = count($this->statements);
$this->statement = $this->statementsCount > 0 ? current($this->statements) : null;
$this->isSelect = $this->statement instanceof PhpMyAdmin\SqlParser\Statements\SelectStatement === true;
}
/**
* Set dao.
*
* @param object $dao
* @access public
* @return void
*/
public function setDAO($dao)
{
$this->dao = $dao;
}
/**
* Parse statement.
*
* @access public
* @return void
*/
public function parseStatement()
{
if(empty($this->statement)) return;
$this->columns = $this->parseColumns();
$this->tables = $this->parseTables();
}
/**
* Create statement.
*
* @access public
* @return void
*/
public function createStatement()
{
$this->statement = new PhpMyAdmin\SqlParser\Statements\SelectStatement();
}
/**
* Set from.
*
* @param object $from
* @access public
* @return void
*/
public function setFrom($from)
{
$this->statement->from = array($from);
}
/**
* Add select.
*
* @param array|object $exprs
* @access public
* @return void
*/
public function addSelect($exprs)
{
if(empty($exprs)) return;
if($exprs instanceof PhpMyAdmin\SqlParser\Components\Expression)
{
$this->statement->expr[] = $exprs;
return;
}
foreach($exprs as $expr) $this->addSelect($expr);
}
/**
* Add join.
*
* @param array|object $joins
* @access public
* @return void
*/
public function addJoin($joins)
{
if(empty($joins)) return;
if($joins instanceof PhpMyAdmin\SqlParser\Components\JoinKeyword)
{
$this->statement->join[] = $joins;
return;
}
foreach($joins as $join) $this->addJoin($join);
}
/**
* Add where.
*
* @param array|object $wheres
* @access public
* @return void
*/
public function addWhere($wheres)
{
if(empty($wheres)) return;
if($wheres instanceof PhpMyAdmin\SqlParser\Components\Condition)
{
$this->statement->where[] = $wheres;
return;
}
foreach($wheres as $where) $this->addWhere($where);
}
/**
* Add group.
*
* @param array|object $groups
* @access public
* @return void
*/
public function addGroup($groups)
{
if(empty($groups)) return;
if($groups instanceof PhpMyAdmin\SqlParser\Components\GroupKeyword)
{
$this->statement->group[] = $groups;
return;
}
foreach($groups as $group) $this->addGroup($group);
}
/**
* Get function.
*
* @param string $name
* @param mixed $args
* @access public
* @return string
*/
public function getFunction($name, ...$args)
{
$name = strtoupper($name);
$argStr = implode(', ', $args);
$argStr = str_replace(PHP_EOL, '', $argStr);
return "$name($argStr)";
}
/**
* Get expression.
*
* @param string|array $table
* @param string $column
* @param string $alias
* @param string $function
* @access public
* @return PhpMyAdmin\SqlParser\Components\Expression
*/
public function getExpression($table = null, $column = null, $alias = null, $function = null)
{
if(is_array($table)) return call_user_func_array(array($this, 'getExpression'), $table);
$expression = new PhpMyAdmin\SqlParser\Components\Expression();
if(!empty($function))
{
$expression->function = $function;
$expression->expr = $this->getFunction($function, $expression->build($this->getExpression($table, $column)));
}
else
{
if($column === '*')
{
$table = $this->trimExpr($table);
$expression->expr = empty($table) ? '*' : "`$table`.*";
}
else
{
$expression->table = $table;
$expression->column = $column;
}
}
$expression->alias = $alias;
return $expression;
}
/**
* operatorCondition
*
* @param string $type
* @access public
* @return void
*/
public function operatorCondition($type)
{
$condition = new PhpMyAdmin\SqlParser\Components\Condition();
$condition->isOperator = true;
$condition->expr = strtoupper($type);
return $condition;
}
/**
* Get condition.
*
* @param mixed $tableA
* @param mixed $columnA
* @param string $operator
* @param mixed $tableB
* @param mixed $columnB
* @param mixed $group
* @access public
* @return object
*/
public function getCondition($tableA = null, $columnA = null, $operator = '', $tableB = null, $columnB = null, $group = 1, $quote = true)
{
if(is_array($tableA)) return call_user_func_array(array($this, 'getCondition'), $tableA);
$condition = new PhpMyAdmin\SqlParser\Components\Condition();
$tableA = $this->trimExpr($tableA);
$columnA = $this->trimExpr($columnA);
$tableB = $this->trimExpr($tableB);
$expr = '';
if(empty($operator))
{
$expr = $columnA;
}
else
{
/* 如果tableB不为空那么columnB就是字段需要trim('`')。*/
if(!empty($tableB)) $columnB = $this->trimExpr($columnB);
/* 如果tableB为空那么columnB是值需要trim("'")。*/
if(empty($tableB)) $columnB = $this->trimExpr($columnB, "'");
$columnB = empty($tableB) && $quote ? "'$columnB'" : $columnB;
$exprA = empty($tableA) ? "`$columnA`" : "`$tableA`.`$columnA`";
$exprB = empty($tableB) ? $columnB : "`$tableB`.`$columnB`";
$operator = strtoupper($operator);
$expr = "$exprA $operator $exprB";
}
$condition->expr = $expr;
$condition->group = $group;
return $condition;
}
/**
* Get conditions from array.
*
* @param array $conditions
* @access public
* @return array
*/
public function getConditionsFromArray($conditions)
{
if(is_string($conditions)) return $this->operatorCondition($conditions);
$first = current($conditions);
if(!is_array($first) && !in_array($first, array('and', 'or'))) return $this->getCondition($conditions);
$conditionExprs = array();
foreach($conditions as $condition) $conditionExprs[] = $this->getConditionsFromArray($condition);
return $conditionExprs;
}
/**
* Get left join.
*
* @param string $table
* @param string $alias
* @param array $on
* @access public
* @return object
*/
public function getLeftJoin($table, $alias, $on)
{
$left = new PhpMyAdmin\SqlParser\Components\JoinKeyword();
$left->type = 'LEFT';
$left->expr = $this->getExpression($table, null, $alias);
$left->on = $this->combineConditions($on);
return $left;
}
/**
* Get group.
*
* @param object $expr
* @access public
* @return void
*/
public function getGroup($expr)
{
$group = new PhpMyAdmin\SqlParser\Components\GroupKeyword();
$group->expr = $expr;
return $group;
}
/**
* Combine conditions.
*
* @param array|object $conditions
* @access public
* @return array
*/
public function combineConditions($conditions, $quote = false)
{
if(empty($conditions)) return array();
if($conditions instanceof PhpMyAdmin\SqlParser\Components\Condition === true) return $conditions;
$first = reset($conditions);
$last = end($conditions);
if($quote)
{
$first = $this->addQuote($first, 'left');
$last = $this->addQuote($last, 'right');
}
$quote = true;
if(is_array($first)) $first = $this->combineConditions($first, $quote);
if(is_array($last)) $last = $this->combineConditions($last, $quote);
$conditions[0] = $first;
$conditions[count($conditions) - 1] = $last;
return $conditions;
}
public function addQuote($conditions, $side)
{
if($conditions instanceof PhpMyAdmin\SqlParser\Components\Condition === true)
{
$expr = $conditions->expr;
$conditions->expr = $side == 'left' ? '(' . $expr : $expr . ')';
return $conditions;
}
$condition = $side == 'left' ? current($conditions) : end($conditions);
$index = $side == 'left' ? 0 : count($conditions) - 1;
$conditions[$index] = $this->addQuote($condition, $side);
return $conditions;
}
/**
* Match columns with table.
*
* @access public
* @return array
*/
public function matchColumnsWithTable()
{
if(empty($this->statement)) return array();
if(count($this->tables) == 1) return $this->combineSingleTable();
return $this->combineMultipleTable();
}
/**
* Trim expr.
*
* @param string $expr
* @access private
* @return string
*/
private function trimExpr($expr, $char = '`')
{
if(empty($expr)) return $expr;
return trim(trim($expr), $char);
}
/**
* Combine single table to columns.
*
* @access private
* @return array
*/
private function combineSingleTable()
{
$combineColumns = array();
$fromTable = current($this->tables);
foreach($this->columns as $columnName => $column)
{
$column['table'] = array_merge($fromTable, array('column' => $column['origin']));
$combineColumns[$columnName] = $column;
}
return $combineColumns;
}
/**
* Combine multiple table to columns.
*
* @access private
* @return array
*/
private function combineMultipleTable()
{
foreach($this->columns as $columnName => $column)
{
$column['table'] = $this->searchTables($column['table'], $this->tables, $column['origin']);
$combineColumns[$columnName] = $column;
}
return $combineColumns;
}
/**
* Search table from origin tables.
*
* @param string $tableName
* @param string $tables
* @param string $column
* @access private
* @return string|false
*/
private function searchTables($tableName, $tables, $column)
{
/* 如果能使用别名匹配上,那么直接返回。*/
/* If it can be matched using an alias, then it returns. */
foreach($tables as $table) if($tableName == $table['alias']) return array_merge($table, array('column' => $column));
/* 如果匹配不上,则字段没有使用别名进行限制,那么需要通过字段去遍历所有表。*/
/* If it doesn't match, then the field is not aliased, and you need to iterate over all tables using the field. */
foreach($tables as $table)
{
$isTable = $table['isTable'];
$originTable = $table['originTable'];
/* 如果是原始表,并且列在原始表中存在,那么返回这个表。*/
/* If it is the original table and the column exists in the original table, then the table is returned. */
if($isTable && $this->columnExistInOriginTable($originTable, $column)) return array_merge($table, array('column' => $column));
/* 如果不是原始表,并且列在子句中存在,那么返回子句中这个字段对应的表。*/
/* If it is not the original table and the column exists in the clause, then the table corresponding to the field in the clause is returned. */
if(!$isTable && isset($originTable[$column])) return array_merge($originTable[$column]['table'], array('column' => $originTable[$column]['origin']));
}
return false;
}
/**
* Parse columns.
*
* @access private
* @return void
*/
private function parseColumns()
{
$fields = array();
foreach($this->statement->expr as $expr)
{
/* 获取查询数据后真正展示出来的列名 */
$columnName = empty($expr->alias) ? $expr->column : $expr->alias;
$fields[$columnName] = array('origin' => $expr->column, 'table' => $expr->table);
}
return $fields;
}
/**
* Parse tables.
*
* @access private
* @return void
*/
private function parseTables()
{
$from = current($this->statement->from);
$joins = $this->statement->join;
$tables = array();
$tables[] = $this->parseTable($from, 'from');
if(!empty($joins)) foreach($joins as $join) $tables[] = $this->parseTable($join->expr, 'join');
return $tables;
}
/**
* Parse table.
*
* @param object $expr
* @param string $type
* @access private
* @return void
*/
private function parseTable($expr, $type)
{
$isTable = empty($expr->subquery);
$table = array('alias' => $expr->alias, 'isTable' => $isTable, 'type' => $type);
$table['originTable'] = $this->getOriginTable($expr->expr, $isTable);
return $table;
}
/**
* Get origin table from table name or expr.
*
* @param string $table
* @param bool $isTable
* @access private
* @return string|array
*/
private function getOriginTable($table, $isTable)
{
if(!$isTable)
{
$parser = new sqlparser($table);
$parser->setDAO($this->dao);
$parser->parseStatement();
return $parser->matchColumnsWithTable();
}
$this->storeOriginTable($table);
return $table;
}
/**
* Judge column exist in origin table or not.
*
* @param string $table
* @param string $column
* @access private
* @return bool
*/
private function columnExistInOriginTable($table, $column)
{
$originTable = $this->getOriginTableColumns($table);
if(!$originTable) return false;
return isset($originTable[$column]);
}
/**
* Get origin table columns.
*
* @param string $table
* @access private
* @return array|null
*/
private function getOriginTableColumns($table)
{
$originTables = $this->originTables;
return isset($originTables[$table]) ? $originTables[$table] : null;
}
/**
* Store origin table.
*
* @param string $table
* @access private
* @return void
*/
private function storeOriginTable($table)
{
if(!isset($this->originTables[$table])) $this->originTables[$table] = $this->dao->descTable($table);
}
/**
* Skip line break in sql.
*
* @param string $sql
* @access private
* @return string
*/
private function skipLineBreak($sql)
{
if(empty($sql)) return $sql;
$sql = str_replace("\n\t", " ", $sql);
$sql = str_replace("\t\n", " ", $sql);
$sql = str_replace("\n\r", " ", $sql);
$sql = str_replace("\r\n", " ", $sql);
$sql = str_replace("\r", " ", $sql);
$sql = str_replace("\n", " ", $sql);
return $sql;
}
}