如何在PHP中解析和转义sql连接条件

如何在PHP中解析和转义sql连接条件,php,escaping,Php,Escaping,我已经在自己的SQL库/查询生成器上工作了一段时间。(https://github.com/aviat4ion/Query)在很大程度上,我对事情的运作方式很满意 一个问题是查询联接 比如说 $db->join($table, 'table1.field1=table2.field2', 'inner'); 对于如何解析第二个参数,我感到相当困惑,它需要正确地转义表标识符 我希望能够在这种情况下处理函数 我当前的实现相当幼稚-拆分空间上的条件,因此“table1.field1=table

我已经在自己的SQL库/查询生成器上工作了一段时间。(https://github.com/aviat4ion/Query)在很大程度上,我对事情的运作方式很满意

一个问题是查询联接

比如说

$db->join($table, 'table1.field1=table2.field2', 'inner');
对于如何解析第二个参数,我感到相当困惑,它需要正确地转义表标识符

我希望能够在这种情况下处理函数

我当前的实现相当幼稚-拆分空间上的条件,因此“table1.field1=table2.field2”将失败,“table1.field1=table2.field2”将起作用

每个数据库驱动程序都有一个抽象标识符转义的函数,该函数用于表标识符,如
database.table.field
,以便将其转义为
“database”。“table”。“field”

所以我的基本问题是:如何解析出标识符以在连接条件中转义它们

编辑:


我需要以一种可以用于MySQL、Postgres、SQLite和Firebird的方式来实现这一点

如果您只想解析where表达式,一个简单的运算符优先解析器就可以做到这一点。您必须对解析树应用一些检查以确保表达式有效,但这并不困难

您可以在这里下载一个优秀的解析指南(“解析技术-实用指南”)。优先解析在第187页9.2优先解析中介绍

该技术假设您有两件事:

  • 标记器。这应该识别标记,如:标识符/关键字、数字、运算符、空格/注释等
  • 优先表
  • 您可以从标记器中逐个读取标记。当您发现令牌是运算符(您知道这是因为它们存储在优先级表中)时,您可以确定当前令牌的优先级是否高于或低于前一个运算符。如果当前运算符的优先级低于前一个标记的优先级,则必须将前一个运算符及其操作数写入解析树,然后从那里回过头来查找前一个运算符的前一个运算符是什么。如果标记器以双链接列表的形式交付标记,以便您可以轻松地遍历标记,则这些操作最有效

    如果这听起来很难,那么:

  • 使用现有的SQL解析器。例如,见
  • 重新考虑你的API 关于选项#2,不允许人们将表达式指定为原始文本,您可以要求他们将其作为数组传递,或者作为易于解析的格式(如JSON甚至XML)传递

    例如,你可以这样做:

    $db->join->inner($table, array(
        '=' => array(
            'left' => array (
                'table' => 'tab1'
            ,   'column' => 'col1' 
            )
        ,   'right' => array (
                'table' => 'tab2'
            ,   'column' => 'col2' 
            )
        )
    ));
    

    下面是我大致得出的结论:

    class Query_Parser {
    
    /**
     * Regex patterns for various syntax components
     *
     * @var array
     */
    private $match_patterns = array(
        'function' => '([a-zA-Z0-9_]+\((.*?)\))',
        'identifier' => '([a-zA-Z0-9_-]+\.?)+',
        'operator' => '=|AND|&&?|~|\|\|?|\^|/|>=?|<=?|-|%|OR|\+|NOT|\!=?|<>|XOR'
    );
    
    /**
     * Regex matches
     *
     * @var array
     */
    public $matches = array(
        'functions' => array(),
        'identifiers' => array(),
        'operators' => array(),
        'combined' => array(),
    );
    
    /**
     * Constructor/entry point into parser
     *
     * @param string
     */
    public function __construct($sql = '')
    {
        // Get sql clause components
        preg_match_all('`'.$this->match_patterns['function'].'`', $sql, $this->matches['functions'], PREG_SET_ORDER);
        preg_match_all('`'.$this->match_patterns['identifier'].'`', $sql, $this->matches['identifiers'], PREG_SET_ORDER);
        preg_match_all('`'.$this->match_patterns['operator'].'`', $sql, $this->matches['operators'], PREG_SET_ORDER);
    
        // Get everything at once for ordering
        $full_pattern = '`'.$this->match_patterns['function'].'+|'.$this->match_patterns['identifier'].'|('.$this->match_patterns['operator'].')+`i';
        preg_match_all($full_pattern, $sql, $this->matches['combined'], PREG_SET_ORDER);
    
        // Go through the matches, and get the most relevant matches
        $this->matches = array_map(array($this, 'filter_array'), $this->matches);
    }
    
    // --------------------------------------------------------------------------
    
    /**
     * Public parser method for seting the parse string
     *
     * @param string
     */
    public function parse_join($sql)
    {
        $this->__construct($sql);
        return $this->matches;
    }
    
    // --------------------------------------------------------------------------
    
    /**
     * Returns a more useful match array
     *
     * @param array
     * @return array
     */
    private function filter_array($array)
    {
        $new_array = array();
    
        foreach($array as $row)
        {
            if (is_array($row))
            {
                $new_array[] = $row[0];
            }
            else
            {
                $new_array[] = $row;
            }
        }
    
        return $new_array;
    }
    
    }
    
    类查询解析器{ /** *各种语法组件的正则表达式模式 * *@var数组 */ 私有$match_patterns=数组( “函数'=>'([a-zA-Z0-9\]+\(.*)”, '标识符'=>'([a-zA-Z0-9.-]+\.?)+',
    “运算符”=>“==”和“&&&”&&&&&&&&&··············································································这里可能是一个更简单的方法。我可以理解对更简单解决方案的诱惑。根据经验,我必须不幸地建议不要落入这个陷阱。要么重新构造API,这样你就不必解析任何东西(第二个选项),要么构建一个解析器。从另一方面来说,是一个“全面的”这种情况下的解析器真的没有那么难。它只需要一点时间。你的观点很好。我不想更改API,因为我希望API与另一个库兼容。如果没有其他问题,这是一个很好的学习练习。好的。给自己一点时间阅读优先级解析。我相信你会发现它符合你的目的ose非常好,也没有那么难。例如,标记器很容易使用一些正则表达式魔术创建(这不会很快,但实现起来相当快),优先级查找也没有那么难-只是一点点工作,不多不少。
    // Parse out the join condition
    $parts = $parser->parse_join($condition);
    $count = count($parts['identifiers']);
    
    // Go through and quote the identifiers
    for($i=0; $i <= $count; $i++)
    {
        if (in_array($parts['combined'][$i], $parts['identifiers']) && ! is_numeric($parts['combined'][$i]))
        {
            $parts['combined'][$i] = $this->quote_ident($parts['combined'][$i]);
        }
    }
    
    $parsed_condition = implode(' ', $parts['combined']);