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

Revision 436, 25.2 KB (checked in by kovell, 11 years ago)

Fixes: file query cache correctly updates, feed admin panel more usable.

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            $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            $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.