2

我一直在研究我自己的 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 的方式执行此操作。

4

2 回答 2

1

如果您只想解析 where 表达式,一个简单的运算符优先级解析器就可以解决问题。您必须对解析树进行一些检查以确保表达式有效,但这并不难。

您可以在http://dickgrune.com/Books/PTAPG_1st_Edition/(“解析技术 - 实用指南”)下载一个出色的解析指南。优先级解析在 9.2 优先级解析,第 187 页中介绍。

该技术假设您有两件事:

  1. 标记器。这应该识别标记,例如:标识符/关键字、数字、运算符、空格/注释等。
  2. 优先表。

您从分词器中一一读取令牌。当您发现令牌是运算符时(您知道这是因为它们存储在优先级表中),然后您确定当前令牌的优先级是否高于前一个运算符。如果当前运算符的优先级低于前一个标记的优先级,那么您必须将前一个运算符及其操作数写入解析树,然后从那里回头查找前一个运算符的前一个运算符是什么。如果标记器将标记作为双链表传递,这些操作效果最好,这样您就可以轻松地遍历标记。

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

  1. 使用现有的 SQL 解析器。参见例如http://code.google.com/p/php-sql-parser/
  2. 重新考虑你的 API。

关于选项 #2,您可以要求他们将其作为数组或以 JSON 甚至 XML 等易于解析的格式传递,而不是允许人们将表达式指定为原始文本。

例如,你可以这样:

$db->join->inner($table, array(
    '=' => array(
        'left' => array (
            'table' => 'tab1'
        ,   'column' => 'col1' 
        )
    ,   'right' => array (
            'table' => 'tab2'
        ,   'column' => 'col2' 
        )
    )
));
于 2012-08-01T15:24:01.697 回答
0

所以这大致是我想出的:

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;
}

}

然后我在我的 Query Builder 类中运行它,在子句中引用标识符,然后将它串在一起:

// 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']);
于 2012-08-09T20:42:48.770 回答