root/dev/common/includes/class.db_cache.php @ 391

Revision 391, 11.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
3require_once('db.php');
4
5//! mysqli file-cached query class. Manages SQL queries to a MySQL DB using mysqli.
6class DBCachedQuery
7{
8    //! Set up a mysql cached query object with default values.
9    function DBCachedQuery()
10    {
11        static $totalexectime = 0;
12                $this->totalexectime_ = &$totalexectime;
13        $this->executed_ = false;
14        $this->_cache = array();
15        $this->_cached = false;
16
17        // this is the minimum runtime a query has to run to be
18        // eligible for caching in seconds
19        $this->_minruntime = 0.05;
20
21        // maximum size of a cached result set (512kB)
22        $this->_maxcachesize = 524288;
23        $this->d = true;
24    }
25
26    //! Check if this query has been cached and the cache valid.
27
28    /*
29     * \return true if this query has been cached and the cache is valid.
30     */
31    function checkCache()
32    {
33        // only cache selects
34        // we don't use select ... into so there is no problem
35        $this->_sql = str_replace(array("\r\n", "\n"), ' ', $this->_sql);
36        if (strtolower(substr($this->_sql, 0, 6)) != 'select' && strtolower(substr($this->_sql, 0, 4)) != 'show')
37        {
38            // this is no select, update the table
39            $this->markAffectedTables();
40            return false;
41        }
42
43        if (file_exists(KB_CACHEDIR.'/qcache_qry_'.$this->_hash))
44        {
45            $this->_mtime = filemtime(KB_CACHEDIR.'/qcache_qry_'.$this->_hash);
46            /// Remove cached queries more than an hour old.
47            if (time() - $this->_mtime > 3600 )
48            {
49                unlink(KB_CACHEDIR.'/qcache_qry_'.$this->_hash);
50                return false;
51            }
52            if ($this->isCacheValid())
53            {
54                return true;
55            }
56        }
57
58        return false;
59    }
60
61    //! Extract all tables affected by a database modification.
62
63    //! The resulting list is set internally to this object.
64    function parseSQL()
65    {
66        // gets all involved tables for a select statement
67        $text = strtolower($this->_sql).' ';
68
69        // we try to get the text from 'from' to 'where' because all involved
70        // tables are declared in that part
71        $from = strpos($text, 'from')+5;
72        if (!$to = strpos($text, 'where'))
73        {
74            $to = strlen($text);
75        }
76        $parse = trim(substr($text, $from, $to-$from));
77
78        $tables = array();
79        if (strpos($parse, ',') !== false)
80        {
81            // , is a synonym for join so we'll replace them
82            $parse = str_replace(',', ' join ', $parse);
83        }
84
85        $parse = 'join '.$parse;
86        if (strpos($parse, 'join'))
87        {
88            // if this query is a join we parse it with regexp to get all tables
89            preg_match_all('/join (.*?) /', $parse, $match);
90            $tables = $match[1];
91        }
92        else
93        {
94            // no join so it is hopefully a simple table select
95            $tables[] = $parse;
96        }
97
98        $this->_usedtables = $tables;
99    }
100
101    //! Check if the cached query is valid.
102
103    /*! Determines whether the tables used by a query have been modified
104     * since the query was cached
105     */
106    function isCacheValid()
107    {
108        // check if cachefiles are stil valid
109
110        // first, we need to get all involved tables
111        $this->parseSQL();
112
113        foreach ($this->_usedtables as $table)
114        {
115            $file = KB_CACHEDIR.'/qcache_tbl_'.trim($table);
116            if (file_exists($file))
117            {
118                // if one of the tables is outdated, the query is outdated
119                if ($this->_mtime < filemtime($file))
120                {
121                    return false;
122                }
123            }
124        }
125        return true;
126    }
127
128    //! Marks all tables affected by a database modification
129    function markAffectedTables()
130    {
131        // this function invalidates cache files for touched tables
132        $text = trim(strtolower($this->_sql));
133        $text = str_replace(array('ignore','`', "\r\n", "\n"), '', $text);
134        $text = str_replace('(', ' (', $text);
135        $text = str_replace(',', ', ', $text);
136        $ta = preg_split('/\s/', $text, 0, PREG_SPLIT_NO_EMPTY);
137
138        // check for sql keywords and get the table from the appropriate position
139        $tables = array();
140        if ($ta[0] == 'update')
141        {
142            $tables[] = $ta[1];
143        }
144        elseif ($ta[0] == 'insert')
145        {
146            $tables[] = $ta[2];
147        }
148        elseif ($ta[0] == 'replace')
149        {
150            $tables[] = $ta[2];
151        }
152        elseif ($ta[0] == 'delete' && $ta[1] == 'from')
153        {
154            $tables[] = $ta[2];
155        }
156        elseif ($ta[0] == 'delete')
157        {
158            $i = 1;
159            while($ta[$i] != 'from')
160            {
161                $tables[] = $ta[$i];
162                $i++;
163            }
164        }elseif ($ta[0] == 'drop')
165        {
166            $tables[] = $ta[2];
167        }
168        elseif ($ta[0] == 'alter')
169        {
170            return false;
171        }
172        elseif ($ta[0] == 'create')
173        {
174            return false;
175        }
176        else
177        {
178            var_dump($ta);
179            trigger_error('No suitable handler for query found.',E_USER_WARNING);
180            return false;
181        }
182
183        foreach ($tables as $table)
184        {
185            $file = KB_CACHEDIR.'/qcache_tbl_'.$table;
186            @touch($file);
187        }
188        // refresh php's filestatcache so we dont get wrong timestamps on changed files
189        clearstatcache();
190    }
191
192    //! Generate the query cache.
193
194    //! Serialise a query and write to file.
195    function genCache()
196    {
197        // this function fetches all rows and writes the data into a textfile
198
199        // don't attemp to cache updates!
200        if (strtolower(substr($this->_sql, 0, 6)) != 'select' && strtolower(substr($this->_sql, 0, 4)) != 'show')
201        {
202            return false;
203        }
204
205        $bsize = 0;
206        while ($row = $this->getRow())
207        {
208            $this->_cache[] = $row;
209
210            // if the bytesize of the table exceeds the limit we'll abort
211            // the cache generation and leave this query unbuffered
212            $bsize += strlen(join('', $row));
213            if ($bsize > $this->_maxcachesize)
214            {
215                $this->_cache[] = array();
216                $this->_cached = false;
217                $this->rewind();
218                return false;
219            }
220        }
221
222        // write data into textfile
223        file_put_contents(KB_CACHEDIR.'/qcache_qry_'.$this->_hash, serialize($this->_cache));
224
225        $this->_cached = true;
226        $this->_currrow = 0;
227        $this->executed_ = true;
228    }
229
230    //! Read a cached query from file.
231    function loadCache()
232    {
233        // loads the cachefile into the memory
234        $this->_cache = unserialize(file_get_contents(KB_CACHEDIR.'/qcache_qry_'.$this->_hash));
235
236        $this->_cached = true;
237        $this->_currrow = 0;
238        $this->executed_ = true;
239    }
240
241    //! Execute an SQL string.
242
243/*
244 * If DB_HALTONERROR is set then this will exit on an error.
245 * \return false on error or true if successful.
246 */
247    function execute($sql)
248    {
249        $this->_sql = trim($sql);
250        $this->_hash = md5($this->_sql);
251        $this->_cache = array();
252        $this->_cached = false;
253
254        if ($this->checkCache())
255        {
256            $this->loadCache();
257            $this->queryCachedCount(true);
258            return true;
259        }
260
261        // we got no or no valid cache so open the connection and run the query
262        $this->dbconn_ = new DBConnection;
263
264        $t1 = strtok(microtime(), ' ') + strtok('');
265
266        $this->resid_ = mysql_query($sql, $this->dbconn_->id());
267
268        if (!$this->resid_ || mysql_errno($this->dbconn_->id()))
269        {
270            if(defined('KB_PROFILE'))
271                        {
272                                DBDebug::recordError("Database error: ".$this->dbconn_->id()->error);
273                                DBDebug::recordError("SQL: ".$this->_sql);
274                        }
275            if (DB_HALTONERROR === true)
276            {
277                echo "Database error: ".mysql_error($this->dbconn_->id())."<br/>";
278                echo "SQL: ".$this->_sql."<br/>";
279                exit;
280            }
281            else
282            {
283                return false;
284            }
285        }
286
287        $this->exectime_ = strtok(microtime(), ' ') + strtok('') - $t1;
288        $this->totalexectime_ += $this->exectime_;
289        $this->executed_ = true;
290
291        if(defined('KB_PROFILE')) DBDebug::profile($sql);
292
293        // if the query was too slow we'll fetch all rows and run it cached
294        if ($this->exectime_ > $this->_minruntime)
295        {
296            $this->genCache();
297        }
298
299        $this->queryCount(true);
300        return true;
301    }
302
303    //! Return the count of queries performed.
304
305    /*!
306     * \param $increase if true then increment the count.
307     * \return the count of queries so far.
308     */
309    function queryCount($increase = false)
310    {
311        static $count;
312
313        if ($increase)
314        {
315            $count++;
316        }
317
318        return $count;
319    }
320
321    //! Return the count of cached queries performed.
322
323    /*!
324     * \param $increase if true then increment the count.
325     * \return the count of queries so far.
326     */
327    function queryCachedCount($increase = false)
328    {
329        static $count;
330
331        if ($increase)
332        {
333            $count++;
334        }
335
336        return $count;
337    }
338
339    //! Return the number of rows returned by the last query.
340    function recordCount()
341    {
342        if ($this->_cached)
343        {
344            return count($this->_cache);
345        }
346        return mysql_num_rows($this->resid_);
347    }
348
349    //! Return the next row of results from the last query.
350    function getRow()
351    {
352        if ($this->_cached)
353        {
354            if (!isset($this->_cache[$this->_currrow]))
355            {
356                return false;
357            }
358            // return the current row and increase the pointer by one
359            return $this->_cache[$this->_currrow++];
360        }
361        if (is_resource($this->resid_))
362        {
363            return mysql_fetch_assoc($this->resid_);
364        }
365        return false;
366    }
367
368    //! Reset list of results to return the first row from the last query.
369    function rewind()
370    {
371        if ($this->_cached)
372        {
373            $this->_currrow = 0;
374        }
375        @mysql_data_seek($this->resid_, 0);
376    }
377
378    //! Return the auto-increment ID from the last insert operation.
379    function getInsertID()
380    {
381        return mysql_insert_id();
382    }
383
384    //! Return the execution time of the last query.
385    function execTime()
386    {
387        return $this->exectime_;
388    }
389
390    //! Return true if a query has been executed or false if none has been.
391    function executed()
392    {
393        return $this->executed_;
394    }
395
396    //! Return the most recent error message for the DB connection.
397    function getErrorMsg()
398    {
399        $msg = $this->sql_."<br>";
400        $msg .= "Query failed. ".mysql_error($this->dbconn_->id());
401
402        return $msg;
403    }
404    //Not implemented with mysql library
405    function autocommit($commit = true)
406    {
407        return false;
408    }
409
410    //Not implemented with mysql library
411    function rollback()
412    {
413        return false;
414    }
415}
416?>
Note: See TracBrowser for help on using the browser.