2

私はしばらくの間、独自の SQL ライブラリ/クエリ ビルダーに取り組んできました。(https://github.com/aviat4ion/Query) ほとんどの場合、動作に満足しています。

1 つの問題は、クエリの結合にあります。

のようなことを言う

$db->join($table, 'table1.field1=table2.field2', 'inner');

テーブル識別子を適切にエスケープする必要がある2番目の引数を解析する方法について、私はかなり困惑しています。

条件でも関数を処理できるようにしたいです。

私の現在の実装はかなり素朴です - スペースで条件を分割するので、「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 PRECEDENCE PARSING, page 187 で説明されています。

この手法では、次の 2 つがあることを前提としています。

  1. トークナイザー。これは、識別子/キーワード、数字、演算子、空白/コメントなどのトークンを認識する必要があります。
  2. 優先順位表。

トークナイザーからトークンを 1 つずつ読み取ります。トークンが演算子であることがわかった場合 (それらは優先順位テーブルに格納されているためです)、現在のトークンの優先順位が前の演算子よりも高いか低いかを判断します。現在の演算子の優先順位が前のトークンの優先順位よりも低い場合は、前の演算子をそのオペランドと共に解析ツリーに書き込み、そこから振り返って前の前の演算子の前の演算子が何であったかを見つける必要があります。これらの操作は、トークンを簡単にトラバースできるように、トークナイザーがトークンを二重リンク リストとして提供する場合に最適に機能します。

これが難しそうに聞こえる場合は、次のいずれかを行います。

  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 に答える