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

Revision 23195, 40.2 KB checked in by pineray, 11 years ago (diff)

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

  • 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        if ($this->inTransaction()) {
204            return $this->conn->commit();
205        } else {
206            return false;
207        }
208    }
209
210    /**
211     * トランザクションを開始する.
212     *
213     * @return MDB2_OK 成功した場合は MDB2_OK;
214     *         失敗した場合は PEAR::Error オブジェクト
215     */
216    public function begin()
217    {
218        if (!$this->inTransaction()) {
219            return $this->conn->beginTransaction();
220        } else {
221            return false;
222        }
223    }
224
225    /**
226     * トランザクションをロールバックする.
227     *
228     * @return MDB2_OK 成功した場合は MDB2_OK;
229     *         失敗した場合は PEAR::Error オブジェクト
230     */
231    public function rollback()
232    {
233        if ($this->inTransaction()) {
234            return $this->conn->rollback();
235        } else {
236            return false;
237        }
238    }
239
240    /**
241     * トランザクションが開始されているかチェックする.
242     *
243     * @return boolean トランザクションが開始されている場合 true
244     */
245    public function inTransaction()
246    {
247        return $this->conn->inTransaction();
248    }
249
250    /**
251     * 更新系の SQL を実行する.
252     *
253     * この関数は SC_Query::query() のエイリアスです.
254     *
255     * FIXME MDB2::exec() の実装であるべき
256     */
257    public function exec($str, $arrVal = array())
258    {
259        return $this->query($str, $arrVal);
260    }
261
262    /**
263     * クエリを実行し、結果行毎にコールバック関数を適用する
264     *
265     * @param  callback $function  コールバック先
266     * @param  string   $sql       SQL クエリ
267     * @param  array    $arrVal    プリペアドステートメントの実行時に使用される配列。配列の要素数は、クエリ内のプレースホルダの数と同じでなければなりません。
268     * @param  integer  $fetchmode 使用するフェッチモード。デフォルトは DB_FETCHMODE_ASSOC。
269     * @return boolean  結果
270     */
271    public function doCallbackAll($cbFunc, $sql, $arrVal = array(), $fetchmode = MDB2_FETCHMODE_ASSOC)
272    {
273        $sql = $this->dbFactory->sfChangeMySQL($sql);
274
275        $sth =& $this->prepare($sql);
276        if (PEAR::isError($sth) && $this->force_run) {
277            return;
278        }
279
280        $affected =& $this->execute($sth, $arrVal);
281        if (PEAR::isError($affected) && $this->force_run) {
282            return;
283        }
284
285        while ($data = $affected->fetchRow($fetchmode)) {
286            $result = call_user_func($cbFunc, $data);
287            if ($result === false) {
288                break;
289            }
290        }
291        $sth->free();
292
293        return $result;
294    }
295
296    /**
297     * クエリを実行し、全ての行を返す
298     *
299     * @param  string  $sql       SQL クエリ
300     * @param  array   $arrVal    プリペアドステートメントの実行時に使用される配列。配列の要素数は、クエリ内のプレースホルダの数と同じでなければなりません。
301     * @param  integer $fetchmode 使用するフェッチモード。デフォルトは DB_FETCHMODE_ASSOC。
302     * @return array   データを含む2次元配列。失敗した場合に 0 または DB_Error オブジェクトを返します。
303     */
304    public function getAll($sql, $arrVal = array(), $fetchmode = MDB2_FETCHMODE_ASSOC)
305    {
306        $sql = $this->dbFactory->sfChangeMySQL($sql);
307
308        $sth =& $this->prepare($sql);
309        if (PEAR::isError($sth) && $this->force_run) {
310            return;
311        }
312
313        $affected =& $this->execute($sth, $arrVal);
314        if (PEAR::isError($affected) && $this->force_run) {
315            return;
316        }
317
318        // MySQL での不具合対応のため、一旦変数に退避
319        $arrRet = $affected->fetchAll($fetchmode);
320
321        // PREPAREの解放
322        $sth->free();
323
324        return $arrRet;
325    }
326
327    /**
328     * 構築した SELECT 文を取得する.
329     *
330     * クラス変数から WHERE 句を組み立てる場合、$arrWhereVal を経由してプレースホルダもクラス変数のもので上書きする。
331     * @param  string $cols        SELECT 文に含めるカラム名
332     * @param  string $from        SELECT 文に含めるテーブル名
333     * @param  string $where       SELECT 文に含める WHERE 句
334     * @param  mixed  $arrWhereVal プレースホルダ(参照)
335     * @return string 構築済みの SELECT 文
336     */
337    public function getSql($cols, $from = '', $where = '', &$arrWhereVal = null)
338    {
339        $dbFactory = SC_DB_DBFactory_Ex::getInstance();
340
341        $sqlse = "SELECT $cols";
342
343        if (strlen($from) === 0) {
344            $sqlse .= ' ' . $dbFactory->getDummyFromClauseSql();
345        } else {
346            $sqlse .= " FROM $from";
347        }
348
349        // 引数の$whereを優先する。
350        if (strlen($where) >= 1) {
351            $sqlse .= " WHERE $where";
352        } elseif (strlen($this->where) >= 1) {
353            $sqlse .= ' WHERE ' . $this->where;
354            // 実行時と同じくキャストしてから評価する (空文字を要素1の配列と評価させる意図)
355            $arrWhereValForEval = (array) $arrWhereVal;
356            if (empty($arrWhereValForEval)) {
357                $arrWhereVal = $this->arrWhereVal;
358            }
359        }
360
361        $sqlse .= ' ' . $this->groupby . ' ' . $this->order . ' ' . $this->option;
362
363        return $sqlse;
364    }
365
366    /**
367     * SELECT 文の末尾に付与する SQL を設定する.
368     *
369     * この関数で設定した値は SC_Query::getSql() で使用されます.
370     *
371     * @param  string   $str 付与する SQL 文
372     * @return SC_Query 自分自身のインスタンス
373     */
374    public function setOption($str)
375    {
376        $this->option = $str;
377
378        return $this;
379    }
380
381    /**
382     * SELECT 文に付与する LIMIT, OFFSET 句を設定する.
383     *
384     * この関数で設定した値は SC_Query::getSql() で使用されます.
385     *
386     * @param  integer  $limit  LIMIT 句に付与する値
387     * @param  integer  $offset OFFSET 句に付与する値
388     * @return SC_Query 自分自身のインスタンス
389     */
390    public function setLimitOffset($limit, $offset = 0)
391    {
392        if (is_numeric($limit) && is_numeric($offset)) {
393            $this->conn->setLimit($limit, $offset);
394        }
395
396        return $this;
397    }
398
399    /**
400     * SELECT 文に付与する GROUP BY 句を設定する.
401     *
402     * この関数で設定した値は SC_Query::getSql() で使用されます.
403     *
404     * @param  string   $str GROUP BY 句に付与する文字列
405     * @return SC_Query 自分自身のインスタンス
406     */
407    public function setGroupBy($str)
408    {
409        if (strlen($str) == 0) {
410            $this->groupby = '';
411        } else {
412            $this->groupby = 'GROUP BY ' . $str;
413        }
414
415        return $this;
416    }
417
418    /**
419     * SELECT 文の WHERE 句に付与する AND 条件を設定する.
420     *
421     * この関数で設定した値は SC_Query::getSql() で使用されます.
422     *
423     * @param  string   $str WHERE 句に付与する AND 条件の文字列
424     * @return SC_Query 自分自身のインスタンス
425     */
426    public function andWhere($str)
427    {
428        if ($this->where != '') {
429            $this->where .= ' AND ' . $str;
430        } else {
431            $this->where = $str;
432        }
433
434        return $this;
435    }
436
437    /**
438     * SELECT 文の WHERE 句に付与する OR 条件を設定する.
439     *
440     * この関数で設定した値は SC_Query::getSql() で使用されます.
441     *
442     * @param  string   $str WHERE 句に付与する OR 条件の文字列
443     * @return SC_Query 自分自身のインスタンス
444     */
445    public function orWhere($str)
446    {
447        if ($this->where != '') {
448            $this->where .= ' OR ' . $str;
449        } else {
450            $this->where = $str;
451        }
452
453        return $this;
454    }
455
456    /**
457     * SELECT 文に付与する WHERE 句を設定する.
458     *
459     * この関数で設定した値は SC_Query::getSql() で使用されます.
460     *
461     * @param  string   $where       WHERE 句に付与する文字列
462     * @param  mixed    $arrWhereVal プレースホルダ
463     * @return SC_Query 自分自身のインスタンス
464     */
465    public function setWhere($where = '', $arrWhereVal = array())
466    {
467        $this->where = $where;
468        $this->arrWhereVal = $arrWhereVal;
469
470        return $this;
471    }
472
473    /**
474     * SELECT 文に付与する ORDER BY 句を設定する.
475     *
476     * この関数で設定した値は SC_Query::getSql() で使用されます.
477     *
478     * @param  string   $str ORDER BY 句に付与する文字列
479     * @return SC_Query 自分自身のインスタンス
480     */
481    public function setOrder($str)
482    {
483        if (strlen($str) == 0) {
484            $this->order = '';
485        } else {
486            $this->order = 'ORDER BY ' . $str;
487        }
488
489        return $this;
490    }
491
492    /**
493     * SELECT 文に付与する LIMIT 句を設定する.
494     *
495     * この関数で設定した値は SC_Query::getSql() で使用されます.
496     *
497     * @param  integer  $limit LIMIT 句に設定する値
498     * @return SC_Query 自分自身のインスタンス
499     */
500    public function setLimit($limit)
501    {
502        if (is_numeric($limit)) {
503            $this->conn->setLimit($limit);
504        }
505
506        return $this;
507    }
508
509    /**
510     * SELECT 文に付与する OFFSET 句を設定する.
511     *
512     * この関数で設定した値は SC_Query::getSql() で使用されます.
513     *
514     * @param  integer  $offset OFFSET 句に設定する値
515     * @return SC_Query 自分自身のインスタンス
516     */
517    public function setOffset($offset)
518    {
519        if (is_numeric($offset)) {
520            $this->conn->setLimit($this->conn->limit, $offset);
521        }
522
523        return $this;
524    }
525
526    /**
527     * INSERT文を実行する.
528     *
529     * @param  string                   $table      テーブル名
530     * @param  array                    $arrVal     array('カラム名' => '値', ...)の連想配列
531     * @param  array                    $arrSql     array('カラム名' => 'SQL文', ...)の連想配列
532     * @param  array                    $arrSqlVal  SQL文の中で使用するプレースホルダ配列
533     * @param  string                   $from       FROM 句・WHERE 句
534     * @param  string                   $arrFromVal FROM 句・WHERE 句で使用するプレースホルダ配列
535     * @return integer|DB_Error|boolean 挿入件数またはエラー(DB_Error, false)
536     */
537    public function insert($table, $arrVal, $arrSql = array(), $arrSqlVal = array(), $from = '', $arrFromVal = array())
538    {
539        $strcol = '';
540        $strval = '';
541        $find = false;
542        $arrValForQuery = array();
543
544        foreach ($arrVal as $key => $val) {
545            $strcol .= $key . ',';
546            if (strcasecmp('Now()', $val) === 0) {
547                $strval .= 'Now(),';
548            } elseif (strcasecmp('CURRENT_TIMESTAMP', $val) === 0) {
549                $strval .= 'CURRENT_TIMESTAMP,';
550            } else {
551                $strval .= '?,';
552                $arrValForQuery[] = $val;
553            }
554            $find = true;
555        }
556
557        foreach ($arrSql as $key => $val) {
558            $strcol .= $key . ',';
559            $strval .= $val . ',';
560            $find = true;
561        }
562
563        $arrValForQuery = array_merge($arrValForQuery, $arrSqlVal);
564
565        if (!$find) {
566            return false;
567        }
568        // 文末の','を削除
569        $strcol = rtrim($strcol, ',');
570        $strval = rtrim($strval, ',');
571        $sqlin = "INSERT INTO $table($strcol) SELECT $strval";
572
573        if (strlen($from) >= 1) {
574            $sqlin .= ' ' . $from;
575            $arrValForQuery = array_merge($arrValForQuery, $arrFromVal);
576        }
577
578        // INSERT文の実行
579        $ret = $this->query($sqlin, $arrValForQuery, false, null, MDB2_PREPARE_MANIP);
580
581        return $ret;
582    }
583
584    /**
585     * UPDATE文を実行する.
586     *
587     * @param string $table        テーブル名
588     * @param array  $arrVal       array('カラム名' => '値', ...)の連想配列
589     * @param string $where        WHERE句
590     * @param array  $arrWhereVal  WHERE句用のプレースホルダ配列 (従来は追加カラム用も兼ねていた)
591     * @param array  $arrRawSql    追加カラム
592     * @param array  $arrRawSqlVal 追加カラム用のプレースホルダ配列
593     * @return
594     */
595    public function update($table, $arrVal, $where = '', $arrWhereVal = array(), $arrRawSql = array(), $arrRawSqlVal = array())
596    {
597        $arrCol = array();
598        $arrValForQuery = array();
599        $find = false;
600
601        foreach ($arrVal as $key => $val) {
602            if (strcasecmp('Now()', $val) === 0) {
603                $arrCol[] = $key . '= Now()';
604            } elseif (strcasecmp('CURRENT_TIMESTAMP', $val) === 0) {
605                $arrCol[] = $key . '= CURRENT_TIMESTAMP';
606            } else {
607                $arrCol[] = $key . '= ?';
608                $arrValForQuery[] = $val;
609            }
610            $find = true;
611        }
612
613        if ($arrRawSql != '') {
614            foreach ($arrRawSql as $key => $val) {
615                $arrCol[] = "$key = $val";
616            }
617        }
618
619        $arrValForQuery = array_merge($arrValForQuery, $arrRawSqlVal);
620
621        if (empty($arrCol)) {
622            return false;
623        }
624
625        // 文末の','を削除
626        $strcol = implode(', ', $arrCol);
627
628        if (is_array($arrWhereVal)) { // 旧版との互換用
629            // プレースホルダー用に配列を追加
630            $arrValForQuery = array_merge($arrValForQuery, $arrWhereVal);
631        }
632
633        $sqlup = "UPDATE $table SET $strcol";
634        if (strlen($where) >= 1) {
635            $sqlup .= " WHERE $where";
636        }
637
638        // UPDATE文の実行
639        return $this->query($sqlup, $arrValForQuery, false, null, MDB2_PREPARE_MANIP);
640    }
641
642    /**
643     * MAX文を実行する.
644     *
645     * @param  string  $table       テーブル名
646     * @param  string  $col         カラム名
647     * @param  string  $where       付与する WHERE 句
648     * @param  array   $arrWhereVal プレースホルダに挿入する値
649     * @return integer MAX文の実行結果
650     */
651    public function max($col, $table, $where = '', $arrWhereVal = array())
652    {
653        $ret = $this->get("MAX($col)", $table, $where, $arrWhereVal);
654
655        return $ret;
656    }
657
658    /**
659     * MIN文を実行する.
660     *
661     * @param  string  $table       テーブル名
662     * @param  string  $col         カラム名
663     * @param  string  $where       付与する WHERE 句
664     * @param  array   $arrWhereVal プレースホルダに挿入する値
665     * @return integer MIN文の実行結果
666     */
667    public function min($col, $table, $where = '', $arrWhereVal = array())
668    {
669        $ret = $this->get("MIN($col)", $table, $where, $arrWhereVal);
670
671        return $ret;
672    }
673
674    /**
675     * SQL を構築して, 特定のカラムの値を取得する.
676     *
677     * @param  string $table       テーブル名
678     * @param  string $col         カラム名
679     * @param  string $where       付与する WHERE 句
680     * @param  array  $arrWhereVal プレースホルダに挿入する値
681     * @return mixed  SQL の実行結果
682     */
683    public function get($col, $table = '', $where = '', $arrWhereVal = array())
684    {
685        $sqlse = $this->getSql($col, $table, $where, $arrWhereVal);
686        // SQL文の実行
687        $ret = $this->getOne($sqlse, $arrWhereVal);
688
689        return $ret;
690    }
691
692    /**
693     * SQL を指定して, 特定のカラムの値を取得する.
694     *
695     * @param  string $sql    実行する SQL
696     * @param  array  $arrVal プレースホルダに挿入する値
697     * @return mixed  SQL の実行結果
698     */
699    public function getOne($sql, $arrVal = array())
700    {
701        $sql = $this->dbFactory->sfChangeMySQL($sql);
702
703        $sth =& $this->prepare($sql);
704        if (PEAR::isError($sth) && $this->force_run) {
705            return;
706        }
707
708        $affected =& $this->execute($sth, $arrVal);
709        if (PEAR::isError($affected) && $this->force_run) {
710            return;
711        }
712
713        // MySQL での不具合対応のため、一旦変数に退避
714        $arrRet = $affected->fetchOne();
715
716        // PREPAREの解放
717        $sth->free();
718
719        return $arrRet;
720    }
721
722    /**
723     * 一行をカラム名をキーとした連想配列として取得
724     *
725     * @param  string  $table       テーブル名
726     * @param  string  $col         カラム名
727     * @param  string  $where       WHERE句
728     * @param  array   $arrWhereVal プレースホルダ配列
729     * @param  integer $fetchmode   使用するフェッチモード。デフォルトは MDB2_FETCHMODE_ASSOC。
730     * @return array   array('カラム名' => '値', ...)の連想配列
731     */
732    public function getRow($col, $table = '', $where = '', $arrWhereVal = array(), $fetchmode = MDB2_FETCHMODE_ASSOC)
733    {
734        $sql = $this->getSql($col, $table, $where, $arrWhereVal);
735        $sql = $this->dbFactory->sfChangeMySQL($sql);
736
737        $sth =& $this->prepare($sql);
738        if (PEAR::isError($sth) && $this->force_run) {
739            return;
740        }
741
742        $affected =& $this->execute($sth, $arrWhereVal);
743        if (PEAR::isError($affected) && $this->force_run) {
744            return;
745        }
746
747        // MySQL での不具合対応のため、一旦変数に退避
748        $arrRet = $affected->fetchRow($fetchmode);
749
750        // PREPAREの解放
751        $sth->free();
752
753        return $arrRet;
754    }
755
756    /**
757     * SELECT 文の実行結果を 1列のみ取得する.
758     *
759     * @param  string $table       テーブル名
760     * @param  string $col         カラム名
761     * @param  string $where       付与する WHERE 句
762     * @param  array  $arrWhereVal プレースホルダに挿入する値
763     * @return array  SQL の実行結果の配列
764     */
765    public function getCol($col, $table = '', $where = '', $arrWhereVal = array())
766    {
767        $sql = $this->getSql($col, $table, $where, $arrWhereVal);
768        $sql = $this->dbFactory->sfChangeMySQL($sql);
769
770        $sth =& $this->prepare($sql);
771        if (PEAR::isError($sth) && $this->force_run) {
772            return;
773        }
774
775        $affected =& $this->execute($sth, $arrWhereVal);
776        if (PEAR::isError($affected) && $this->force_run) {
777            return;
778        }
779
780        // MySQL での不具合対応のため、一旦変数に退避
781        $arrRet = $affected->fetchCol();
782
783        // PREPAREの解放
784        $sth->free();
785
786        return $arrRet;
787    }
788
789    /**
790     * レコードの削除
791     *
792     * @param string $table       テーブル名
793     * @param string $where       WHERE句
794     * @param array  $arrWhereVal プレースホルダ
795     * @return
796     */
797    public function delete($table, $where = '', $arrWhereVal = array())
798    {
799        if (strlen($where) <= 0) {
800            $sqlde = 'DELETE FROM ' . $this->conn->quoteIdentifier($table);
801        } else {
802            $sqlde = 'DELETE FROM ' . $this->conn->quoteIdentifier($table) . ' WHERE ' . $where;
803        }
804        $ret = $this->query($sqlde, $arrWhereVal, false, null, MDB2_PREPARE_MANIP);
805
806        return $ret;
807    }
808
809    /**
810     * 次のシーケンス値を取得する.
811     *
812     * @param string $seq_name 取得するシーケンス名
813     * @param integer 次のシーケンス値
814     */
815    public function nextVal($seq_name)
816    {
817        return $this->conn->nextID($seq_name);
818    }
819
820    /**
821     * 現在のシーケンス値を取得する.
822     *
823     * @param  string  $seq_name 取得するシーケンス名
824     * @return integer 現在のシーケンス値
825     */
826    public function currVal($seq_name)
827    {
828        return $this->conn->currID($seq_name);
829    }
830
831    /**
832     * シーケンス値を設定する.
833     *
834     * @param  string  $seq_name シーケンス名
835     * @param  integer $start    設定するシーケンス値
836     * @return MDB2_OK
837     */
838    public function setVal($seq_name, $start)
839    {
840        $objManager =& $this->conn->loadModule('Manager');
841
842        // XXX 値変更の役割のため、存在チェックは行なわない。存在しない場合、ここでエラーとなる。
843        $ret = $objManager->dropSequence($seq_name);
844        if (PEAR::isError($ret)) {
845            $this->error("setVal -> dropSequence [$seq_name]");
846        }
847
848        $ret = $objManager->createSequence($seq_name, $start);
849        if (PEAR::isError($ret)) {
850            $this->error("setVal -> createSequence [$seq_name] [$start]");
851        }
852
853        return $ret;
854    }
855
856    /**
857     * SQL を実行する.
858     *
859     * FIXME $ignore_errが無視されるようになっているが互換性として問題が無いか確認が必要
860     *
861     * @param  string  $n            実行する SQL 文
862     * @param  array   $arr          プレースホルダに挿入する値
863     * @param  boolean $ignore_err   MDB2切替で無効化されている (エラーが発生しても処理を続行する場合 true)
864     * @param  mixed   $types        プレースホルダの型指定 デフォルトnull = string
865     * @param  mixed   $result_types 返値の型指定またはDML実行(MDB2_PREPARE_MANIP)
866     * @return array   SQL の実行結果の配列
867     */
868    public function query($n ,$arr = array(), $ignore_err = false, $types = null, $result_types = MDB2_PREPARE_RESULT)
869    {
870        $n = $this->dbFactory->sfChangeMySQL($n);
871
872        $sth =& $this->prepare($n, $types, $result_types);
873        if (PEAR::isError($sth) && $this->force_run) {
874            return $sth;
875        }
876
877        $result = $this->execute($sth, $arr);
878        if (PEAR::isError($result) && $this->force_run) {
879            return $sth;
880        }
881
882        // PREPAREの解放
883        $sth->free();
884
885        return $result;
886    }
887
888    /**
889     * シーケンスの一覧を取得する.
890     *
891     * @return array シーケンス名の配列
892     */
893    public function listSequences()
894    {
895        $objManager =& $this->conn->loadModule('Manager');
896
897        return $objManager->listSequences();
898    }
899
900    /**
901     * テーブル一覧を取得する.
902     *
903     * @return array テーブル名の配列
904     */
905    public function listTables()
906    {
907        return $this->dbFactory->listTables($this);
908    }
909
910    /**
911     * テーブルのカラム一覧を取得する.
912     *
913     * @param  string $table テーブル名
914     * @return array  指定のテーブルのカラム名の配列
915     */
916    public function listTableFields($table)
917    {
918        $objManager =& $this->conn->loadModule('Manager');
919
920        return $objManager->listTableFields($table);
921    }
922
923    /**
924     * テーブルのインデックス一覧を取得する.
925     *
926     * @param  string $table テーブル名
927     * @return array  指定のテーブルのインデックス一覧
928     */
929    public function listTableIndexes($table)
930    {
931        $objManager =& $this->conn->loadModule('Manager');
932
933        return $objManager->listTableIndexes($table);
934    }
935
936    /**
937     * テーブルにインデックスを付与する
938     *
939     * @param string $table      テーブル名
940     * @param string $name       インデックス名
941     * @param array  $definition フィールド名など 通常のフィールド指定時は、$definition=array('fields' => array('フィールド名' => array()));
942     *               MySQLのtext型フィールドを指定する場合は $definition['length'] = 'text_field(NNN)' が必要
943     */
944    public function createIndex($table, $name, $definition)
945    {
946        $definition = $this->dbFactory->sfGetCreateIndexDefinition($table, $name, $definition);
947        $objManager =& $this->conn->loadModule('Manager');
948
949        return $objManager->createIndex($table, $name, $definition);
950    }
951
952    /**
953     * テーブルにインデックスを破棄する
954     *
955     * @param string $table テーブル名
956     * @param string $name  インデックス名
957     */
958    public function dropIndex($table, $name)
959    {
960        $objManager =& $this->conn->loadModule('Manager');
961
962        return $objManager->dropIndex($table, $name);
963    }
964
965    /**
966     * テーブルの詳細情報を取得する。
967     *
968     * @param  string $table テーブル名
969     * @return array  テーブル情報の配列
970     */
971    public function getTableInfo($table)
972    {
973        $objManager =& $this->conn->loadModule('Reverse');
974
975        return $objManager->tableInfo($table, NULL);
976    }
977
978    /**
979     * 値を適切にクォートする.
980     *
981     * TODO MDB2 に対応するための暫定的な措置.
982     *      プレースホルダが使用できない実装があるため.
983     *      本来であれば, MDB2::prepare() を適切に使用するべき
984     *
985     * @see MDB2::quote()
986     * @param  string $val クォートを行う文字列
987     * @return string クォートされた文字列
988     */
989    public function quote($val)
990    {
991        return $this->conn->quote($val);
992    }
993
994    /**
995     * パラメーターの連想配列から, テーブルに存在する列のみを取得する.
996     *
997     * @param string $table テーブル名
998     * @param array プレースホルダの連想配列
999     * @return array テーブルに存在する列のみ抽出した連想配列
1000     */
1001    public function extractOnlyColsOf($table, $arrParams)
1002    {
1003        $arrCols = $this->listTableFields($table);
1004        $arrResults = array();
1005        foreach ($arrParams as $key => $val) {
1006            if (in_array($key, $arrCols)) {
1007                $arrResults[$key] = $val;
1008            }
1009        }
1010
1011        return $arrResults;
1012    }
1013
1014    /**
1015     * プリペアドステートメントを構築する.
1016     *
1017     * @access private
1018     * @param  string                $sql          プリペアドステートメントを構築する SQL
1019     * @param  mixed                 $types        プレースホルダの型指定 デフォルト null
1020     * @param  mixed                 $result_types 返値の型指定またはDML実行(MDB2_PREPARE_MANIP)、nullは指定無し
1021     * @return MDB2_Statement_Common プリペアドステートメントインスタンス
1022     */
1023    public function prepare($sql, $types = null, $result_types = MDB2_PREPARE_RESULT)
1024    {
1025        $sth =& $this->conn->prepare($sql, $types, $result_types);
1026        if (PEAR::isError($sth)) {
1027            $msg = $this->traceError($sth, $sql);
1028            $this->error($msg);
1029        }
1030
1031        return $sth;
1032    }
1033
1034    /**
1035     * プリペアドクエリを実行する.
1036     *
1037     * @access private
1038     * @param MDB2_Statement_Common プリペアドステートメントインスタンス
1039     * @param  array       $arrVal プレースホルダに挿入する配列
1040     * @return MDB2_Result 結果セットのインスタンス
1041     */
1042    public function execute(&$sth, $arrVal = array())
1043    {
1044        // #1658 (SC_Query の各種メソッドでプレースホルダの数に誤りがあるとメモリリークが発生する) 対応
1045        // TODO 現状は PEAR 内のバックトレースを抑制することで、メモリーリークの影響を小さくしている。
1046        //      根本的には、そのバックトレースが、どこに居座っているかを特定して、対策すべき。
1047        $pear_property =& PEAR5::getStaticProperty('PEAR_Error', 'skiptrace');
1048        $bak = $pear_property;
1049        $pear_property = true;
1050
1051        $arrStartInfo =& $this->lfStartDbTraceLog($sth, $arrVal);
1052        $affected =& $sth->execute((array) $arrVal);
1053        $this->lfEndDbTraceLog($arrStartInfo, $sth, $arrVal);
1054
1055        $pear_property = $bak;
1056
1057        if (PEAR::isError($affected)) {
1058            $sql = isset($sth->query) ? $sth->query : '';
1059            $msg = $this->traceError($affected, $sql, $arrVal);
1060            $this->error($msg);
1061        }
1062        $this->conn->last_query = stripslashes($sth->query);
1063
1064        return $affected;
1065    }
1066
1067    /**
1068     * エラーの内容をトレースする.
1069     *
1070     * XXX trigger_error で処理する場合、1024文字以内に抑える必要がある。
1071     * XXX 重要な情報を先頭に置き、冗長になりすぎないように留意する。
1072     * @access private
1073     * @param  PEAR::Error $error  PEAR::Error インスタンス
1074     * @param  string      $sql    エラーの発生した SQL 文
1075     * @param  array       $arrVal プレースホルダ
1076     * @return string      トレースしたエラー文字列
1077     */
1078    public function traceError($error, $sql = '', $arrVal = false)
1079    {
1080        $err = "SQL: [$sql]\n";
1081        if ($arrVal !== false) {
1082            $err .= 'PlaceHolder: [' . var_export($arrVal, true) . "]\n";
1083        }
1084        $err .= $error->getMessage() . "\n";
1085        $err .= rtrim($error->getUserInfo()) . "\n";
1086
1087        // PEAR::MDB2 内部のスタックトレースを出力する場合、下記のコメントを外す。
1088        // $err .= GC_Utils_Ex::toStringBacktrace($error->getBackTrace());
1089        return $err;
1090    }
1091
1092    /**
1093     * エラー処理
1094     */
1095    public function error($msg)
1096    {
1097        $msg = "DB処理でエラーが発生しました。\n" . $msg;
1098        if (!$this->force_run) {
1099            trigger_error($msg, E_USER_ERROR);
1100        } else {
1101            GC_Utils_Ex::gfPrintLog($msg, ERROR_LOG_REALFILE, true);
1102        }
1103    }
1104
1105    /**
1106     * SQLクエリの結果セットのカラム名だけを取得する
1107     *
1108     * @param string $n   実行する SQL 文
1109     * @param array  $arr プレースホルダに挿入する値
1110     * @param boolean エラーが発生しても処理を続行する場合 true
1111     * @param  mixed $types        プレースホルダの型指定 デフォルトnull = string
1112     * @param  mixed $result_types 返値の型指定またはDML実行(MDB2_PREPARE_MANIP)
1113     * @return array 実行結果の配列
1114     */
1115    public function getQueryDefsFields($n ,$arr = array(), $ignore_err = false, $types = null, $result_types = MDB2_PREPARE_RESULT)
1116    {
1117        $n = $this->dbFactory->sfChangeMySQL($n);
1118
1119        $sth =& $this->prepare($n, $types, $result_types);
1120        if (PEAR::isError($sth) && ($this->force_run || $ignore_err)) {
1121            return;
1122        }
1123
1124        $result = $this->execute($sth, $arr);
1125        if (PEAR::isError($result) && ($this->force_run || $ignore_err)) {
1126            return;
1127        }
1128        $arrRet = $result->getColumnNames();
1129        // PREPAREの解放
1130        $sth->free();
1131
1132        return $arrRet;
1133    }
1134
1135    /**
1136     * SQL の実行ログ (トレースログ) を書き出す
1137     *
1138     * @param string 実行するSQL文
1139     * @param  array $arrVal プレースホルダに挿入する配列
1140     * @return void
1141     */
1142    private function lfStartDbTraceLog(&$objSth, &$arrVal)
1143    {
1144        if (!defined('SQL_QUERY_LOG_MODE') || SQL_QUERY_LOG_MODE === 0) {
1145            return;
1146        }
1147        $arrInfo =& $GLOBALS['_SC_Query_TraceLogInfo'];
1148        if (!isset($arrInfo['http_request_id'])) {
1149            $arrInfo['http_request_id'] = uniqid();
1150        }
1151
1152        $arrStartInfo = array(
1153            'http_request_id'   => $arrInfo['http_request_id'],
1154            'time_start'        => microtime(true),
1155            'count'             => ++$arrInfo['count'],
1156        );
1157
1158        // ログモード1の場合、開始はログに出力しない
1159        if (SQL_QUERY_LOG_MODE === 1) {
1160            return $arrStartInfo;
1161        }
1162
1163        $msg = "[execute start {$arrStartInfo['http_request_id']}#{$arrStartInfo['count']}]\n"
1164             . 'SQL: ' . $objSth->query . "\n"
1165             . 'PlaceHolder: ' . var_export($arrVal, true) . "\n";
1166        GC_Utils_Ex::gfPrintLog($msg, DB_LOG_REALFILE);
1167
1168        return $arrStartInfo;
1169    }
1170
1171    /**
1172     * SQL の実行ログ (トレースログ) を書き出す
1173     *
1174     * @param string 実行するSQL文
1175     * @param  array $arrVal プレースホルダに挿入する配列
1176     * @return void
1177     */
1178    private function lfEndDbTraceLog(&$arrStartInfo, &$objSth, &$arrVal)
1179    {
1180        if (!defined('SQL_QUERY_LOG_MODE') || SQL_QUERY_LOG_MODE === 0) {
1181            return;
1182        }
1183        $msg = "[execute end {$arrStartInfo['http_request_id']}#{$arrStartInfo['count']}]\n";
1184
1185        $timeEnd = microtime(true);
1186        $timeExecTime = $timeEnd - $arrStartInfo['time_start'];
1187
1188        // ログモード1の場合、
1189        if (SQL_QUERY_LOG_MODE === 1) {
1190            // 規定時間より速い場合、ログに出力しない
1191            if (!defined('SQL_QUERY_LOG_MIN_EXEC_TIME') || $timeExecTime < (float) SQL_QUERY_LOG_MIN_EXEC_TIME) {
1192                return;
1193            }
1194            // 開始時にログ出力していないため、ここで実行内容を出力する
1195            $msg .= 'SQL: ' . $objSth->query . "\n";
1196            $msg .= 'PlaceHolder: ' . var_export($arrVal, true) . "\n";
1197        }
1198
1199        $msg .= 'execution time: ' . sprintf('%.2f sec', $timeExecTime) . "\n";
1200        GC_Utils_Ex::gfPrintLog($msg, DB_LOG_REALFILE);
1201    }
1202
1203    /**
1204     * インスタンスをプールする
1205     *
1206     * @param  SC_Query $objThis プールするインスタンス
1207     * @param  string   $dsn     データソース名
1208     * @return SC_Query プールしたインスタンス
1209     */
1210    public static function setPoolInstance(&$objThis, $dsn = '')
1211    {
1212        $key_str = serialize($dsn);
1213
1214        return SC_Query_Ex::$arrPoolInstance[$key_str] = $objThis;
1215    }
1216
1217    /**
1218     * プールしているインスタンスを取得する
1219     *
1220     * @param  string        $dsn データソース名
1221     * @return SC_Query|null
1222     */
1223    public static function getPoolInstance($dsn = '')
1224    {
1225        $key_str = serialize($dsn);
1226        if (isset(SC_Query_Ex::$arrPoolInstance[$key_str])) {
1227            return SC_Query_Ex::$arrPoolInstance[$key_str];
1228        }
1229    }
1230}
Note: See TracBrowser for help on using the repository browser.