root/branch/rev3/common/includes/class.db_cache.php @ 245

Revision 245, 8.5 KB (checked in by exi, 13 years ago)

Fixed SQL Query caching.
Changed userportraits to use img/portraits.
Added JS code for IE/PNG-fix.

Line 
1<?php
2
3class DBCachedQuery
4{
5    function DBCachedQuery()
6    {
7        $this->executed_ = false;
8        $this->_cache = array();
9        $this->_cached = false;
10
11        // this is the minimum runtime a query has to run to be
12        // eligible for caching in seconds
13        $this->_minruntime = 0.1;
14
15        // maximum size of a cached result set (512kB)
16        $this->_maxcachesize = 524288;
17        $this->d = true;
18    }
19
20    function checkCache()
21    {
22        // only cache selects
23        // we don't use select ... into so there is no problem
24        if (strtolower(substr($this->_sql, 0, 6)) != 'select' && strtolower(substr($this->_sql, 0, 4)) != 'show')
25        {
26            // this is no select, update the table
27            $this->markAffectedTables();
28            return false;
29        }
30
31        if (file_exists(KB_CACHEDIR.'/qcache_qry_'.$this->_hash))
32        {
33            $this->_mtime = filemtime(KB_CACHEDIR.'/qcache_qry_'.$this->_hash);
34            if ($this->isCacheValid())
35            {
36                return true;
37            }
38        }
39
40        return false;
41    }
42
43    function parseSQL()
44    {
45        // gets all involved tables for a select statement
46        $text = strtolower($this->_sql).' ';
47
48        // we try to get the text from 'from' to 'where' because all involved
49        // tables are declared in that part
50        $from = strpos($text, 'from')+5;
51        if (!$to = strpos($text, 'where'))
52        {
53            $to = strlen($text);
54        }
55        $parse = trim(substr($text, $from, $to-$from));
56
57        $tables = array();
58        if (strpos($parse, ',') !== false)
59        {
60            // , is a synonym for join so we'll replace them
61            $parse = str_replace(',', ' join ', $parse);
62        }
63        if (strpos($parse, 'join'))
64        {
65            // if this query is a join we parse it with regexp to get all tables
66            preg_match_all('/join (.*?) /', $parse, $match);
67            $tables = $match[1];
68        }
69        else
70        {
71            // no join so it is hopefully a simple table select
72            $tables[] = $parse;
73        }
74
75        $this->_usedtables = $tables;
76    }
77
78    function isCacheValid()
79    {
80        // check if cachefiles are stil valid
81
82        // first, we need to get all involved tables
83        $this->parseSQL();
84
85        foreach ($this->_usedtables as $table)
86        {
87            $file = KB_CACHEDIR.'/qcache_tbl_'.trim($table);
88            if (file_exists($file))
89            {
90                // if one of the tables is outdated, the query is outdated
91                if ($this->_mtime < filemtime($file))
92                {
93                    return false;
94                }
95            }
96        }
97        return true;
98    }
99
100    function markAffectedTables()
101    {
102        // this function invalidates cache files for touched tables
103        $text = trim(strtolower($this->_sql));
104        $text = str_replace(array('ignore','`', "\r\n", "\n"), '', $text);
105        $ta = preg_split('/ /', $text, 0, PREG_SPLIT_NO_EMPTY);
106
107        // check for sql keywords and get the table from the appropriate position
108        $tables = array();
109        if ($ta[0] == 'update')
110        {
111            $tables[] = $ta[1];
112        }
113        elseif ($ta[0] == 'insert')
114        {
115            $tables[] = $ta[2];
116        }
117        elseif ($ta[0] == 'replace')
118        {
119            $tables[] = $ta[2];
120        }
121        elseif ($ta[0] == 'delete')
122        {
123            $tables[] = $ta[2];
124        }
125        elseif ($ta[0] == 'alter')
126        {
127            return false;
128        }
129        elseif ($ta[0] == 'create')
130        {
131            return false;
132        }
133        else
134        {
135            var_dump($ta);
136            trigger_error('No suitable handler for query found.',E_USER_WARNING);
137            return false;
138        }
139
140        foreach ($tables as $table)
141        {
142            $file = KB_CACHEDIR.'/qcache_tbl_'.$table;
143            touch($file);
144        }
145        // refresh php's filestatcache so we dont get wrong timestamps on changed files
146        clearstatcache();
147    }
148
149    function genCache()
150    {
151        // this function fetches all rows and writes the data into a textfile
152
153        // don't attemp to cache updates!
154        if (strtolower(substr($this->_sql, 0, 6)) != 'select' && strtolower(substr($this->_sql, 0, 4)) != 'show')
155        {
156            return false;
157        }
158
159        $bsize = 0;
160        while ($row = $this->getRow())
161        {
162            $this->_cache[] = $row;
163
164            // if the bytesize of the table exceeds the limit we'll abort
165            // the cache generation and leave this query unbuffered
166            $bsize += join('', $row);
167            if ($bsize > $this->_maxcachesize)
168            {
169                $this->_cache[] = array();
170                $this->_cached = false;
171                $this->rewind();
172                return false;
173            }
174        }
175
176        // write data into textfile
177        file_put_contents(KB_CACHEDIR.'/qcache_qry_'.$this->_hash, serialize($this->_cache));
178
179        $this->_cached = true;
180        $this->_currrow = 0;
181        $this->executed_ = true;
182    }
183
184    function loadCache()
185    {
186        // loads the cachefile into the memory
187        $this->_cache = unserialize(file_get_contents(KB_CACHEDIR.'/qcache_qry_'.$this->_hash));
188
189        $this->_cached = true;
190        $this->_currrow = 0;
191        $this->executed_ = true;
192    }
193
194    function execute($sql)
195    {
196        $this->_sql = trim($sql);
197        $this->_hash = md5($this->_sql);
198        $this->_cache = array();
199        $this->_cached = false;
200
201        if ($this->checkCache())
202        {
203            $this->loadCache();
204            $this->queryCachedCount(true);
205            return true;
206        }
207
208        // we got no or no valid cache so open the connection and run the query
209        $this->dbconn_ = new DBConnection;
210
211        $t1 = strtok(microtime(), ' ') + strtok('');
212
213        $this->resid_ = mysql_query($sql, $this->dbconn_->id());
214
215        if ($this->resid_ == false)
216        {
217            if (DB_HALTONERROR === true)
218            {
219                echo "Database error: ".mysql_error($this->dbconn_->id())."<br/>";
220                echo "SQL: ".$this->_sql."<br/>";
221                exit;
222            }
223            else
224            {
225                return false;
226            }
227        }
228
229        $this->exectime_ = strtok(microtime(), ' ') + strtok('') - $t1;
230        $this->executed_ = true;
231
232        if (KB_PROFILE == 2)
233        {
234            file_put_contents('/tmp/profile.lst', $sql."\nExecution time: ".$this->exectime_."\n", FILE_APPEND);
235        }
236
237        // if the query was too slow we'll fetch all rows and run it cached
238        if ($this->exectime_ > $this->_minruntime)
239        {
240            $this->genCache();
241        }
242
243        $this->queryCount(true);
244        return true;
245    }
246
247    function queryCount($increase = false)
248    {
249        static $count;
250
251        if ($increase)
252        {
253            $count++;
254        }
255
256        return $count;
257    }
258
259    function queryCachedCount($increase = false)
260    {
261        static $count;
262
263        if ($increase)
264        {
265            $count++;
266        }
267
268        return $count;
269    }
270
271    function recordCount()
272    {
273        if ($this->_cached)
274        {
275            return count($this->_cache);
276        }
277        return mysql_num_rows($this->resid_);
278    }
279
280    function getRow()
281    {
282        if ($this->_cached)
283        {
284            if (!isset($this->_cache[$this->_currrow]))
285            {
286                return false;
287            }
288            // return the current row and increase the pointer by one
289            return $this->_cache[$this->_currrow++];
290        }
291        if (is_resource($this->resid_))
292        {
293            return mysql_fetch_assoc($this->resid_);
294        }
295        return false;
296    }
297
298    function rewind()
299    {
300        if ($this->_cached)
301        {
302            $this->_currrow = 0;
303        }
304        @mysql_data_seek($this->resid_, 0);
305    }
306
307    function getInsertID()
308    {
309        return mysql_insert_id();
310    }
311
312    function execTime()
313    {
314        return $this->exectime_;
315    }
316
317    function executed()
318    {
319        return $this->executed_;
320    }
321
322    function getErrorMsg()
323    {
324        $msg = $this->sql_."<br>";
325        $msg .= "Query failed. ".mysql_error($this->dbconn_->id());
326
327        return $msg;
328    }
329}
330?>
Note: See TracBrowser for help on using the browser.