Система управления «Сайт PRO»
Версия 20240107

Класс Parser

Объект $Parser: Cms\Root\Data\Parser наследует Cms\Site\Base

Объект для разбора SQL-запроса

Исходный код
class Parser extends \Cms\Site\Base { … }

Свойства

$reserved

$Parser->reserved = array(
        'ABORT', 'ACCESSIBLE', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'ASENSITIVE', 'ATTACH', 'AUTOINCREMENT', 'BEFORE', 'BEGIN', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BY', 'CALL', 'CASCADE', 'CASE', 'CAST', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'COMMIT', 'CONDITION', 'CONFLICT', 'CONSTRAINT', 'CONTINUE', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'CURSOR', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MICROSECOND', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DECLARE', 'DEFAULT', 'DEFERRABLE', 'DEFERRED', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DETACH', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'DUAL', 'EACH', 'ELSE', 'ELSEIF', 'ENCLOSED', 'END', 'ESCAPE', 'ESCAPED', 'EXCEPT', 'EXCLUSIVE', 'EXISTS', 'EXIT', 'EXPLAIN', 'FAIL', 'FALSE', 'FETCH', 'FLOAT', 'FLOAT4', 'FLOAT8', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULL', 'FULLTEXT', 'GENERAL', 'GLOB', 'GRANT', 'GROUP', 'HAVING', 'HIGH_PRIORITY', 'HOUR_MICROSECOND', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'IGNORE_SERVER_IDS', 'IMMEDIATE', 'IN', 'INDEX', 'INDEXED', 'INFILE', 'INITIALLY', 'INNER', 'INOUT', 'INSENSITIVE', 'INSERT', 'INSTEAD', 'INT', 'INT1', 'INT2', 'INT3', 'INT4', 'INT8', 'INTEGER', 'INTERSECT', 'INTERVAL', 'INTO', 'IS', 'ISNULL', 'ITERATE', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEAVE', 'LEFT', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOOP', 'LOW_PRIORITY', 'MASTER_HEARTBEAT_PERIOD', 'MASTER_SSL_VERIFY_SERVER_CERT', 'MATCH', 'MAXVALUE', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_MICROSECOND', 'MINUTE_SECOND', 'MOD', 'MODIFIES', 'NATURAL', 'NO', 'NOT', 'NOTNULL', 'NO_WRITE_TO_BINLOG', 'NULL', 'NUMERIC', 'OF', 'OFFSET', 'ON', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUT', 'OUTER', 'OUTFILE', 'PLAN', 'PRAGMA', 'PRECISION', 'PRIMARY', 'PROCEDURE', 'PURGE', 'QUERY', 'RAISE', 'RANGE', 'READ', 'READS', 'READ_WRITE', 'REAL', 'REFERENCES', 'REGEXP', 'REINDEX', 'RELEASE', 'RENAME', 'REPEAT', 'REPLACE', 'REQUIRE', 'RESIGNAL', 'RESTRICT', 'RETURN', 'REVOKE', 'RIGHT', 'RLIKE', 'ROLLBACK', 'ROW', 'SAVEPOINT', 'SCHEMA', 'SCHEMAS', 'SECOND_MICROSECOND', 'SELECT', 'SENSITIVE', 'SEPARATOR', 'SET', 'SHOW', 'SIGNAL', 'SLOW', 'SMALLINT', 'SPATIAL', 'SPECIFIC', 'SQL', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SSL', 'STARTING', 'STRAIGHT_JOIN', 'TABLE', 'TEMP', 'TEMPORARY', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRANSACTION', 'TRIGGER', 'TRUE', 'UNDO', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USING', 'UTC_DATE', 'UTC_TIME', 'UTC_TIMESTAMP', 'VACUUM', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'VIEW', 'VIRTUAL', 'WHEN', 'WHERE', 'WHILE', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL',
    );
Исходный код
    static $reserved = array( … );

$reserved_select

$Parser->reserved_select = array(
        'SELECT', 'FROM', 'JOIN', 'LEFT JOIN', 'WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT',
    );
Исходный код
    static $reserved_select = array( … );

$sql

