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

Revision 23440, 40.6 KB checked in by pineray, 7 years ago (diff)

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

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