1<?php
2// +----------------------------------------------------------------------+
3// | PEAR :: Cache :: MDB Container                                       |
4// +----------------------------------------------------------------------+
5// | Copyright (c) 1997-2003 The PHP Group                                |
6// +----------------------------------------------------------------------+
7// | This source file is subject to version 2.0 of the PHP license,       |
8// | that is bundled with this package in the file LICENSE, and is        |
9// | available at through the world-wide-web at                           |
10// | http://www.php.net/license/2_02.txt.                                 |
11// | If you did not receive a copy of the PHP license and are unable to   |
12// | obtain it through the world-wide-web, please send a note to          |
13// | license@php.net so we can mail you a copy immediately.               |
14// +----------------------------------------------------------------------+
15// | Note: This is a MDB-oriented rewrite of Cache/Container/db.php.      |
16// | Thanks to Lukas Smith for his patience in answering my questions     |
17// +----------------------------------------------------------------------+
18// | Author: Lorenzo Alberton <l.alberton at quipo.it>                    |
19// +----------------------------------------------------------------------+
20//
21// $Id: mdb.php 174777 2004-12-15 09:09:33Z dufuz $
22
23require_once 'MDB.php';
24require_once 'Cache/Container.php';
25
26/**
27* PEAR/MDB Cache Container.
28*
29* NB: The field 'changed' has no meaning for the Cache itself. It's just there
30* because it's a good idea to have an automatically updated timestamp
31* field for debugging in all of your tables.
32*
33* A XML MDB-compliant schema example for the table needed is provided.
34* Look at the file "mdb_cache_schema.xml" for that.
35*
36* ------------------------------------------
37* A basic usage example:
38* ------------------------------------------
39*
40* $dbinfo = array(
41*   'database'    => 'dbname',
42*   'phptype'     => 'mysql',
43*   'username'    => 'root',
44*   'password'    => '',
45*   'cache_table' => 'cache'
46* );
47*
48*
49* $cache = new Cache('mdb', $dbinfo);
50* $id = $cache->generateID('testentry');
51*
52* if ($data = $cache->get($id)) {
53*    echo 'Cache hit.<br />Data: '.$data;
54*
55* } else {
56*   $data = 'data of any kind';
57*   $cache->save($id, $data);
58*   echo 'Cache miss.<br />';
59* }
60*
61* ------------------------------------------
62*
63* @author   Lorenzo Alberton <l.alberton at quipo.it>
64* @version  $Id: mdb.php 174777 2004-12-15 09:09:33Z dufuz $
65* @package  Cache
66*/
67class Cache_Container_mdb extends Cache_Container
68{
69
70    /**
71     * Name of the MDB table to store caching data
72     *
73     * @see  Cache_Container_file::$filename_prefix
74     */
75    var $cache_table = '';
76
77    /**
78     * PEAR MDB object
79     *
80     * @var  object PEAR_MDB
81     */
82    var $db;
83
84    /**
85     * Constructor
86     *
87     * @param mixed Array with connection info or dsn string
88     */
89    function Cache_Container_mdb($options)
90    {
91        $this->db = &MDB::Connect($options);
92        if (MDB::isError($this->db)) {
93           return new Cache_Error('MDB::connect failed: '
94                    . $this->db->getMessage(), __FILE__, __LINE__);
95        } else {
96            $this->db->setFetchMode(MDB_FETCHMODE_ASSOC);
97        }
98        $this->setOptions($options, array_merge($this->allowed_options,
99                                         array('dsn', 'cache_table')));
100    }
101
102    /**
103     * Fetch in the db the data that matches input parameters
104     *
105     * @param    string  dataset ID
106     * @param    string  cache group
107     * @return   mixed   dataset value or null/Cache_Error on failure
108     * @access   public
109     */
110    function fetch($id, $group)
111    {
112        $query = 'SELECT cachedata FROM ' . $this->cache_table
113                .' WHERE id='       . $this->db->getTextValue($id)
114                .' AND cachegroup=' . $this->db->getTextValue($group);
115        if ($res = $this->db->query($query)) {
116            if ($this->db->endOfResult($res)) {
117                //no rows returned
118                $data = array(null, null, null);
119            } else {
120                $clob = $this->db->fetchClob($res,0,'cachedata');
121                if (!MDB::isError($clob)) {
122                    $cached_data = '';
123                    while(!$this->db->endOfLOB($clob)) {
124                        if (MDB::isError($error =
125                                    $this->db->readLob($clob,$data,8000)<0)) {
126                            return new Cache_Error('MDB::query failed: '
127                                    . $error->getMessage(), __FILE__, __LINE__);
128                        }
129                        $cached_data .= $data;
130                    }
131                    unset($data);
132                    $this->db->destroyLob($clob);
133                    $this->db->freeResult($res);
134
135                    //finished fetching LOB, now fetch other fields...
136                    $query = 'SELECT userdata, expires FROM ' . $this->cache_table
137                            .' WHERE id='       . $this->db->getTextValue($id)
138                            .' AND cachegroup=' . $this->db->getTextValue($group);
139                    if ($res = $this->db->query($query)) {
140                        $row = $this->db->fetchInto($res);
141                        if (is_array($row)) {
142                            $data = array(
143                                        $row['expires'],
144                                        $this->decode($cached_data),
145                                        $row['userdata']
146                                    );
147                        } else {
148                            $data = array(null, null, null);
149                        }
150                    } else {
151                        $data = array(null, null, null);
152                    }
153                } else {
154                    return new Cache_Error('MDB::query failed: '
155                             . $clob->getMessage(), __FILE__, __LINE__);
156                }
157            }
158            $this->db->freeResult($res);
159        } else {
160            //return new Cache_Error('MDB::query failed: '
161            //          . $result->getMessage(), __FILE__, __LINE__);
162            $data = array(null, null, null);
163        }
164
165        // last used required by the garbage collection
166        $query = 'UPDATE '          . $this->cache_table
167                .' SET changed='    . time()
168                .' WHERE id='       . $this->db->getTextValue($id)
169                .' AND cachegroup=' . $this->db->getTextValue($group);
170
171        $res = $this->db->query($query);
172        if (MDB::isError($res)) {
173            return new Cache_Error('MDB::query failed: '
174                . $this->db->errorMessage($res), __FILE__, __LINE__);
175        }
176        return $data;
177    }
178
179   /**
180     * Stores a dataset in the database
181     *
182     * If dataset_ID already exists, overwrite it with new data,
183     * else insert data in a new record.
184     *
185     * @param    string  dataset ID
186     * @param    mixed   data to be cached
187     * @param    integer expiration time
188     * @param    string  cache group
189     * @param    string  userdata
190     * @access   public
191     */
192    function save($id, $data, $expires, $group, $userdata)
193    {
194        global $db;
195        $this->flushPreload($id, $group);
196
197        $fields = array(
198            'id'        => array(
199                            'Type'   => 'text',
200                            'Value'  => $id,
201                            'Key'    => true
202                        ),
203            'userdata'  => array(
204                            'Type'   => 'integer',
205                            'Value'  => $userdata,
206                            'null'   => ($userdata ? false : true)
207                        ),
208            'expires'   => array(
209                            'Type'   => 'integer',
210                            'Value'  => $this->getExpiresAbsolute($expires)
211                        ),
212            'cachegroup' => array(
213                            'Type'   => 'text',
214                            'Value'  => $group
215                        )
216            );
217
218        $result = $this->db->replace($this->cache_table, $fields);
219
220        if (MDB::isError($result)) {
221            //Var_Dump::display($result);
222            return new Cache_Error('MDB::query failed: '
223                    . $this->db->errorMessage($result), __FILE__, __LINE__);
224        }
225        unset($fields); //end first part of query
226        $query2 = 'UPDATE '   . $this->cache_table
227                 .' SET cachedata=?'
228                 .' WHERE id='. $this->db->getTextValue($id);
229
230        if (($prepared_query = $this->db->prepareQuery($query2))) {
231            $char_lob = array(
232                            'Error' => '',
233                            'Type' => 'data',
234                            'Data' => $this->encode($data)
235                        );
236            if (!MDB::isError($clob = $this->db->createLob($char_lob))) {
237                $this->db->setParamClob($prepared_query,1,$clob,'cachedata');
238                if(MDB::isError($error=$this->db->executeQuery($prepared_query))) {
239                    return new Cache_Error('MDB::query failed: '
240                            . $error->getMessage() , __FILE__, __LINE__);
241                }
242                $this->db->destroyLob($clob);
243            } else {
244                // creation of the handler object failed
245                return new Cache_Error('MDB::query failed: '
246                        . $clob->getMessage() , __FILE__, __LINE__);
247            }
248            $this->db->freePreparedQuery($prepared_query);
249        } else {
250            //prepared query failed
251            return new Cache_Error('MDB::query failed: '
252                    . $prepared_query->getMessage() , __FILE__, __LINE__);
253        }
254    }
255
256    /**
257     * Removes a dataset from the database
258     *
259     * @param    string  dataset ID
260     * @param    string  cache group
261     */
262    function remove($id, $group)
263    {
264        $this->flushPreload($id, $group);
265
266        $query = 'DELETE FROM '     . $this->cache_table
267                .' WHERE id='       . $this->db->getTextValue($id)
268                .' AND cachegroup=' . $this->db->getTextValue($group);
269
270        $res = $this->db->query($query);
271        if (MDB::isError($res)) {
272            return new Cache_Error('MDB::query failed: '
273                    . $this->db->errorMessage($res), __FILE__, __LINE__);
274        }
275    }
276
277    /**
278     * Remove all cached data for a certain group, or empty
279     * the cache table if no group is specified.
280     *
281     * @param    string  cache group
282     */
283    function flush($group = '')
284    {
285        $this->flushPreload();
286
287        if ($group) {
288            $query = 'DELETE FROM '       . $this->cache_table
289                    .' WHERE cachegroup=' . $this->db->getTextValue($group);
290        } else {
291            $query = 'DELETE FROM ' . $this->cache_table;
292        }
293
294        $res = $this->db->query($query);
295        if (MDB::isError($res)) {
296            return new Cache_Error('MDB::query failed: '
297                . $this->db->errorMessage($res), __FILE__, __LINE__);
298        }
299    }
300
301    /**
302     * Check if a dataset ID/group exists.
303     *
304     * @param    string  dataset ID
305     * @param    string  cache group
306     * @return   boolean
307     */
308    function idExists($id, $group)
309    {
310        $query = 'SELECT id FROM '  . $this->cache_table
311                .' WHERE id='       . $this->db->getTextValue($id)
312                .' AND cachegroup=' . $this->db->getTextValue($group);
313        echo $query;
314        $res = $this->db->query($query);
315        if (MDB::isError($res)) {
316            return new Cache_Error('MDB::query failed: '
317                    . $this->db->errorMessage($res), __FILE__, __LINE__);
318        }
319        $row = $this->db->fetchInto($res);
320
321        if (is_array($row)) {
322            return true;
323        }
324        return false;
325    }
326
327    /**
328     * Garbage collector.
329     *
330     * @param    int maxlifetime
331     */
332    function garbageCollection($maxlifetime)
333    {
334        $this->flushPreload();
335        $query = 'DELETE FROM '        . $this->cache_table
336                .' WHERE (expires <= ' . time()
337                .' AND expires > 0) OR changed <= '. time() - $maxlifetime;
338
339        $res = $this->db->query($query);
340
341        $query = 'SELECT sum(length(cachedata)) as CacheSize FROM '
342                . $this->cache_table;
343
344        $cachesize = $this->db->getOne($query);
345        if (MDB::isError($cachesize)) {
346            return new Cache_Error('MDB::query failed: '
347                   . $this->db->errorMessage($cachesize), __FILE__, __LINE__);
348        }
349        //if cache is to big.
350        if ($cachesize > $this->highwater) {
351            //find the lowwater mark.
352            $query = 'SELECT length(cachedata) as size, changed FROM '
353                    . $this->cache_table .' ORDER BY changed DESC';
354
355            $res = $this->db->query($query);
356            if (MDB::isError($res)) {
357               return new Cache_Error('MDB::query failed: '
358                    . $this->db->errorMessage($res), __FILE__, __LINE__);
359            }
360            $numrows = $this->db->numRows($res);
361            $keep_size = 0;
362            while ($keep_size < $this->lowwater && $numrows--) {
363                $entry = $this->db->fetchInto($res,MDB_FETCHMODE_ASSOC);
364                $keep_size += $entry['size'];
365            }
366
367            //delete all entries, which were changed before the "lowwater mark"
368            $query = 'DELETE FROM ' . $this->cache_table
369                    .' WHERE changed<='.($entry['changed'] ? $entry['changed'] : 0);
370
371            $res = $this->db->query($query);
372            if (MDB::isError($res)) {
373               return new Cache_Error('MDB::query failed: '
374                    . $this->db->errorMessage($res), __FILE__, __LINE__);
375            }
376        }
377    }
378
379}
380?>