$Parser->sql = null;
Исходный код
    protected $sql = null;

$tokens

$Parser->tokens = array();
Исходный код
    protected $tokens = array();

$keywords

$Parser->keywords = array();
Исходный код
    protected $keywords = array();

$sql_type

$Parser->sql_type = "sqlite";
Исходный код
    protected $sql_type = "sqlite";

Методы

reset()

$Parser->reset();
Исходный код
    function reset() {
        $this->setSql(null);
    }

parse()

$Parser->parse($sql=null, $r=null);
Исходный код
    function parse($sql = null, $r = null) {
        if (isset($this)) {
            $self = $this;
            $self->reset();
        } else {
            $class = __CLASS__;
            $self = new $class;
        }
        if ($sql === null) {
            $sql = $this->getSql();
        } else {
            $self->setSql($sql);
        }
        if (!is_object($r)) {
            $App = $self->App();
            $class = $App->getClass("sql");
            $r = new $class;
        }
        $self->tokenize();
        $tokens = $self->getTokens();
        $keywords = $self->getKeywords();
        $keywords[count($tokens)] = '';
        if ($keywords[0] == "SELECT") {
            $e = new \Cms\Root\Error("SQL error: $sql");
            $r->setQuery("SELECT");
            $prev_index = $next_index = 0;
            $prev_keyword = $next_keyword = null;
            foreach ($keywords as $next_index => $next_keyword) if (!strlen($next_keyword) || in_array($next_keyword, self::$reserved_select)) {
                if ($prev_keyword == "SELECT") {
                    $what = $self->getExpressionList($prev_index, $next_index, ",");
                    $r->setWhat($what);
                } elseif ($prev_keyword == "FROM") {
                    $table = $self->getExpression($prev_index, $next_index);
                    $r->setTable($table);
                } elseif ($prev_keyword == "JOIN") {
                    $join = $self->getExpressionList($prev_index, $next_index, "ON");
                    $r->addJoin(array_shift($join), array_shift($join));
                } elseif ($prev_keyword == "LEFT JOIN") {
                    $join = $self->getExpressionList($prev_index, $next_index, "ON");
                    $r->addJoin(array_shift($join), array_shift($join), array( "left" => true ));
                } elseif ($prev_keyword == "WHERE") {
                    $where = $self->getExpressionList($prev_index, $next_index, "AND");
                    $r->setWhere($where);
                } elseif ($prev_keyword == "GROUP BY") {
                    $group = $self->getExpressionList($prev_index, $next_index, ",");
                    $r->setGroup($group);
                } elseif ($prev_keyword == "HAVING") {
                    $having = $self->getExpressionList($prev_index, $next_index, "AND");
                    $r->setHaving($having);
                } elseif ($prev_keyword == "ORDER BY") {
                    $order = $self->getExpressionList($prev_index, $next_index, ",");
                    $r->setOrder($order);
                } elseif ($prev_keyword == "LIMIT") {
                    $limit = $self->getExpression($prev_index, $next_index);
                    $r->setLimit($limit);
                }
                $prev_index = $next_index + 1;
                $prev_keyword = $next_keyword;
            }
        }
        return $r;
    }

tokenize()

