root/dev/common/includes/class.db.mysqli.php @ 491

Revision 491, 25.3 KB (checked in by kovell, 10 years ago)

Installation packages updated. Fetcher does not set session ID. DB queries check if charset method exists before use. History mod is history.

Line 
1<?php
2// mssql: select SCOPE_IDENTITY() AS id
3// postgresql: INSERT INTO mytable (lastname) VALUES ('Cher') RETURNING id;
4
5//! mysqli connection class.
6//! Establishes the connection to the database.
7class DBConnection_mysqli
8{
9    //! Set up a mysqli DB connection.
10    function DBConnection_mysqli()
11    {
12        static $conn_id;
13
14        if ($conn_id)
15        {
16            $this->id_ = $conn_id;
17            return;
18        }
19        if(defined('DB_PORT'))
20        {
21            if (!$this->id_ = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME, DB_PORT))
22            die("Unable to connect to mysql database.");
23            if(method_exists($this->id_,'set_charset')) $this->id_->set_charset('utf8');
24        }
25        else
26        {
27            if (!$this->id_ = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME))
28            die("Unable to connect to mysql database.");
29            if(method_exists($this->id_,'set_charset')) $this->id_->set_charset('utf8');
30        }
31
32        //mysqli_select_db(DB_NAME);
33        $conn_id = $this->id_;
34    }
35    //! Return the connection id for this connection. Used for connection specific commands.
36    function id()
37    {
38        return $this->id_;
39    }
40    //! Return the number of rows affected by a query.
41    function affectedRows()
42    {
43        return mysqli_affected_rows($this->id_);
44    }
45}
46//! mysqli uncached query class. Manages SQL queries to a MySQL DB using mysqli.
47class DBNormalQuery_mysqli
48{
49    //! Prepare a connection for a new mysqli query.
50    function DBNormalQuery_mysqli()
51    {
52        $this->executed_ = false;
53        $this->dbconn_ = new DBConnection_mysqli;
54        static $totalexectime = 0;
55                $this->totalexectime_ = &$totalexectime;
56    }
57    //! Return the count of queries performed.
58
59    /*!
60     * \param $increase if true then increment the count.
61     * \return the count of queries so far.
62     */
63    function queryCount($increase = false)
64    {
65        static $count;
66
67        if ($increase)
68        {
69            $count++;
70        }
71
72        return $count;
73    }
74    //! Return the count of cached queries performed - 0 for uncache queries.
75    function queryCachedCount($increase = false)
76    {
77        return 0;
78    }
79    //! Execute an SQL string.
80
81    /*
82     * If DB_HALTONERROR is set then this will exit on an error.
83     * \return false on error or true if successful.
84     */
85    function execute($sql)
86    {
87        $t1 = strtok(microtime(), ' ') + strtok('');
88
89                //if(isset($this->resid_)) $this->resid_->free();
90
91        $this->resid_ = mysqli_query($this->dbconn_->id(),$sql);
92
93        if ($this->resid_ === false || $this->dbconn_->id()->errno)
94        {
95            if(defined('KB_PROFILE'))
96                        {
97                                DBDebug::recordError("Database error: ".$this->dbconn_->id()->error);
98                                DBDebug::recordError("SQL: ".$sql);
99                        }
100            if (defined('DB_HALTONERROR') && DB_HALTONERROR)
101            {
102                echo "Database error: " . $this->dbconn_->id()->error . "<br>";
103                echo "SQL: " . $sql . "<br>";
104                exit;
105            }
106            else
107            {
108                return false;
109            }
110        }
111
112        $this->exectime_ = strtok(microtime(), ' ') + strtok('') - $t1;
113        $this->totalexectime_ += $this->exectime_;
114        $this->executed_ = true;
115
116        if(defined('KB_PROFILE')) DBDebug::profile($sql);
117
118        $this->queryCount(true);
119
120        return true;
121    }
122    //! Return the number of rows returned by the last query.
123    function recordCount()
124    {
125        if ($this->resid_)
126        {
127            return $this->resid_->num_rows;
128        }
129        return false;
130    }
131    //! Return the next row of results from the last query.
132    function getRow()
133    {
134        if ($this->resid_)
135        {
136            return $this->resid_->fetch_assoc();
137        }
138        return false;
139    }
140    //! Reset list of results to return the first row from the last query.
141    function rewind()
142    {
143        @mysqli_data_seek($this->resid_, 0);
144    }
145    //! Return the auto-increment ID from the last insert operation.
146    function getInsertID()
147    {
148        return $this->dbconn_->id()->insert_id;
149    }
150    //! Return the execution time of the last query.
151    function execTime()
152    {
153        return $this->exectime_;
154    }
155    //! Return true if a query has been executed or false if none has been.
156    function executed()
157    {
158        return $this->executed_;
159    }
160    //! Return the most recent error message for the DB connection.
161    function getErrorMsg()
162    {
163        $msg = $this->sql_ . "<br>";
164        $msg .= "Query failed. " . mysqli_error($this->dbconn_->id());
165
166        return $msg;
167    }
168    //! Set the autocommit status.
169
170    /*! The default of true commits after every query.
171     * If set to false the queries will not be commited until autocommit is set
172     * to true.
173     *  \param $commit The new autocommit status.
174     *  \return true on success and false on failure.
175     */
176    function autocommit($commit = true)
177    {
178        return $this->dbconn_->id()->autocommit($commit);
179    }
180    //! Rollback all queries in the current transaction.
181    function rollback()
182    {
183        return mysqli_rollback($this->dbconn_->id());
184    }
185}
186//! mysqli file-cached query class. Manages SQL queries to a MySQL DB using mysqli.
187class DBCachedQuery_mysqli
188{
189    //! Set up a mysqli cached query object with default values.
190    function DBCachedQuery_mysqli()
191    {
192        static $totalexectime = 0;
193                $this->totalexectime_ = &$totalexectime;
194        $this->executed_ = false;
195        $this->_cache = array();
196        $this->_cached = false;
197
198        // this is the minimum runtime a query has to run to be
199        // eligible for caching in seconds
200        $this->_minruntime = 0.1;
201
202        // maximum size of a cached result set (512kB)
203        $this->_maxcachesize = 524288;
204        $this->d = true;
205    }
206    //! Check if this query has been cached and the cache valid.
207
208    /*
209     * \return true if this query has been cached and the cache is valid.
210     */
211    function checkCache()
212    {
213        // only cache selects
214        // we don't use select ... into so there is no problem
215        $this->_sql = str_replace(array("\r\n", "\n"), ' ', $this->_sql);
216        if (strtolower(substr($this->_sql, 0, 6)) != 'select' && strtolower(substr($this->_sql, 0, 4)) != 'show')
217        {
218            // this is no select, update the table
219            $this->markAffectedTables();
220            return false;
221        }
222
223        if (file_exists(KB_CACHEDIR.'/qcache_qry_'.$this->_hash))
224        {
225            $this->_mtime = filemtime(KB_CACHEDIR.'/qcache_qry_'.$this->_hash);
226            /// Remove cached queries more than an hour old.
227            if (time() - $this->_mtime > 3600 )
228            {
229                unlink(KB_CACHEDIR.'/qcache_qry_'.$this->_hash);
230                return false;
231            }
232            if ($this->isCacheValid())
233            {
234                return true;
235            }
236        }
237
238        return false;
239    }
240
241    //! Extract all tables affected by a database modification.
242
243    //! The resulting list is set internally to this object.
244    function parseSQL($sql)
245    {
246        // gets all involved tables for a select statement
247        $sql = strtolower($sql).' ';
248
249        // we try to get the text from 'from' to 'where' because all involved
250        // tables are declared in that part
251        $from = strpos($sql, 'from')+5;
252                if($from > strlen($sql)) return '';
253                // if there is a subquery then recurse into the string between the next
254                // from and first unclosed ) or where
255                $from2 = strpos($sql, 'from', $from);
256                if($from2) $sql = substr_replace($sql, $this->parseSQL(substr($sql,$from2 - 1)), $from2);
257
258        if (!$to = strpos($sql, 'where'))
259        {
260            $to = strlen($sql);
261        }
262                // Find an unmatched ')'.
263                $bracketpos = $from;
264                $countbr = 0;
265                while($bracketpos < $to && $countbr >=0)
266                {
267                        $bracketpos++;
268                        if($sql[$bracketpos] == '(') $countbr++;
269                        elseif($sql[$bracketpos] == ')') $countbr++;
270                }
271                $to = $bracketpos;
272
273        $parse = trim(substr($sql, $from, $to-$from));
274                $parse = str_replace('`', ' ', $parse);
275
276        $tables = array();
277        if (strpos($parse, ',') !== false)
278        {
279            // , is a synonym for join so we'll replace them
280            $parse = str_replace(',', ' join ', $parse);
281        }
282
283        if (strpos($parse, 'join'))
284        {
285            // if this query is a join we parse it with regexp to get all tables
286                        $parse = 'join '.$parse;
287            preg_match_all('/join\s+([^ ]+)\s/', $parse, $match);
288            $this->_usedtables = $this->_usedtables + $match[1];
289        }
290        else
291        {
292            // no join so it is hopefully a simple table select
293            $this->_usedtables[] = preg_replace('/\s.*/', '', $parse);
294        }
295                return substr_replace($sql, '', $from, $to-$from);
296    }
297    //! Check if the cached query is valid.
298
299    /*! Determines whether the tables used by a query have been modified
300     * since the query was cached
301     */
302    function isCacheValid()
303    {
304        // check if cachefiles are still valid
305                $this->_usedtables = array();
306        // first, we need to get all involved tables
307        $this->parseSQL($this->_sql);
308
309                foreach ($this->_usedtables as $table)
310        {
311            $file = KB_CACHEDIR.'/qcache_tbl_'.trim($table);
312            if (file_exists($file))
313            {
314                // if one of the tables is outdated, the query is outdated
315                if ($this->_mtime <= filemtime($file))
316                {
317                    return false;
318                }
319            }
320        }
321        return true;
322    }
323    //! Marks all tables affected by a database modification
324    function markAffectedTables()
325    {
326        // this function invalidates cache files for touched tables
327        $text = trim(strtolower($this->_sql));
328        $text = str_replace(array('ignore','`', "\r\n", "\n"), '', $text);
329        $text = str_replace('(', ' (', $text);
330        $ta = preg_split('/\s/', $text, 0, PREG_SPLIT_NO_EMPTY);
331
332        // check for sql keywords and get the table from the appropriate position
333        $tables = array();
334        if ($ta[0] == 'update')
335        {
336            $tables[] = $ta[1];
337        }
338        elseif ($ta[0] == 'insert')
339        {
340            $tables[] = $ta[2];
341        }
342        elseif ($ta[0] == 'replace')
343        {
344            $tables[] = $ta[2];
345        }
346        elseif ($ta[0] == 'delete')
347        {
348            $tables[] = $ta[2];
349        }elseif ($ta[0] == 'drop')
350        {
351            $tables[] = $ta[2];
352        }
353        elseif ($ta[0] == 'alter')
354        {
355            return false;
356        }
357        elseif ($ta[0] == 'create')
358        {
359            return false;
360        }
361        else
362        {
363            var_dump($ta);
364            trigger_error('No suitable handler for query found.',E_USER_WARNING);
365            return false;
366        }
367
368        foreach ($tables as $table)
369        {
370            $file = KB_CACHEDIR.'/qcache_tbl_'.$table;
371            @touch($file);
372        }
373        // refresh php's filestatcache so we dont get wrong timestamps on changed files
374        clearstatcache();
375    }
376    //! Generate the query cache.
377
378    //! Serialise a query and write to file.
379    function genCache()
380    {
381        // this function fetches all rows and writes the data into a textfile
382        // don't attemp to cache updates!
383        if (strtolower(substr($this->_sql, 0, 6)) != 'select' && strtolower(substr($this->_sql, 0, 4)) != 'show')
384        {
385            return false;
386        }
387
388        $bsize = 0;
389        while ($row = $this->getRow())
390        {
391            $this->_cache[] = $row;
392
393            // if the bytesize of the table exceeds the limit we'll abort
394            // the cache generation and leave this query unbuffered
395            $bsize += strlen(join('', $row));
396            if ($bsize > $this->_maxcachesize)
397            {
398                $this->_cache[] = array();
399                $this->_cached = false;
400                $this->rewind();
401                return false;
402            }
403        }
404
405        // write data into textfile
406        file_put_contents(KB_CACHEDIR.'/qcache_qry_'.$this->_hash, serialize($this->_cache));
407
408        $this->_cached = true;
409        $this->_currrow = 0;
410        $this->executed_ = true;
411    }
412    //! Read a cached query from file.
413    function loadCache()
414    {
415        // loads the cachefile into the memory
416        $this->_cache = unserialize(file_get_contents(KB_CACHEDIR.'/qcache_qry_'.$this->_hash));
417
418        $this->_cached = true;
419        $this->_currrow = 0;
420        $this->executed_ = true;
421    }
422
423    //! Execute an SQL string.
424
425    /*
426     * If DB_HALTONERROR is set then this will exit on an error.
427     * \return false on error or true if successful.
428     */
429    function execute($sql)
430    {
431        $this->_sql = trim($sql);
432        $this->_hash = md5($this->_sql);
433        $this->_cache = array();
434        $this->_cached = false;
435
436        if ($this->checkCache())
437        {
438            $this->loadCache();
439            $this->queryCachedCount(true);
440            return true;
441        }
442
443        // we got no or no valid cache so open the connection and run the query
444        $this->dbconn_ = new DBConnection_mysqli();
445                //if(isset($this->resid_)) $this->resid_->free();
446
447                $t1 = strtok(microtime(), ' ') + strtok('');
448
449        $this->resid_ = mysqli_query($this->dbconn_->id(), $sql);
450
451        if (!$this->resid_ || $this->dbconn_->id()->errno)
452        {
453            if(defined('KB_PROFILE'))
454                        {
455                                DBDebug::recordError("Database error: ".$this->dbconn_->id()->error);
456                                DBDebug::recordError("SQL: ".$this->_sql);
457                        }
458            if (DB_HALTONERROR === true)
459            {
460                echo "Database error: ".$this->dbconn_->id()->error."<br/>";
461                echo "SQL: ".$this->_sql."<br/>";
462                exit;
463            }
464            else
465            {
466                return false;
467            }
468        }
469
470        $this->exectime_ = strtok(microtime(), ' ') + strtok('') - $t1;
471        $this->totalexectime_ += $this->exectime_;
472        $this->executed_ = true;
473
474        if(defined('KB_PROFILE')) DBDebug::profile($sql);
475
476        // if the query was too slow we'll fetch all rows and run it cached
477        if ($this->exectime_ > $this->_minruntime)
478        {
479            $this->genCache();
480                        // We will use the cached version now so free the mysqli resource.
481                        // Except now it crashes so we won't.
482                        if(false && $this->_cached)
483                        {
484                                $this->resid_->free();
485                                unset($this->resid_);
486                        }
487        }
488
489        $this->queryCount(true);
490        return true;
491    }
492
493    //! Return the count of queries performed.
494
495    /*!
496     * \param $increase if true then increment the count.
497     * \return the count of queries so far.
498     */
499    function queryCount($increase = false)
500    {
501        static $count;
502
503        if ($increase)
504        {
505            $count++;
506        }
507
508        return $count;
509    }
510    //! Return the count of cached queries performed.
511
512    /*!
513     * \param $increase if true then increment the count.
514     * \return the count of queries so far.
515     */
516    function queryCachedCount($increase = false)
517    {
518        static $count;
519
520        if ($increase)
521        {
522            $count++;
523        }
524
525        return $count;
526    }
527
528    //! Return the number of rows returned by the last query.
529    function recordCount()
530    {
531        if ($this->_cached)
532        {
533            return count($this->_cache);
534        }
535        elseif ($this->resid_)
536        {
537            return $this->resid_->num_rows;
538        }
539        return false;
540    }
541
542    //! Return the next row of results from the last query.
543    function getRow()
544    {
545        if ($this->_cached)
546        {
547            if (!isset($this->_cache[$this->_currrow]))
548            {
549                return false;
550            }
551            // return the current row and increase the pointer by one
552            return $this->_cache[$this->_currrow++];
553        }
554        if ($this->resid_)
555        {
556            return $this->resid_->fetch_assoc();
557        }
558        return false;
559    }
560
561    //! Reset list of results to return the first row from the last query.
562    function rewind()
563    {
564        if ($this->_cached)
565        {
566            $this->_currrow = 0;
567        }
568                @mysqli_data_seek($this->resid_, 0);
569    }
570
571    //! Return the auto-increment ID from the last insert operation.
572    function getInsertID()
573    {
574        return $this->dbconn_->id()->insert_id;
575    }
576
577    //! Return the execution time of the last query.
578    function execTime()
579    {
580        return $this->exectime_;
581    }
582
583    //! Return true if a query has been executed or false if none has been.
584    function executed()
585    {
586        return $this->executed_;
587    }
588
589    //! Return the most recent error message for the DB connection.
590    function getErrorMsg()
591    {
592        $msg = $this->sql_."<br>";
593        $msg .= "Query failed. ".mysqli_error($this->dbconn_->id());
594
595        return $msg;
596    }
597
598    //! Set the autocommit status.
599
600    /*! The default of true commits after every query.
601     * If set to false the queries will not be commited until autocommit is set
602     * to true.
603     *  \param $commit The new autocommit status.
604     *  \return true on success and false on failure.
605     */
606    function autocommit($commit = true)
607    {
608        if(!$this->dbconn_) $this->dbconn_ = new DBConnection_mysqli();
609        return $this->dbconn_->id()->autocommit($commit);
610    }
611
612    //! Rollback all queries in the current transaction.
613    function rollback()
614    {
615        // if there's no connection to the db then there's nothing to roll back
616        if(!$this->dbconn_) return true;
617        return $this->dbconn_->id()->rollback();
618    }
619}
620
621//! mysqli memcached query class. Manages SQL queries to a MySQL DB using mysqli.
622class DBMemcachedQuery_mysqli
623{
624    function DBMemcachedQuery_mysqli()
625    {
626        static $totalexectime = 0;
627                $this->totalexectime_ = &$totalexectime;
628        $this->executed_ = false;
629        $this->_cache = array();
630        $this->_cached = false;
631
632        // this is the minimum runtime a query has to run to be
633        // eligible for caching in seconds
634        $this->_minruntime = 0.1;
635
636        // maximum size of a cached result set (512kB)
637        $this->_maxcachesize = 524288;
638        $this->d = true;
639    }
640
641    //! Check if this query has been cached.
642
643    /*
644     * \return true if this query has been cached.
645     */
646    function checkCache()
647    {
648        global $mc;
649
650        // only cache selects
651        // we don't use select ... into so there is no problem
652        $this->_sql = str_replace(array("\r\n", "\n"), ' ', $this->_sql);
653        if (strtolower(substr($this->_sql, 0, 6)) != 'select' && strtolower(substr($this->_sql, 0, 4)) != 'show')
654        return false;
655
656        $cached = $mc->get(KB_SITE . '_sql_' . $this->_hash);
657        if($cached) {
658            return true;
659        }
660
661        return false;
662    }
663    function genCache()
664    {
665        global $mc;
666
667        // this function fetches all rows and writes the data into a textfile
668
669        // don't attemp to cache updates!
670        if (strtolower(substr($this->_sql, 0, 6)) != 'select' && strtolower(substr($this->_sql, 0, 4)) != 'show')
671        {
672            return false;
673        }
674
675        $bsize = 0;
676        while ($row = $this->getRow())
677        {
678            $this->_cache[] = $row;
679
680            $bsize += strlen(join('', $row));
681            if ($bsize > $this->_maxcachesize)
682            {
683                $this->_cache[] = array();
684                $this->_cached = false;
685                $this->rewind();
686                return false;
687            }
688
689        }
690
691        // write data into textfile
692        $mc->set(KB_SITE . '_sql_' . $this->_hash, $this->_cache, 0, 600);
693
694        $this->_cached = true;
695        $this->_currrow = 0;
696        $this->executed_ = true;
697    }
698
699    //! Execute an SQL string.
700
701    /*
702     * If DB_HALTONERROR is set then this will exit on an error.
703     * \return false on error or true if successful.
704     */
705    function execute($sql)
706    {
707        global $mc;
708
709        $this->_sql = trim($sql);
710        $this->_hash = md5($this->_sql);
711        $this->_cache = array();
712        $this->_cached = false;
713
714        $cached = $mc->get(KB_SITE . '_sql_' . $this->_hash);
715        if($cached) {
716            $this->_cache = $cached;
717            $this->_cached = true;
718            $this->_currrow = 0;
719            $this->executed_ = true;
720            $this->queryCachedCount(true);
721            return true;
722        }
723
724        // we got no or no valid cache so open the connection and run the query
725        $this->dbconn_ = new DBConnection_mysqli;
726                //if(isset($this->resid_)) $this->resid_->free();
727
728        $t1 = strtok(microtime(), ' ') + strtok('');
729
730        $this->resid_ = $this->dbconn_->id()->query($sql);
731
732        if (!$this->resid_ || $this->dbconn_->id()->errno)
733        {
734            if(defined('KB_PROFILE'))
735                        {
736                                DBDebug::recordError("Database error: ".$this->dbconn_->id()->error);
737                                DBDebug::recordError("SQL: ".$this->_sql);
738                        }
739            if (DB_HALTONERROR === true)
740            {
741                echo "Database error: ".$this->dbconn_->id()->error."<br/>";
742                echo "SQL: ".$this->_sql."<br/>";
743                exit;
744            }
745            else
746            {
747                return false;
748            }
749        }
750
751        $this->exectime_ = strtok(microtime(), ' ') + strtok('') - $t1;
752        $this->totalexectime_ += $this->exectime_;
753        $this->executed_ = true;
754
755        if(defined('KB_PROFILE')) DBDebug::profile($sql);
756
757        // if the query was too slow we'll fetch all rows and run it cached
758        $this->genCache();
759
760        $this->queryCount(true);
761        return true;
762    }
763
764    //! Return the count of queries performed.
765
766    /*!
767     * \param $increase if true then increment the count.
768     * \return the count of queries so far.
769     */
770    function queryCount($increase = false)
771    {
772        static $count;
773
774        if ($increase)
775        {
776            $count++;
777        }
778
779        return $count;
780    }
781
782    //! Return the count of cached queries performed.
783
784    /*!
785     * \param $increase if true then increment the count.
786     * \return the count of queries so far.
787     */
788    function queryCachedCount($increase = false)
789    {
790        static $count;
791
792        if ($increase)
793        {
794            $count++;
795        }
796
797        return $count;
798    }
799
800    //! Return the number of rows returned by the last query.
801    function recordCount()
802    {
803        if ($this->_cached)
804        {
805            return count($this->_cache);
806        }
807        elseif ($this->resid_)
808        {
809            return $this->resid_->num_rows;
810        }
811        return false;
812    }
813
814    //! Return the next row of results from the last query.
815    function getRow()
816    {
817        if ($this->_cached)
818        {
819            if (!isset($this->_cache[$this->_currrow]))
820            {
821                return false;
822            }
823            // return the current row and increase the pointer by one
824            return $this->_cache[$this->_currrow++];
825        }
826        if ($this->resid_)
827        {
828            return $this->resid_->fetch_assoc();
829        }
830        return false;
831    }
832
833    //! Reset list of results to return the first row from the last query.
834    function rewind()
835    {
836        if ($this->_cached)
837        {
838            $this->_currrow = 0;
839        }
840        @mysqli_data_seek($this->resid_, 0);
841    }
842
843    //! Return the auto-increment ID from the last insert operation.
844    function getInsertID()
845    {
846        return $this->dbconn_->id()->insert_id;
847    }
848
849    //! Return the execution time of the last query.
850    function execTime()
851    {
852        return $this->exectime_;
853    }
854
855    //! Return true if a query has been executed or false if none has been.
856    function executed()
857    {
858        return $this->executed_;
859    }
860
861    //! Return the most recent error message for the DB connection.
862    function getErrorMsg()
863    {
864        $msg = $this->sql_."<br>";
865        $msg .= "Query failed. ".mysqli_error($this->dbconn_->id());
866
867        return $msg;
868    }
869
870    //! Set the autocommit status.
871
872    /*! The default of true commits after every query.
873     * If set to false the queries will not be commited until autocommit is set
874     * to true.
875     *  \param $commit The new autocommit status.
876     *  \return true on success and false on failure.
877     */
878    function autocommit($commit = true)
879    {
880        if(!$this->dbconn_) $this->dbconn_ = new DBConnection_mysqli();
881        return $this->dbconn_->id()->autocommit($commit);
882    }
883
884    //! Rollback all queries in the current transaction.
885    function rollback()
886    {
887        // if there's no connection to the db then there's nothing to roll back
888        if(!$this->dbconn_) return true;
889        return $this->dbconn_->id()->rollback();
890    }
891}
892?>
Note: See TracBrowser for help on using the browser.