source: branches/version-2_13-dev/data/class/SC_Query.php @ 23198

Revision 23198, 39.9 KB checked in by pineray, 11 years ago (diff)

#2370 トランザクション処理の前にトランザクションの状態を確認する

MDB2のコードを良く読んだら、内部で同じような処理を行っているので、今回の変更を差し戻します。

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Id
  • Property svn:mime-type set to text/x-httpd-php; charset=UTF-8
Line 
1<?php
2/*
3 * This file is part of EC-CUBE
4 *
5 * Copyright(c) 2000-2013 LOCKON CO.,LTD. All Rights Reserved.
6 *
7 * http://www.lockon.co.jp/
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 */
23
24/**
25 * SQLの構築・実行を行う
26 *
27 * TODO エラーハンドリング, ロギング方法を見直す
28 *
29 * @author LOCKON CO.,LTD.
30 * @version $Id$
31 */
32class SC_Query
33{
34    public $option = '';
35    public $where = '';
36    public $arrWhereVal = array();
37    public $conn;
38    public $groupby = '';
39    public $order = '';
40    public $force_run = false;
41    /** シングルトン動作のためのインスタンスプール配列。キーは DSN の識別情報。 */
42    public static $arrPoolInstance = array();
43
44    /**
45     * コンストラクタ.
46     *
47     * @param string  $dsn       データソース名
48     * @param boolean $force_run エラーが発生しても処理を続行する場合 true
49     * @param boolean $new       新規に接続を行うかどうか
50     */
51    public function __construct($dsn = '', $force_run = false, $new = false)
52    {
53        if ($dsn == '') {
54            $dsn = array('phptype'  => DB_TYPE,
55                         'username' => DB_USER,
56                         'password' => DB_PASSWORD,
57                         'protocol' => 'tcp',
58                         'hostspec' => DB_SERVER,
59                         'port'     => DB_PORT,
60                         'database' => DB_NAME
61                         );
62        }
63
64        // オプション
65        $options = array(
66            // 持続的接続
67            'persistent' => PEAR_DB_PERSISTENT,
68            // Debugモード
69            'debug' => PEAR_DB_DEBUG,
70        );
71
72        // バッファリング trueにするとメモリが解放されない。
73        // 連続クエリ実行時に問題が生じる。
74        $options['result_buffering'] = false;
75
76        if ($new) {
77            $this->conn = MDB2::connect($dsn, $options);
78        } else {
79            $this->conn = MDB2::singleton($dsn, $options);
80        }
81        if (!PEAR::isError($this->conn)) {
82            $this->conn->setCharset('utf8');
83            $this->conn->setFetchMode(MDB2_FETCHMODE_ASSOC);
84        }
85
86        // XXX 上書きインストール時にDBを変更するケースを想定し第1引数を与えている。
87        $this->dbFactory = SC_DB_DBFactory_Ex::getInstance($this->conn->dsn['phptype']);
88        $this->dbFactory->initObjQuery($this);
89
90        $this->force_run = $force_run;
91    }
92
93    /**
94     * シングルトンの SC_Query インスタンスを取得する.
95     *
96     * @param  string   $dsn       データソース名
97     * @param  boolean  $force_run エラーが発生しても処理を続行する場合 true
98     * @param  boolean  $new       新規に接続を行うかどうか
99     * @return SC_Query シングルトンの SC_Query インスタンス
100     */
101    public static function getSingletonInstance($dsn = '', $force_run = false, $new = false)
102    {
103        $objThis = SC_Query_Ex::getPoolInstance($dsn);
104        if (is_null($objThis)) {
105            $objThis = SC_Query_Ex::setPoolInstance(new SC_Query_Ex($dsn, $force_run, $new), $dsn);
106        }
107        /*
108         * 歴史的な事情で、このメソッドの呼び出し元は参照で受け取る確率がある。
109         * 退避しているインスタンスをそのまま返すと、退避している SC_Query の
110         * プロパティを直接書き換えることになる。これを回避するため、クローンを返す。
111         * 厳密な意味でのシングルトンではないが、パフォーマンス的に大差は無い。
112         */
113
114        return clone $objThis;
115    }
116
117    /**
118     * エラー判定を行う.
119     *
120     * @deprecated PEAR::isError() を使用して下さい
121     * @return boolean
122     */
123    public function isError()
124    {
125        if (PEAR::isError($this->conn)) {
126            return true;
127        }
128
129        return false;
130    }
131
132    /**
133     * COUNT文を実行する.
134     *
135     * @param  string  $table       テーブル名
136     * @param  string  $where       where句
137     * @param  array   $arrWhereVal プレースホルダ
138     * @return integer 件数
139     */
140    public function count($table, $where = '', $arrWhereVal = array())
141    {
142        return $this->get('COUNT(*)', $table, $where, $arrWhereVal);
143    }
144
145    /**
146     * EXISTS文を実行する.
147     *
148     * @param  string  $table       テーブル名
149     * @param  string  $where       where句
150     * @param  array   $arrWhereVal プレースホルダ
151     * @return boolean 有無
152     */
153    public function exists($table, $where = '', $arrWhereVal = array())
154    {
155        $sql_inner = $this->getSql('*', $table, $where, $arrWhereVal);
156        $sql = "SELECT CASE WHEN EXISTS($sql_inner) THEN 1 ELSE 0 END";
157        $res = $this->getOne($sql, $arrWhereVal);
158
159        return (bool) $res;
160    }
161
162    /**
163     * SELECT文を実行する.
164     *
165     * @param  string     $cols        カラム名. 複数カラムの場合はカンマ区切りで書く
166     * @param  string     $from        テーブル名
167     * @param  string     $where       WHERE句
168     * @param  array      $arrWhereVal プレースホルダ
169     * @param  integer    $fetchmode   使用するフェッチモード。デフォルトは MDB2_FETCHMODE_ASSOC。
170     * @return array|null
171     */
172    public function select($cols, $from = '', $where = '', $arrWhereVal = array(), $fetchmode = MDB2_FETCHMODE_ASSOC)
173    {
174        $sqlse = $this->getSql($cols, $from, $where, $arrWhereVal);
175
176        return $this->getAll($sqlse, $arrWhereVal, $fetchmode);
177    }
178
179    /**
180     * 直前に実行されたSQL文を取得する.
181     *
182     * @param  boolean $disp trueの場合、画面出力を行う.
183     * @return string  SQL文
184     */
185    public function getLastQuery($disp = true)
186    {
187        $sql = $this->conn->last_query;
188        if ($disp) {
189            echo $sql . ";<br />\n";
190        }
191
192        return $sql;
193    }
194
195    /**
196     * トランザクションをコミットする.
197     *
198     * @return MDB2_OK 成功した場合は MDB2_OK;
199     *         失敗した場合は PEAR::Error オブジェクト
200     */
201    public function commit()
202    {
203        return $this->conn->commit();
204    }
205
206    /**
207     * トランザクションを開始する.
208     *
209     * @return MDB2_OK 成功した場合は MDB2_OK;
210     *         失敗した場合は PEAR::Error オブジェクト
211     */
212    public function begin()
213    {
214        return $this->conn->beginTransaction();
215    }
216
217    /**
218     * トランザクションをロールバックする.
219     *
220     * @return MDB2_OK 成功した場合は MDB2_OK;
221     *         失敗した場合は PEAR::Error オブジェクト
222     */
223    public function rollback()
224    {
225        return $this->conn->rollback();
226    }
227
228    /**
229     * トランザクションが開始されているかチェックする.
230     *
231     * @return boolean トランザクションが開始されている場合 true
232     */
233    public function inTransaction()
234    {
235        return $this->conn->inTransaction();
236    }
237
238    /**
239     * 更新系の SQL を実行する.
240     *
241     * この関数は SC_Query::query() のエイリアスです.
242     *
243     * FIXME MDB2::exec() の実装であるべき
244     */
245    public function exec($str, $arrVal = array())
246    {
247        return $this->query($str, $arrVal);
248    }
249
250    /**
251     * クエリを実行し、結果行毎にコールバック関数を適用する
252     *
253     * @param  callback $function  コールバック先
254     * @param  string   $sql       SQL クエリ
255     * @param  array    $arrVal    プリペアドステートメントの実行時に使用される配列。配列の要素数は、クエリ内のプレースホルダの数と同じでなければなりません。
256     * @param  integer  $fetchmode 使用するフェッチモード。デフォルトは DB_FETCHMODE_ASSOC。
257     * @return boolean  結果
258     */
259    public function doCallbackAll($cbFunc, $sql, $arrVal = array(), $fetchmode = MDB2_FETCHMODE_ASSOC)
260    {
261        $sql = $this->dbFactory->sfChangeMySQL($sql);
262
263        $sth =& $this->prepare($sql);
264        if (PEAR::isError($sth) && $this->force_run) {
265            return;
266        }
267
268        $affected =& $this->execute($sth, $arrVal);
269        if (PEAR::isError($affected) && $this->force_run) {
270            return;
271        }
272
273        while ($data = $affected->fetchRow($fetchmode)) {
274            $result = call_user_func($cbFunc, $data);
275            if ($result === false) {
276                break;
277            }
278        }
279        $sth->free();
280
281        return $result;
282    }
283
284    /**
285     * クエリを実行し、全ての行を返す
286     *
287     * @param  string  $sql       SQL クエリ
288     * @param  array   $arrVal    プリペアドステートメントの実行時に使用される配列。配列の要素数は、クエリ内のプレースホルダの数と同じでなければなりません。
289     * @param  integer $fetchmode 使用するフェッチモード。デフォルトは DB_FETCHMODE_ASSOC。
290     * @return array   データを含む2次元配列。失敗した場合に 0 または DB_Error オブジェクトを返します。
291     */
292    public function getAll($sql, $arrVal = array(), $fetchmode = MDB2_FETCHMODE_ASSOC)
293    {
294        $sql = $this->dbFactory->sfChangeMySQL($sql);
295
296        $sth =& $this->prepare($sql);
297        if (PEAR::isError($sth) && $this->force_run) {
298            return;
299        }
300
301        $affected =& $this->execute($sth, $arrVal);
302        if (PEAR::isError($affected) && $this->force_run) {
303            return;
304        }
305
306        // MySQL での不具合対応のため、一旦変数に退避
307        $arrRet = $affected->fetchAll($fetchmode);
308
309        // PREPAREの解放
310        $sth->free();
311
312        return $arrRet;
313    }
314
315    /**
316     * 構築した SELECT 文を取得する.
317     *
318     * クラス変数から WHERE 句を組み立てる場合、$arrWhereVal を経由してプレースホルダもクラス変数のもので上書きする。
319     * @param  string $cols        SELECT 文に含めるカラム名
320     * @param  string $from        SELECT 文に含めるテーブル名
321     * @param  string $where       SELECT 文に含める WHERE 句
322     * @param  mixed  $arrWhereVal プレースホルダ(参照)
323     * @return string 構築済みの SELECT 文
324     */
325    public function getSql($cols, $from = '', $where = '', &$arrWhereVal = null)
326    {
327        $dbFactory = SC_DB_DBFactory_Ex::getInstance();
328
329        $sqlse = "SELECT $cols";
330
331        if (strlen($from) === 0) {
332            $sqlse .= ' ' . $dbFactory->getDummyFromClauseSql();
333        } else {
334            $sqlse .= " FROM $from";
335        }
336
337        // 引数の$whereを優先する。
338        if (strlen($where) >= 1) {
339            $sqlse .= " WHERE $where";
340        } elseif (strlen($this->where) >= 1) {
341            $sqlse .= ' WHERE ' . $this->where;
342            // 実行時と同じくキャストしてから評価する (空文字を要素1の配列と評価させる意図)
343            $arrWhereValForEval = (array) $arrWhereVal;
344            if (empty($arrWhereValForEval)) {
345                $arrWhereVal = $this->arrWhereVal;
346            }
347        }
348
349        $sqlse .= ' ' . $this->groupby . ' ' . $this->order . ' ' . $this->option;
350
351        return $sqlse;
352    }
353
354    /**
355     * SELECT 文の末尾に付与する SQL を設定する.
356     *
357     * この関数で設定した値は SC_Query::getSql() で使用されます.
358     *
359     * @param  string   $str 付与する SQL 文
360     * @return SC_Query 自分自身のインスタンス
361     */
362    public function setOption($str)
363    {
364        $this->option = $str;
365
366        return $this;
367    }
368
369    /**
370     * SELECT 文に付与する LIMIT, OFFSET 句を設定する.
371     *
372     * この関数で設定した値は SC_Query::getSql() で使用されます.
373     *
374     * @param  integer  $limit  LIMIT 句に付与する値
375     * @param  integer  $offset OFFSET 句に付与する値
376     * @return SC_Query 自分自身のインスタンス
377     */
378    public function setLimitOffset($limit, $offset = 0)
379    {
380        if (is_numeric($limit) && is_numeric($offset)) {
381            $this->conn->setLimit($limit, $offset);
382        }
383
384        return $this;
385    }
386
387    /**
388     * SELECT 文に付与する GROUP BY 句を設定する.
389     *
390     * この関数で設定した値は SC_Query::getSql() で使用されます.
391     *
392     * @param  string   $str GROUP BY 句に付与する文字列
393     * @return SC_Query 自分自身のインスタンス
394     */
395    public function setGroupBy($str)
396    {
397        if (strlen($str) == 0) {
398            $this->groupby = '';
399        } else {
400            $this->groupby = 'GROUP BY ' . $str;
401        }
402
403        return $this;
404    }
405
406    /**
407     * SELECT 文の WHERE 句に付与する AND 条件を設定する.
408     *
409     * この関数で設定した値は SC_Query::getSql() で使用されます.
410     *
411     * @param  string   $str WHERE 句に付与する AND 条件の文字列
412     * @return SC_Query 自分自身のインスタンス
413     */
414    public function andWhere($str)
415    {
416        if ($this->where != '') {
417            $this->where .= ' AND ' . $str;
418        } else {
419            $this->where = $str;
420        }
421
422        return $this;
423    }
424
425    /**
426     * SELECT 文の WHERE 句に付与する OR 条件を設定する.
427     *
428     * この関数で設定した値は SC_Query::getSql() で使用されます.
429     *
430     * @param  string   $str WHERE 句に付与する OR 条件の文字列
431     * @return SC_Query 自分自身のインスタンス
432     */
433    public function orWhere($str)
434    {
435        if ($this->where != '') {
436            $this->where .= ' OR ' . $str;
437        } else {
438            $this->where = $str;
439        }
440
441        return $this;
442    }
443
444    /**
445     * SELECT 文に付与する WHERE 句を設定する.
446     *
447     * この関数で設定した値は SC_Query::getSql() で使用されます.
448     *
449     * @param  string   $where       WHERE 句に付与する文字列
450     * @param  mixed    $arrWhereVal プレースホルダ
451     * @return SC_Query 自分自身のインスタンス
452     */
453    public function setWhere($where = '', $arrWhereVal = array())
454    {
455        $this->where = $where;
456        $this->arrWhereVal = $arrWhereVal;
457
458        return $this;
459    }
460
461    /**
462     * SELECT 文に付与する ORDER BY 句を設定する.
463     *
464     * この関数で設定した値は SC_Query::getSql() で使用されます.
465     *
466     * @param  string   $str ORDER BY 句に付与する文字列
467     * @return SC_Query 自分自身のインスタンス
468     */
469    public function setOrder($str)
470    {
471        if (strlen($str) == 0) {
472            $this->order = '';
473        } else {
474            $this->order = 'ORDER BY ' . $str;
475        }
476
477        return $this;
478    }
479
480    /**
481     * SELECT 文に付与する LIMIT 句を設定する.
482     *
483     * この関数で設定した値は SC_Query::getSql() で使用されます.
484     *
485     * @param  integer  $limit LIMIT 句に設定する値
486     * @return SC_Query 自分自身のインスタンス
487     */
488    public function setLimit($limit)
489    {
490        if (is_numeric($limit)) {
491            $this->conn->setLimit($limit);
492        }
493
494        return $this;
495    }
496
497    /**
498     * SELECT 文に付与する OFFSET 句を設定する.
499     *
500     * この関数で設定した値は SC_Query::getSql() で使用されます.
501     *
502     * @param  integer  $offset OFFSET 句に設定する値
503     * @return SC_Query 自分自身のインスタンス
504     */
505    public function setOffset($offset)
506    {
507        if (is_numeric($offset)) {
508            $this->conn->setLimit($this->conn->limit, $offset);
509        }
510
511        return $this;
512    }
513
514    /**
515     * INSERT文を実行する.
516     *
517     * @param  string                   $table      テーブル名
518     * @param  array                    $arrVal     array('カラム名' => '値', ...)の連想配列
519     * @param  array                    $arrSql     array('カラム名' => 'SQL文', ...)の連想配列
520     * @param  array                    $arrSqlVal  SQL文の中で使用するプレースホルダ配列
521     * @param  string                   $from       FROM 句・WHERE 句
522     * @param  string                   $arrFromVal FROM 句・WHERE 句で使用するプレースホルダ配列
523     * @return integer|DB_Error|boolean 挿入件数またはエラー(DB_Error, false)
524     */
525    public function insert($table, $arrVal, $arrSql = array(), $arrSqlVal = array(), $from = '', $arrFromVal = array())
526    {
527        $strcol = '';
528        $strval = '';
529        $find = false;
530        $arrValForQuery = array();
531
532        foreach ($arrVal as $key => $val) {
533            $strcol .= $key . ',';
534            if (strcasecmp('Now()', $val) === 0) {
535                $strval .= 'Now(),';
536            } elseif (strcasecmp('CURRENT_TIMESTAMP', $val) === 0) {
537                $strval .= 'CURRENT_TIMESTAMP,';
538            } else {
539                $strval .= '?,';
540                $arrValForQuery[] = $val;
541            }
542            $find = true;
543        }
544
545        foreach ($arrSql as $key => $val) {
546            $strcol .= $key . ',';
547            $strval .= $val . ',';
548            $find = true;
549        }
550
551        $arrValForQuery = array_merge($arrValForQuery, $arrSqlVal);
552
553        if (!$find) {
554            return false;
555        }
556        // 文末の','を削除
557        $strcol = rtrim($strcol, ',');
558        $strval = rtrim($strval, ',');
559        $sqlin = "INSERT INTO $table($strcol) SELECT $strval";
560
561        if (strlen($from) >= 1) {
562            $sqlin .= ' ' . $from;
563            $arrValForQuery = array_merge($arrValForQuery, $arrFromVal);
564        }
565
566        // INSERT文の実行
567        $ret = $this->query($sqlin, $arrValForQuery, false, null, MDB2_PREPARE_MANIP);
568
569        return $ret;
570    }
571
572    /**
573     * UPDATE文を実行する.
574     *
575     * @param string $table        テーブル名
576     * @param array  $arrVal       array('カラム名' => '値', ...)の連想配列
577     * @param string $where        WHERE句
578     * @param array  $arrWhereVal  WHERE句用のプレースホルダ配列 (従来は追加カラム用も兼ねていた)
579     * @param array  $arrRawSql    追加カラム
580     * @param array  $arrRawSqlVal 追加カラム用のプレースホルダ配列
581     * @return
582     */
583    public function update($table, $arrVal, $where = '', $arrWhereVal = array(), $arrRawSql = array(), $arrRawSqlVal = array())
584    {
585        $arrCol = array();
586        $arrValForQuery = array();
587        $find = false;
588
589        foreach ($arrVal as $key => $val) {
590            if (strcasecmp('Now()', $val) === 0) {
591                $arrCol[] = $key . '= Now()';
592            } elseif (strcasecmp('CURRENT_TIMESTAMP', $val) === 0) {
593                $arrCol[] = $key . '= CURRENT_TIMESTAMP';
594            } else {
595                $arrCol[] = $key . '= ?';
596                $arrValForQuery[] = $val;
597            }
598            $find = true;
599        }
600
601        if ($arrRawSql != '') {
602            foreach ($arrRawSql as $key => $val) {
603                $arrCol[] = "$key = $val";
604            }
605        }
606
607        $arrValForQuery = array_merge($arrValForQuery, $arrRawSqlVal);
608
609        if (empty($arrCol)) {
610            return false;
611        }
612
613        // 文末の','を削除
614        $strcol = implode(', ', $arrCol);
615
616        if (is_array($arrWhereVal)) { // 旧版との互換用
617            // プレースホルダー用に配列を追加
618            $arrValForQuery = array_merge($arrValForQuery, $arrWhereVal);
619        }
620
621        $sqlup = "UPDATE $table SET $strcol";
622        if (strlen($where) >= 1) {
623            $sqlup .= " WHERE $where";
624        }
625
626        // UPDATE文の実行
627        return $this->query($sqlup, $arrValForQuery, false, null, MDB2_PREPARE_MANIP);
628    }
629
630    /**
631     * MAX文を実行する.
632     *
633     * @param  string  $table       テーブル名
634     * @param  string  $col         カラム名
635     * @param  string  $where       付与する WHERE 句
636     * @param  array   $arrWhereVal プレースホルダに挿入する値
637     * @return integer MAX文の実行結果
638     */
639    public function max($col, $table, $where = '', $arrWhereVal = array())
640    {
641        $ret = $this->get("MAX($col)", $table, $where, $arrWhereVal);
642
643        return $ret;
644    }
645
646    /**
647     * MIN文を実行する.
648     *
649     * @param  string  $table       テーブル名
650     * @param  string  $col         カラム名
651     * @param  string  $where       付与する WHERE 句
652     * @param  array   $arrWhereVal プレースホルダに挿入する値
653     * @return integer MIN文の実行結果
654     */
655    public function min($col, $table, $where = '', $arrWhereVal = array())
656    {
657        $ret = $this->get("MIN($col)", $table, $where, $arrWhereVal);
658
659        return $ret;
660    }
661
662    /**
663     * SQL を構築して, 特定のカラムの値を取得する.
664     *
665     * @param  string $table       テーブル名
666     * @param  string $col         カラム名
667     * @param  string $where       付与する WHERE 句
668     * @param  array  $arrWhereVal プレースホルダに挿入する値
669     * @return mixed  SQL の実行結果
670     */
671    public function get($col, $table = '', $where = '', $arrWhereVal = array())
672    {
673        $sqlse = $this->getSql($col, $table, $where, $arrWhereVal);
674        // SQL文の実行
675        $ret = $this->getOne($sqlse, $arrWhereVal);
676
677        return $ret;
678    }
679
680    /**
681     * SQL を指定して, 特定のカラムの値を取得する.
682     *
683     * @param  string $sql    実行する SQL
684     * @param  array  $arrVal プレースホルダに挿入する値
685     * @return mixed  SQL の実行結果
686     */
687    public function getOne($sql, $arrVal = array())
688    {
689        $sql = $this->dbFactory->sfChangeMySQL($sql);
690
691        $sth =& $this->prepare($sql);
692        if (PEAR::isError($sth) && $this->force_run) {
693            return;
694        }
695
696        $affected =& $this->execute($sth, $arrVal);
697        if (PEAR::isError($affected) && $this->force_run) {
698            return;
699        }
700
701        // MySQL での不具合対応のため、一旦変数に退避
702        $arrRet = $affected->fetchOne();
703
704        // PREPAREの解放
705        $sth->free();
706
707        return $arrRet;
708    }
709
710    /**
711     * 一行をカラム名をキーとした連想配列として取得
712     *
713     * @param  string  $table       テーブル名
714     * @param  string  $col         カラム名
715     * @param  string  $where       WHERE句
716     * @param  array   $arrWhereVal プレースホルダ配列
717     * @param  integer $fetchmode   使用するフェッチモード。デフォルトは MDB2_FETCHMODE_ASSOC。
718     * @return array   array('カラム名' => '値', ...)の連想配列
719     */
720    public function getRow($col, $table = '', $where = '', $arrWhereVal = array(), $fetchmode = MDB2_FETCHMODE_ASSOC)
721    {
722        $sql = $this->getSql($col, $table, $where, $arrWhereVal);
723        $sql = $this->dbFactory->sfChangeMySQL($sql);
724
725        $sth =& $this->prepare($sql);
726        if (PEAR::isError($sth) && $this->force_run) {
727            return;
728        }
729
730        $affected =& $this->execute($sth, $arrWhereVal);
731        if (PEAR::isError($affected) && $this->force_run) {
732            return;
733        }
734
735        // MySQL での不具合対応のため、一旦変数に退避
736        $arrRet = $affected->fetchRow($fetchmode);
737
738        // PREPAREの解放
739        $sth->free();
740
741        return $arrRet;
742    }
743
744    /**
745     * SELECT 文の実行結果を 1列のみ取得する.
746     *
747     * @param  string $table       テーブル名
748     * @param  string $col         カラム名
749     * @param  string $where       付与する WHERE 句
750     * @param  array  $arrWhereVal プレースホルダに挿入する値
751     * @return array  SQL の実行結果の配列
752     */
753    public function getCol($col, $table = '', $where = '', $arrWhereVal = array())
754    {
755        $sql = $this->getSql($col, $table, $where, $arrWhereVal);
756        $sql = $this->dbFactory->sfChangeMySQL($sql);
757
758        $sth =& $this->prepare($sql);
759        if (PEAR::isError($sth) && $this->force_run) {
760            return;
761        }
762
763        $affected =& $this->execute($sth, $arrWhereVal);
764        if (PEAR::isError($affected) && $this->force_run) {
765            return;
766        }
767
768        // MySQL での不具合対応のため、一旦変数に退避
769        $arrRet = $affected->fetchCol();
770
771        // PREPAREの解放
772        $sth->free();
773
774        return $arrRet;
775    }
776
777    /**
778     * レコードの削除
779     *
780     * @param string $table       テーブル名
781     * @param string $where       WHERE句
782     * @param array  $arrWhereVal プレースホルダ
783     * @return
784     */
785    public function delete($table, $where = '', $arrWhereVal = array())
786    {
787        if (strlen($where) <= 0) {
788            $sqlde = 'DELETE FROM ' . $this->conn->quoteIdentifier($table);
789        } else {
790            $sqlde = 'DELETE FROM ' . $this->conn->quoteIdentifier($table) . ' WHERE ' . $where;
791        }
792        $ret = $this->query($sqlde, $arrWhereVal, false, null, MDB2_PREPARE_MANIP);
793
794        return $ret;
795    }
796
797    /**
798     * 次のシーケンス値を取得する.
799     *
800     * @param string $seq_name 取得するシーケンス名
801     * @param integer 次のシーケンス値
802     */
803    public function nextVal($seq_name)
804    {
805        return $this->conn->nextID($seq_name);
806    }
807
808    /**
809     * 現在のシーケンス値を取得する.
810     *
811     * @param  string  $seq_name 取得するシーケンス名
812     * @return integer 現在のシーケンス値
813     */
814    public function currVal($seq_name)
815    {
816        return $this->conn->currID($seq_name);
817    }
818
819    /**
820     * シーケンス値を設定する.
821     *
822     * @param  string  $seq_name シーケンス名
823     * @param  integer $start    設定するシーケンス値
824     * @return MDB2_OK
825     */
826    public function setVal($seq_name, $start)
827    {
828        $objManager =& $this->conn->loadModule('Manager');
829
830        // XXX 値変更の役割のため、存在チェックは行なわない。存在しない場合、ここでエラーとなる。
831        $ret = $objManager->dropSequence($seq_name);
832        if (PEAR::isError($ret)) {
833            $this->error("setVal -> dropSequence [$seq_name]");
834        }
835
836        $ret = $objManager->createSequence($seq_name, $start);
837        if (PEAR::isError($ret)) {
838            $this->error("setVal -> createSequence [$seq_name] [$start]");
839        }
840
841        return $ret;
842    }
843
844    /**
845     * SQL を実行する.
846     *
847     * FIXME $ignore_errが無視されるようになっているが互換性として問題が無いか確認が必要
848     *
849     * @param  string  $n            実行する SQL 文
850     * @param  array   $arr          プレースホルダに挿入する値
851     * @param  boolean $ignore_err   MDB2切替で無効化されている (エラーが発生しても処理を続行する場合 true)
852     * @param  mixed   $types        プレースホルダの型指定 デフォルトnull = string
853     * @param  mixed   $result_types 返値の型指定またはDML実行(MDB2_PREPARE_MANIP)
854     * @return array   SQL の実行結果の配列
855     */
856    public function query($n ,$arr = array(), $ignore_err = false, $types = null, $result_types = MDB2_PREPARE_RESULT)
857    {
858        $n = $this->dbFactory->sfChangeMySQL($n);
859
860        $sth =& $this->prepare($n, $types, $result_types);
861        if (PEAR::isError($sth) && $this->force_run) {
862            return $sth;
863        }
864
865        $result = $this->execute($sth, $arr);
866        if (PEAR::isError($result) && $this->force_run) {
867            return $sth;
868        }
869
870        // PREPAREの解放
871        $sth->free();
872
873        return $result;
874    }
875
876    /**
877     * シーケンスの一覧を取得する.
878     *
879     * @return array シーケンス名の配列
880     */
881    public function listSequences()
882    {
883        $objManager =& $this->conn->loadModule('Manager');
884
885        return $objManager->listSequences();
886    }
887
888    /**
889     * テーブル一覧を取得する.
890     *
891     * @return array テーブル名の配列
892     */
893    public function listTables()
894    {
895        return $this->dbFactory->listTables($this);
896    }
897
898    /**
899     * テーブルのカラム一覧を取得する.
900     *
901     * @param  string $table テーブル名
902     * @return array  指定のテーブルのカラム名の配列
903     */
904    public function listTableFields($table)
905    {
906        $objManager =& $this->conn->loadModule('Manager');
907
908        return $objManager->listTableFields($table);
909    }
910
911    /**
912     * テーブルのインデックス一覧を取得する.
913     *
914     * @param  string $table テーブル名
915     * @return array  指定のテーブルのインデックス一覧
916     */
917    public function listTableIndexes($table)
918    {
919        $objManager =& $this->conn->loadModule('Manager');
920
921        return $objManager->listTableIndexes($table);
922    }
923
924    /**
925     * テーブルにインデックスを付与する
926     *
927     * @param string $table      テーブル名
928     * @param string $name       インデックス名
929     * @param array  $definition フィールド名など 通常のフィールド指定時は、$definition=array('fields' => array('フィールド名' => array()));
930     *               MySQLのtext型フィールドを指定する場合は $definition['length'] = 'text_field(NNN)' が必要
931     */
932    public function createIndex($table, $name, $definition)
933    {
934        $definition = $this->dbFactory->sfGetCreateIndexDefinition($table, $name, $definition);
935        $objManager =& $this->conn->loadModule('Manager');
936
937        return $objManager->createIndex($table, $name, $definition);
938    }
939
940    /**
941     * テーブルにインデックスを破棄する
942     *
943     * @param string $table テーブル名
944     * @param string $name  インデックス名
945     */
946    public function dropIndex($table, $name)
947    {
948        $objManager =& $this->conn->loadModule('Manager');
949
950        return $objManager->dropIndex($table, $name);
951    }
952
953    /**
954     * テーブルの詳細情報を取得する。
955     *
956     * @param  string $table テーブル名
957     * @return array  テーブル情報の配列
958     */
959    public function getTableInfo($table)
960    {
961        $objManager =& $this->conn->loadModule('Reverse');
962
963        return $objManager->tableInfo($table, NULL);
964    }
965
966    /**
967     * 値を適切にクォートする.
968     *
969     * TODO MDB2 に対応するための暫定的な措置.
970     *      プレースホルダが使用できない実装があるため.
971     *      本来であれば, MDB2::prepare() を適切に使用するべき
972     *
973     * @see MDB2::quote()
974     * @param  string $val クォートを行う文字列
975     * @return string クォートされた文字列
976     */
977    public function quote($val)
978    {
979        return $this->conn->quote($val);
980    }
981
982    /**
983     * パラメーターの連想配列から, テーブルに存在する列のみを取得する.
984     *
985     * @param string $table テーブル名
986     * @param array プレースホルダの連想配列
987     * @return array テーブルに存在する列のみ抽出した連想配列
988     */
989    public function extractOnlyColsOf($table, $arrParams)
990    {
991        $arrCols = $this->listTableFields($table);
992        $arrResults = array();
993        foreach ($arrParams as $key => $val) {
994            if (in_array($key, $arrCols)) {
995                $arrResults[$key] = $val;
996            }
997        }
998
999        return $arrResults;
1000    }
1001
1002    /**
1003     * プリペアドステートメントを構築する.
1004     *
1005     * @access private
1006     * @param  string                $sql          プリペアドステートメントを構築する SQL
1007     * @param  mixed                 $types        プレースホルダの型指定 デフォルト null
1008     * @param  mixed                 $result_types 返値の型指定またはDML実行(MDB2_PREPARE_MANIP)、nullは指定無し
1009     * @return MDB2_Statement_Common プリペアドステートメントインスタンス
1010     */
1011    public function prepare($sql, $types = null, $result_types = MDB2_PREPARE_RESULT)
1012    {
1013        $sth =& $this->conn->prepare($sql, $types, $result_types);
1014        if (PEAR::isError($sth)) {
1015            $msg = $this->traceError($sth, $sql);
1016            $this->error($msg);
1017        }
1018
1019        return $sth;
1020    }
1021
1022    /**
1023     * プリペアドクエリを実行する.
1024     *
1025     * @access private
1026     * @param MDB2_Statement_Common プリペアドステートメントインスタンス
1027     * @param  array       $arrVal プレースホルダに挿入する配列
1028     * @return MDB2_Result 結果セットのインスタンス
1029     */
1030    public function execute(&$sth, $arrVal = array())
1031    {
1032        // #1658 (SC_Query の各種メソッドでプレースホルダの数に誤りがあるとメモリリークが発生する) 対応
1033        // TODO 現状は PEAR 内のバックトレースを抑制することで、メモリーリークの影響を小さくしている。
1034        //      根本的には、そのバックトレースが、どこに居座っているかを特定して、対策すべき。
1035        $pear_property =& PEAR5::getStaticProperty('PEAR_Error', 'skiptrace');
1036        $bak = $pear_property;
1037        $pear_property = true;
1038
1039        $arrStartInfo =& $this->lfStartDbTraceLog($sth, $arrVal);
1040        $affected =& $sth->execute((array) $arrVal);
1041        $this->lfEndDbTraceLog($arrStartInfo, $sth, $arrVal);
1042
1043        $pear_property = $bak;
1044
1045        if (PEAR::isError($affected)) {
1046            $sql = isset($sth->query) ? $sth->query : '';
1047            $msg = $this->traceError($affected, $sql, $arrVal);
1048            $this->error($msg);
1049        }
1050        $this->conn->last_query = stripslashes($sth->query);
1051
1052        return $affected;
1053    }
1054
1055    /**
1056     * エラーの内容をトレースする.
1057     *
1058     * XXX trigger_error で処理する場合、1024文字以内に抑える必要がある。
1059     * XXX 重要な情報を先頭に置き、冗長になりすぎないように留意する。
1060     * @access private
1061     * @param  PEAR::Error $error  PEAR::Error インスタンス
1062     * @param  string      $sql    エラーの発生した SQL 文
1063     * @param  array       $arrVal プレースホルダ
1064     * @return string      トレースしたエラー文字列
1065     */
1066    public function traceError($error, $sql = '', $arrVal = false)
1067    {
1068        $err = "SQL: [$sql]\n";
1069        if ($arrVal !== false) {
1070            $err .= 'PlaceHolder: [' . var_export($arrVal, true) . "]\n";
1071        }
1072        $err .= $error->getMessage() . "\n";
1073        $err .= rtrim($error->getUserInfo()) . "\n";
1074
1075        // PEAR::MDB2 内部のスタックトレースを出力する場合、下記のコメントを外す。
1076        // $err .= GC_Utils_Ex::toStringBacktrace($error->getBackTrace());
1077        return $err;
1078    }
1079
1080    /**
1081     * エラー処理
1082     */
1083    public function error($msg)
1084    {
1085        $msg = "DB処理でエラーが発生しました。\n" . $msg;
1086        if (!$this->force_run) {
1087            trigger_error($msg, E_USER_ERROR);
1088        } else {
1089            GC_Utils_Ex::gfPrintLog($msg, ERROR_LOG_REALFILE, true);
1090        }
1091    }
1092
1093    /**
1094     * SQLクエリの結果セットのカラム名だけを取得する
1095     *
1096     * @param string $n   実行する SQL 文
1097     * @param array  $arr プレースホルダに挿入する値
1098     * @param boolean エラーが発生しても処理を続行する場合 true
1099     * @param  mixed $types        プレースホルダの型指定 デフォルトnull = string
1100     * @param  mixed $result_types 返値の型指定またはDML実行(MDB2_PREPARE_MANIP)
1101     * @return array 実行結果の配列
1102     */
1103    public function getQueryDefsFields($n ,$arr = array(), $ignore_err = false, $types = null, $result_types = MDB2_PREPARE_RESULT)
1104    {
1105        $n = $this->dbFactory->sfChangeMySQL($n);
1106
1107        $sth =& $this->prepare($n, $types, $result_types);
1108        if (PEAR::isError($sth) && ($this->force_run || $ignore_err)) {
1109            return;
1110        }
1111
1112        $result = $this->execute($sth, $arr);
1113        if (PEAR::isError($result) && ($this->force_run || $ignore_err)) {
1114            return;
1115        }
1116        $arrRet = $result->getColumnNames();
1117        // PREPAREの解放
1118        $sth->free();
1119
1120        return $arrRet;
1121    }
1122
1123    /**
1124     * SQL の実行ログ (トレースログ) を書き出す
1125     *
1126     * @param string 実行するSQL文
1127     * @param  array $arrVal プレースホルダに挿入する配列
1128     * @return void
1129     */
1130    private function lfStartDbTraceLog(&$objSth, &$arrVal)
1131    {
1132        if (!defined('SQL_QUERY_LOG_MODE') || SQL_QUERY_LOG_MODE === 0) {
1133            return;
1134        }
1135        $arrInfo =& $GLOBALS['_SC_Query_TraceLogInfo'];
1136        if (!isset($arrInfo['http_request_id'])) {
1137            $arrInfo['http_request_id'] = uniqid();
1138        }
1139
1140        $arrStartInfo = array(
1141            'http_request_id'   => $arrInfo['http_request_id'],
1142            'time_start'        => microtime(true),
1143            'count'             => ++$arrInfo['count'],
1144        );
1145
1146        // ログモード1の場合、開始はログに出力しない
1147        if (SQL_QUERY_LOG_MODE === 1) {
1148            return $arrStartInfo;
1149        }
1150
1151        $msg = "[execute start {$arrStartInfo['http_request_id']}#{$arrStartInfo['count']}]\n"
1152             . 'SQL: ' . $objSth->query . "\n"
1153             . 'PlaceHolder: ' . var_export($arrVal, true) . "\n";
1154        GC_Utils_Ex::gfPrintLog($msg, DB_LOG_REALFILE);
1155
1156        return $arrStartInfo;
1157    }
1158
1159    /**
1160     * SQL の実行ログ (トレースログ) を書き出す
1161     *
1162     * @param string 実行するSQL文
1163     * @param  array $arrVal プレースホルダに挿入する配列
1164     * @return void
1165     */
1166    private function lfEndDbTraceLog(&$arrStartInfo, &$objSth, &$arrVal)
1167    {
1168        if (!defined('SQL_QUERY_LOG_MODE') || SQL_QUERY_LOG_MODE === 0) {
1169            return;
1170        }
1171        $msg = "[execute end {$arrStartInfo['http_request_id']}#{$arrStartInfo['count']}]\n";
1172
1173        $timeEnd = microtime(true);
1174        $timeExecTime = $timeEnd - $arrStartInfo['time_start'];
1175
1176        // ログモード1の場合、
1177        if (SQL_QUERY_LOG_MODE === 1) {
1178            // 規定時間より速い場合、ログに出力しない
1179            if (!defined('SQL_QUERY_LOG_MIN_EXEC_TIME') || $timeExecTime < (float) SQL_QUERY_LOG_MIN_EXEC_TIME) {
1180                return;
1181            }
1182            // 開始時にログ出力していないため、ここで実行内容を出力する
1183            $msg .= 'SQL: ' . $objSth->query . "\n";
1184            $msg .= 'PlaceHolder: ' . var_export($arrVal, true) . "\n";
1185        }
1186
1187        $msg .= 'execution time: ' . sprintf('%.2f sec', $timeExecTime) . "\n";
1188        GC_Utils_Ex::gfPrintLog($msg, DB_LOG_REALFILE);
1189    }
1190
1191    /**
1192     * インスタンスをプールする
1193     *
1194     * @param  SC_Query $objThis プールするインスタンス
1195     * @param  string   $dsn     データソース名
1196     * @return SC_Query プールしたインスタンス
1197     */
1198    public static function setPoolInstance(&$objThis, $dsn = '')
1199    {
1200        $key_str = serialize($dsn);
1201
1202        return SC_Query_Ex::$arrPoolInstance[$key_str] = $objThis;
1203    }
1204
1205    /**
1206     * プールしているインスタンスを取得する
1207     *
1208     * @param  string        $dsn データソース名
1209     * @return SC_Query|null
1210     */
1211    public static function getPoolInstance($dsn = '')
1212    {
1213        $key_str = serialize($dsn);
1214        if (isset(SC_Query_Ex::$arrPoolInstance[$key_str])) {
1215            return SC_Query_Ex::$arrPoolInstance[$key_str];
1216        }
1217    }
1218}
Note: See TracBrowser for help on using the repository browser.