$Parser->tokenize();
Исходный код
    function tokenize() {
        $this->tokens = array();
        $this->keywords = array();
        $sql = $this->sql;
        $position = 0;
        $token = "";
        $type = 0;
        $level = 0;
        $is_string = false;
        $is_variable = false;
        # T_STRING, T_VARIABLE, T_LIST, T_CONST; T_CHARACTER, T_WHITESPACE
        while (($c = $sql[$position ++]) !== "") {
            if ($c == "\\" && $this->sql_type == "mysql") {
                if ($is_string || $is_variable) { # || $type == T_STRING || $type == T_VARIABLE
                    # continue
                    $token .= $c;
                    # continue
                    if (($c = $sql[$position ++]) !== "") $token .= $c;
                } elseif ($type == T_CONST) {
                    # break T_CONST
                    $this->addToken($token, $type);
                    $token = "";
                    $type = 0;
                    # add T_CHARACTER
                    $this->addToken($c, T_CHARACTER);
                } elseif ($type) {
                    # continue T_* - T_LIST
                    $token .= $c;
                } else {
                    # add T_CHARACTER
                    $this->addToken($c, T_CHARACTER);
                }
            } elseif ($c == " " || $c == "\t" || $c == "\n" || $c == "\r") {
                if ($type == T_CONST) {
                    # break T_CONST
                    $this->addToken($token, $type);
                    $token = "";
                    $type = 0;
                    # add T_WHITESPACE
                    $this->addToken($c, T_WHITESPACE);
                } elseif ($type) {
                    # continue T_*
                    $token .= $c;
                } else {
                    # add T_WHITESPACE
                    $this->addToken($c, T_WHITESPACE);
                }
            } elseif ($c == "'") {
                if ($is_variable) { # || $type == T_VARIABLE
                    # continue T_VARIABLE
                    $token .= $c;
                } elseif ($type == T_STRING) {
                    $token .= $c;
                    if (($c = $sql[$position ++]) == "'") {
                        $token .= $c;
                    } else {
                        # break T_STRING
                        $this->addToken($token, $type);
                        $token = "";
                        $type = 0;
                        $is_string = !$is_string;
                    }
                } elseif ($type == T_LIST) {
                    # continue T_LIST
                    $token .= $c;
                    $is_string = !$is_string;
                } elseif ($type) {
                    # break T_* - T_CONST
                    $this->addToken($token, $type);
                    # begin T_STRING
                    $token = $c;
                    $type = T_STRING;
                    $is_string = !$is_string;
                } else {
                    # begin T_STRING
                    $token = $c;
                    $type = T_STRING;
                    $is_string = !$is_string;
                }
            } elseif ($c == "`") {
                if ($is_string) { # || $type == T_STRING
                    # continue T_STRING
                    $token .= $c;
                } elseif ($type == T_VARIABLE) {
                    # break T_VARIABLE
                    $token .= $c;
                    $this->addToken($token, $type);
                    $token = "";
                    $type = 0;
                    $is_variable = !$is_variable;
                } elseif ($type == T_LIST) {
                    # continue T_LIST
                    $token .= $c;
                    $is_variable = !$is_variable;
                } elseif ($type) {
                    # break T_*
                    $this->addToken($token, $type);
                    # begin T_VARIABLE
                    $token = $c;
                    $type = T_VARIABLE;
                    $is_variable = !$is_variable;
                } else {
                    # begin T_VARIABLE
                    $token = $c;
                    $type = T_VARIABLE;
                    $is_variable = !$is_variable;
                }
            } elseif ($c == "(") {
                if ($is_string || $is_variable) { # || $type == T_STRING || $type == T_VARIABLE
                    # continue T_STRING | T_VARIABLE
                    $token .= $c;
                } elseif ($type == T_LIST) {
                    # continue T_LIST
                    $token .= $c;
                    $level ++;
                } elseif ($type) {
                    # break T_*
                    $this->addToken($token, $type);
                    # begin T_LIST
                    $token = $c;
                    $type = T_LIST;
                    $level ++;
                } else {
                    # begin T_LIST
                    $token = $c;
                    $type = T_LIST;
                    $level ++;
                }
            } elseif ($c == ")") {
                if ($is_string || $is_variable) { # || $type == T_STRING || $type == T_VARIABLE
                    # continue T_STRING | T_VARIABLE
                    $token .= $c;
                } elseif ($type == T_LIST) {
                    # continue T_LIST
                    $token .= $c;
                    $level --;
                    if (!$level) {
                        # break T_LIST
                        $this->addToken($token, $type);
                        $token = "";
                        $type = 0;
                    }
                } elseif ($type) {
                    # break T_*
                    $this->addToken($token, $type);
                    $token = "";
                    $type = 0;
                    # add T_CHARACTER
                    $this->addToken($c, T_CHARACTER);
                } else {
                    # add T_CHARACTER
                    $this->addToken($c, T_CHARACTER);
                }
            } elseif ($c >= "0" && $c <= "9") {
                if ($type) {
                    # continue T_*
                    $token .= $c;
                } else {
                    # add T_CHARACTER
                    $this->addToken($c, T_CHARACTER);
                }
            } elseif (($c >= "a" && $c <= "z") || ($c >= "A" && $c <= "Z")) {
                if ($type) {
                    # continue T_*
                    $token .= $c;
                } else {
                    # begin T_CONST
                    $token = $c;
                    $type = T_CONST;
                }
            } else {
                if ($type == T_CONST) {
                    # break T_CONST
                    $this->addToken($token, $type);
                    $token = "";
                    $type = 0;
                    # add T_CHARACTER
                    $this->addToken($c, T_CHARACTER);
                } elseif ($type) {
                    # continue T_*
                    $token .= $c;
                } else {
                    # add T_CHARACTER
                    $this->addToken($c, T_CHARACTER);
                }
            }
        }
        if ($type) {
            # break T_*
            $this->addToken($token, $type);
        }
    }

