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

Revision 391, 25.4 KB (checked in by kovell, 11 years ago)

Fixes: kill_detail comment posting works and non-existent kll_id gives correct error, t3 subsystems have correct location, potential conflicts with old mods and db classes removed.

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