addToken()

$Parser->addToken($token, $type);
Исходный код
    function addToken($token, $type) {
        $this->tokens[] = array($token, $type);
        if ($type == T_CONST) {
            if (in_array($keyword = strtoupper($token), self::$reserved)) {
                end($this->tokens);
                $index = key($this->tokens);
                if ($keyword == "BY") {
                    while (($a = prev($this->tokens)) && $a[1] == T_WHITESPACE) { }
                    if ($a && $a[1] == T_CONST) {
                        $prev_keyword = strtoupper($a[0]);
                        if ($prev_keyword == "GROUP" || $prev_keyword == "ORDER") {
                            # GROUP BY | ORDER BY
                            $keyword = "$prev_keyword $keyword";
                            $prev_index = key($this->tokens);
                            for ($i = $prev_index; $i <= $index; $i ++) array_pop($this->tokens);
                            $this->tokens[$index = $prev_index] = array($keyword, T_CONST);
                        }
                    }
                }
                if ($keyword == "JOIN") {
                    while (($a = prev($this->tokens)) && $a[1] == T_WHITESPACE) { }
                    if ($a && $a[1] == T_CONST) {
                        $prev_keyword = strtoupper($a[0]);
                        if ($prev_keyword == "LEFT") {
                            # LEFT JOIN
                            $keyword = "$prev_keyword $keyword";
                            $prev_index = key($this->tokens);
                            for ($i = $prev_index; $i <= $index; $i ++) array_pop($this->tokens);
                            $this->tokens[$index = $prev_index] = array($keyword, T_CONST);
                        }
                    }
                }
                $this->keywords[$index] = $keyword;
            }
        }
    }

getExpression()

$Parser->getExpression($index1, $index2);
Исходный код
    function getExpression($index1, $index2) {
        $expression = "";
        for ($i = $index1; $i < $index2; $i ++) $expression .= $this->tokens[$i][0];
        return trim($expression);
    }

getExpressionList()

$Parser->getExpressionList($index1, $index2, $separator=",");
Исходный код
    function getExpressionList($index1, $index2, $separator = ",") {
        $r = array();
        if (strlen($separator) == 1) {
            $separator_type = T_CHARACTER;
        } else {
            $separator_type = T_CONST;
            $separator = strtoupper($separator);
        }
        $expression = "";
        for ($i = $index1; $i < $index2; $i ++) { 
            list($token, $type) = $this->tokens[$i];
            if ($type == $separator_type && strtoupper($token) == $separator) {
                if (strlen($expression = trim($expression))) $r[] = $expression;
                $expression = "";
            } else {
                $expression .= $token;
            }
        }
        if (strlen($expression = trim($expression))) $r[] = $expression;
        return $r;
    }

getTokens()

$Parser->getTokens();
Исходный код
    function getTokens() {
        return $this->tokens;
    }

getKeywords()

$Parser->getKeywords();
Исходный код
    function getKeywords() {
        return $this->keywords;
    }

getSql()

$Parser->getSql();
Исходный код
    function getSql() {
        return $this->sql;
    }

setSql()

$Parser->setSql($sql);
Исходный код
    function setSql($sql) {
        $this->sql = trim($sql);
        $this->tokens = array();
        $this->indexes = array();
        $this->keywords = array();
    }