xref: /plugin/sqlite/helper.php (revision aa81d78132800d11a55b7cccbad5662632e4bf36)
1a1e6784eSAndreas Gohr<?php
2a1e6784eSAndreas Gohr/**
3a1e6784eSAndreas Gohr * DokuWiki Plugin sqlite (Helper Component)
4a1e6784eSAndreas Gohr *
5a1e6784eSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6a1e6784eSAndreas Gohr * @author  Andreas Gohr <gohr@cosmocode.de>
7a1e6784eSAndreas Gohr */
8a1e6784eSAndreas Gohr
9a1e6784eSAndreas Gohr// must be run within Dokuwiki
10a1e6784eSAndreas Gohrif (!defined('DOKU_INC')) die();
11a1e6784eSAndreas Gohr
12a1e6784eSAndreas Gohrif (!defined('DOKU_LF')) define('DOKU_LF', "\n");
13a1e6784eSAndreas Gohrif (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
14a1e6784eSAndreas Gohrif (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
15a1e6784eSAndreas Gohr
1687fa2c18Sstretchyboyif (!defined('DOKU_EXT_SQLITE')) define('DOKU_EXT_SQLITE', 'sqlite');
1787fa2c18Sstretchyboyif (!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo');
1887fa2c18Sstretchyboy
19*aa81d781SKlap-inrequire_once(DOKU_PLUGIN.'sqlite/classes/adapter.php');
2087fa2c18Sstretchyboy
21a1e6784eSAndreas Gohrclass helper_plugin_sqlite extends DokuWiki_Plugin {
22*aa81d781SKlap-in    var $adapter = null;
23*aa81d781SKlap-in
24*aa81d781SKlap-in
25*aa81d781SKlap-in    public function getInfo() {
26a1e6784eSAndreas Gohr        return confToHash(dirname(__FILE__).'plugin.info.txt');
27a1e6784eSAndreas Gohr    }
28a1e6784eSAndreas Gohr
29a1e6784eSAndreas Gohr    /**
30*aa81d781SKlap-in     * Keep separate instances for every call to keep database connections
31*aa81d781SKlap-in     */
32*aa81d781SKlap-in    public function isSingleton() {
33*aa81d781SKlap-in        return false;
34*aa81d781SKlap-in    }
35*aa81d781SKlap-in
36*aa81d781SKlap-in    /**
37a1e6784eSAndreas Gohr     * constructor
38a1e6784eSAndreas Gohr     */
39*aa81d781SKlap-in    public function helper_plugin_sqlite(){
4087fa2c18Sstretchyboy
41*aa81d781SKlap-in      if(!$this->adapter)
4287fa2c18Sstretchyboy      {
43*aa81d781SKlap-in        if($this->existsPDOSqlite()){
44*aa81d781SKlap-in            require_once(DOKU_PLUGIN.'sqlite/classes/adapter_pdosqlite.php');
45*aa81d781SKlap-in            $this->adapter = new helper_plugin_sqlite_adapter_pdosqlite();
46*aa81d781SKlap-in        }
47*aa81d781SKlap-in      }
48*aa81d781SKlap-in
49*aa81d781SKlap-in      if(!$this->adapter)
50*aa81d781SKlap-in      {
51*aa81d781SKlap-in        if($this->existsSqlite2()){
52*aa81d781SKlap-in            require_once(DOKU_PLUGIN.'sqlite/classes/adapter_sqlite2.php');
53*aa81d781SKlap-in            $this->adapter = new helper_plugin_sqlite_adapter_sqlite2();
54*aa81d781SKlap-in        }
55*aa81d781SKlap-in      }
56*aa81d781SKlap-in
57*aa81d781SKlap-in      if(!$this->adapter)
58*aa81d781SKlap-in      {
59*aa81d781SKlap-in        msg('SQLite & PDO SQLite support missing in this PHP install - plugin will not work',-1);
60*aa81d781SKlap-in      }
61*aa81d781SKlap-in    }
62*aa81d781SKlap-in
63*aa81d781SKlap-in
64*aa81d781SKlap-in    /**
65*aa81d781SKlap-in     * check availabilty of PHPs sqlite extension (for sqlite2 support)
66*aa81d781SKlap-in     */
67*aa81d781SKlap-in    public function existsSqlite2(){
68*aa81d781SKlap-in        if (!extension_loaded('sqlite')) {
69*aa81d781SKlap-in            $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : '';
70*aa81d781SKlap-in            if(function_exists('dl')) @dl($prefix . 'sqlite.' . PHP_SHLIB_SUFFIX);
71*aa81d781SKlap-in        }
72*aa81d781SKlap-in
73*aa81d781SKlap-in        return function_exists('sqlite_open');
74*aa81d781SKlap-in    }
75*aa81d781SKlap-in
76*aa81d781SKlap-in    /**
77*aa81d781SKlap-in     * check availabilty of PHP PDO sqlite3
78*aa81d781SKlap-in     */
79*aa81d781SKlap-in    public function existsPDOSqlite(){
8087fa2c18Sstretchyboy        if (!extension_loaded('pdo_sqlite')) {
8187fa2c18Sstretchyboy            $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : '';
8287fa2c18Sstretchyboy            if(function_exists('dl')) @dl($prefix . 'pdo_sqlite.' . PHP_SHLIB_SUFFIX);
8387fa2c18Sstretchyboy        }
8487fa2c18Sstretchyboy
8587fa2c18Sstretchyboy        if(class_exists('pdo')){
86*aa81d781SKlap-in            foreach(PDO::getAvailableDrivers() as $driver){
87*aa81d781SKlap-in                if($driver=='sqlite'){
88*aa81d781SKlap-in                    return true;
8987fa2c18Sstretchyboy                }
9087fa2c18Sstretchyboy            }
917ed6069fSAdrian Lang        }
92*aa81d781SKlap-in        return false;
93a1e6784eSAndreas Gohr    }
94a1e6784eSAndreas Gohr
95a1e6784eSAndreas Gohr    /**
96a1e6784eSAndreas Gohr     * Initializes and opens the database
97a1e6784eSAndreas Gohr     *
98a1e6784eSAndreas Gohr     * Needs to be called right after loading this helper plugin
99*aa81d781SKlap-in     *
100*aa81d781SKlap-in     * @param string $dbname
101*aa81d781SKlap-in     * @param string $updatedir - Database update infos
102*aa81d781SKlap-in     * @return bool
103a1e6784eSAndreas Gohr     */
104*aa81d781SKlap-in    public function init($dbname,$updatedir){
105a1e6784eSAndreas Gohr
106*aa81d781SKlap-in        $init = null; // set by initdb()
107*aa81d781SKlap-in        if(!$this->adapter->initdb($dbname,$init)) return false;
108a1e6784eSAndreas Gohr
109*aa81d781SKlap-in        return $this->_updatedb($init,$updatedir);
110a1e6784eSAndreas Gohr    }
111a1e6784eSAndreas Gohr
112a1e6784eSAndreas Gohr    /**
113a1e6784eSAndreas Gohr     * Return the current Database Version
114a1e6784eSAndreas Gohr     */
115*aa81d781SKlap-in    private function _currentDBversion(){
116a1e6784eSAndreas Gohr        $sql = "SELECT val FROM opts WHERE opt = 'dbversion';";
117a1e6784eSAndreas Gohr        $res = $this->query($sql);
118a1e6784eSAndreas Gohr        if(!$res) return false;
119a1e6784eSAndreas Gohr        $row = $this->res2row($res,0);
120a1e6784eSAndreas Gohr        return (int) $row['val'];
121a1e6784eSAndreas Gohr    }
122*aa81d781SKlap-in
123a1e6784eSAndreas Gohr    /**
124a1e6784eSAndreas Gohr     * Update the database if needed
125a1e6784eSAndreas Gohr     *
126a1e6784eSAndreas Gohr     * @param bool   $init      - true if this is a new database to initialize
127a1e6784eSAndreas Gohr     * @param string $updatedir - Database update infos
128*aa81d781SKlap-in     * @return bool
129a1e6784eSAndreas Gohr     */
130*aa81d781SKlap-in    private function _updatedb($init,$updatedir)
1314d9093b4Sstretchyboy    {
132a1e6784eSAndreas Gohr        if($init){
1334d9093b4Sstretchyboy
134a1e6784eSAndreas Gohr            $current = 0;
135a1e6784eSAndreas Gohr        }else{
136a1e6784eSAndreas Gohr            $current = $this->_currentDBversion();
137a1e6784eSAndreas Gohr            if(!$current){
138*aa81d781SKlap-in                msg("SQLite: no DB version found. '".$this->adapter->getDbname()."' DB probably broken.",-1);
139a1e6784eSAndreas Gohr                return false;
140a1e6784eSAndreas Gohr            }
141a1e6784eSAndreas Gohr        }
142a1e6784eSAndreas Gohr
143a1e6784eSAndreas Gohr        // in case of init, add versioning table
144a1e6784eSAndreas Gohr        if($init){
145a1e6784eSAndreas Gohr            if(!$this->_runupdatefile(dirname(__FILE__).'/db.sql',0)){
146*aa81d781SKlap-in                  msg("SQLite: '".$this->adapter->getDbname()."' database upgrade failed for version ", -1);
147a1e6784eSAndreas Gohr                return false;
148a1e6784eSAndreas Gohr            }
149a1e6784eSAndreas Gohr        }
150a1e6784eSAndreas Gohr
151a1e6784eSAndreas Gohr        $latest  = (int) trim(io_readFile($updatedir.'/latest.version'));
152a1e6784eSAndreas Gohr
153a1e6784eSAndreas Gohr        // all up to date?
154a1e6784eSAndreas Gohr        if($current >= $latest) return true;
155a1e6784eSAndreas Gohr        for($i=$current+1; $i<=$latest; $i++){
156a1e6784eSAndreas Gohr            $file = sprintf($updatedir.'/update%04d.sql',$i);
157a1e6784eSAndreas Gohr            if(file_exists($file)){
158a1e6784eSAndreas Gohr                if(!$this->_runupdatefile($file,$i)){
159*aa81d781SKlap-in                    msg("SQLite: '".$this->adapter->getDbname()."' database upgrade failed for version ".$i, -1);
160a1e6784eSAndreas Gohr                    return false;
161a1e6784eSAndreas Gohr                }
162a1e6784eSAndreas Gohr            }
163a1e6784eSAndreas Gohr        }
164a1e6784eSAndreas Gohr        return true;
165a1e6784eSAndreas Gohr    }
166a1e6784eSAndreas Gohr
167a1e6784eSAndreas Gohr    /**
168a1e6784eSAndreas Gohr     * Updates the database structure using the given file to
169a1e6784eSAndreas Gohr     * the given version.
170a1e6784eSAndreas Gohr     */
171*aa81d781SKlap-in    private function _runupdatefile($file,$version){
172a1e6784eSAndreas Gohr        $sql  = io_readFile($file,false);
173a1e6784eSAndreas Gohr
174a1e6784eSAndreas Gohr        $sql = explode(";",$sql);
175a1e6784eSAndreas Gohr        array_unshift($sql,'BEGIN TRANSACTION');
176a1e6784eSAndreas Gohr        array_push($sql,"INSERT OR REPLACE INTO opts (val,opt) VALUES ($version,'dbversion')");
177a1e6784eSAndreas Gohr        array_push($sql,"COMMIT TRANSACTION");
178a1e6784eSAndreas Gohr
179a1e6784eSAndreas Gohr        foreach($sql as $s){
180a1e6784eSAndreas Gohr            $s = preg_replace('!^\s*--.*$!m', '', $s);
181a1e6784eSAndreas Gohr            $s = trim($s);
182a1e6784eSAndreas Gohr            if(!$s) continue;
183fd69a32cSAndreas Gohr
184fd69a32cSAndreas Gohr
185a1e6784eSAndreas Gohr            $res = $this->query("$s;");
186a1e6784eSAndreas Gohr            if ($res === false) {
187*aa81d781SKlap-in                if($this->adapter->getName() == DOKU_EXT_SQLITE)
18887fa2c18Sstretchyboy                {
189a1e6784eSAndreas Gohr                    sqlite_query($this->db, 'ROLLBACK TRANSACTION');
190a1e6784eSAndreas Gohr                }
19105f176edSstretchyboy                return false;
19205f176edSstretchyboy            }
193a1e6784eSAndreas Gohr        }
194a1e6784eSAndreas Gohr
195a1e6784eSAndreas Gohr        return ($version == $this->_currentDBversion());
196a1e6784eSAndreas Gohr    }
197a1e6784eSAndreas Gohr
198a1e6784eSAndreas Gohr    /**
199*aa81d781SKlap-in     * Dump db into a file in meta directory
200fd69a32cSAndreas Gohr     *
201fd69a32cSAndreas Gohr     */
202*aa81d781SKlap-in     public function dumpDatabase($dbname){
203*aa81d781SKlap-in        global $conf;
204fb394683SAndreas Gohr
205*aa81d781SKlap-in        $res = $this->query("SELECT name,sql FROM sqlite_master WHERE type='table'");
206*aa81d781SKlap-in        $tables = $this->res2arr($res);
207fb394683SAndreas Gohr
208*aa81d781SKlap-in        $filename = 'dumpfile_'.$dbname.'.sql';
209*aa81d781SKlap-in        if($fp = fopen($conf['metadir'].'/'.$filename, 'w')){
210*aa81d781SKlap-in
211*aa81d781SKlap-in            fwrite($fp, 'BEGIN TRANSACTION;'."\n");
212*aa81d781SKlap-in
213*aa81d781SKlap-in            foreach($tables as $table){
214*aa81d781SKlap-in
215*aa81d781SKlap-in                fwrite($fp, $table['sql'].";\n");
216*aa81d781SKlap-in
217*aa81d781SKlap-in                $sql = "SELECT * FROM ".$table['name'];
218*aa81d781SKlap-in                $res = $this->query($sql);
219*aa81d781SKlap-in
220*aa81d781SKlap-in                while($row = $this->res_fetch_array($res)){
221*aa81d781SKlap-in
222*aa81d781SKlap-in                    $line = 'INSERT INTO '.$table['name'].' VALUES(';
223*aa81d781SKlap-in                    foreach($row as $no_entry => $entry){
224*aa81d781SKlap-in                        if($no_entry!==0){
225*aa81d781SKlap-in                            $line .= ',';
226fd69a32cSAndreas Gohr                        }
22787fa2c18Sstretchyboy
228*aa81d781SKlap-in                        if(is_null($entry)){
229*aa81d781SKlap-in                            $line .= 'NULL';
230*aa81d781SKlap-in                        }elseif(!is_numeric($entry)){
231*aa81d781SKlap-in                            $line .= $this->quote_string($entry);
232fb394683SAndreas Gohr                        }else{
233*aa81d781SKlap-in                            //FIXME extra leading zeros are truncated e.g 1.300 (thousand three hunderd)-> 1.3
234*aa81d781SKlap-in                            $line .= $entry;
235*aa81d781SKlap-in                        }
236*aa81d781SKlap-in                    }
237*aa81d781SKlap-in                    $line .= ');'."\n";
238*aa81d781SKlap-in
239*aa81d781SKlap-in                    fwrite($fp, $line);
240*aa81d781SKlap-in                }
241fb394683SAndreas Gohr            }
242fb394683SAndreas Gohr
243*aa81d781SKlap-in            $res = $this->query("SELECT name,sql FROM sqlite_master WHERE type='index'");
244*aa81d781SKlap-in            $indexes = $this->res2arr($res);
245*aa81d781SKlap-in            foreach($indexes as $index){
246*aa81d781SKlap-in                fwrite($fp, $index['sql'].";\n");
247fd69a32cSAndreas Gohr            }
248fd69a32cSAndreas Gohr
249*aa81d781SKlap-in            fwrite($fp, 'COMMIT;'."\n");
250*aa81d781SKlap-in
251*aa81d781SKlap-in            fclose($fp);
252*aa81d781SKlap-in            return true;
253fd69a32cSAndreas Gohr        }else{
254*aa81d781SKlap-in            msg('Dumping "'.hsc($dbname).'" has failed. Could not open '.$filename);
255fd69a32cSAndreas Gohr            return false;
256fd69a32cSAndreas Gohr        }
257fd69a32cSAndreas Gohr    }
258fd69a32cSAndreas Gohr
259fd69a32cSAndreas Gohr    /**
2603ae3f79eSKlap-in     * Registers a User Defined Function for use in SQL statements
2613ae3f79eSKlap-in     */
262*aa81d781SKlap-in    public function create_function($function_name,$callback,$num_args){
263*aa81d781SKlap-in        $this->adapter->create_function($function_name,$callback,$num_args);
2643ae3f79eSKlap-in    }
2653ae3f79eSKlap-in
2663ae3f79eSKlap-in    /**
267a1e6784eSAndreas Gohr     * Execute a query with the given parameters.
268a1e6784eSAndreas Gohr     *
269a1e6784eSAndreas Gohr     * Takes care of escaping
270a1e6784eSAndreas Gohr     *
271*aa81d781SKlap-in     * @internal param string $sql - the statement
272*aa81d781SKlap-in     * @internal param $arguments ...
273*aa81d781SKlap-in     * @return bool|\SQLiteResult
274a1e6784eSAndreas Gohr     */
275*aa81d781SKlap-in    public function query(){
276a1e6784eSAndreas Gohr        // get function arguments
277a1e6784eSAndreas Gohr        $args = func_get_args();
278a1e6784eSAndreas Gohr
279*aa81d781SKlap-in        return $this->adapter->query($args);
28087fa2c18Sstretchyboy    }
281a1e6784eSAndreas Gohr
282a1e6784eSAndreas Gohr    /**
283a1e6784eSAndreas Gohr     * Join the given values and quote them for SQL insertion
284a1e6784eSAndreas Gohr     */
285*aa81d781SKlap-in    public function quote_and_join($vals,$sep=',') {
286*aa81d781SKlap-in        return $this->adapter->quote_and_join($vals,$sep);
287a1e6784eSAndreas Gohr    }
288a1e6784eSAndreas Gohr
289a1e6784eSAndreas Gohr    /**
290a1e6784eSAndreas Gohr     * Run sqlite_escape_string() on the given string and surround it
291a1e6784eSAndreas Gohr     * with quotes
292a1e6784eSAndreas Gohr     */
293*aa81d781SKlap-in    public function quote_string($string){
294*aa81d781SKlap-in        return $this->adapter->quote_string($string);
295a1e6784eSAndreas Gohr    }
296a1e6784eSAndreas Gohr
297b5b947d7SAndreas Gohr    /**
298fee3b689Sstretchyboy     * Escape string for sql
299fee3b689Sstretchyboy     */
300*aa81d781SKlap-in    public function escape_string($str){
301*aa81d781SKlap-in        return $this->adapter->escape_string($str);
302ff97cc8fSstretchyboy    }
303ff97cc8fSstretchyboy
304ff97cc8fSstretchyboy    /**
305*aa81d781SKlap-in     * Returns a complete result set as array
306ff97cc8fSstretchyboy     */
307*aa81d781SKlap-in    public function res2arr($res, $assoc = true){
308*aa81d781SKlap-in        return $this->adapter->res2arr($res, $assoc);
309b5b947d7SAndreas Gohr    }
310b5b947d7SAndreas Gohr
311b5b947d7SAndreas Gohr    /**
312*aa81d781SKlap-in     * Return the wanted row from a given result set as
313*aa81d781SKlap-in     * associative array
314b5b947d7SAndreas Gohr     */
315*aa81d781SKlap-in    public function res2row($res,$rownum=0){
316*aa81d781SKlap-in        return $this->adapter->res2row($res,$rownum);
317b5b947d7SAndreas Gohr    }
318b5b947d7SAndreas Gohr
319e7112ccbSAdrian Lang    /**
320*aa81d781SKlap-in     * Return the first value from the first row.
321e7112ccbSAdrian Lang     */
322*aa81d781SKlap-in    public function res2single($res){
323*aa81d781SKlap-in        return $this->adapter->res2single($res);
324e7112ccbSAdrian Lang    }
325fee3b689Sstretchyboy
326fee3b689Sstretchyboy     /**
327fee3b689Sstretchyboy     * fetch the next row as zero indexed array
328fee3b689Sstretchyboy     */
329*aa81d781SKlap-in    public function res_fetch_array($res){
330*aa81d781SKlap-in        return $this->adapter->res_fetch_array($res);
33187fa2c18Sstretchyboy    }
332fee3b689Sstretchyboy
333fee3b689Sstretchyboy
334fee3b689Sstretchyboy    /**
335fee3b689Sstretchyboy     * fetch the next row as assocative array
336fee3b689Sstretchyboy     */
337*aa81d781SKlap-in    public function res_fetch_assoc($res){
338*aa81d781SKlap-in        return $this->adapter->res_fetch_assoc($res);
339fee3b689Sstretchyboy    }
340fee3b689Sstretchyboy
341fee3b689Sstretchyboy    /**
34278977d74SKlap-in     * Count the number of records in result
3433157674bSAndreas Gohr     *
344db58e525SKlap-in     * This function is really inperformant in PDO and should be avoided!
345fee3b689Sstretchyboy     */
346*aa81d781SKlap-in    public function res2count($res) {
347*aa81d781SKlap-in        return $this->adapter->res2count($res);
348fee3b689Sstretchyboy    }
34924a03f6cSstretchyboy
35024a03f6cSstretchyboy    /**
35124a03f6cSstretchyboy    * Count the number of records changed last time
35224a03f6cSstretchyboy    */
353*aa81d781SKlap-in    public function countChanges($db, $res){
354*aa81d781SKlap-in        return $this->adapter->countChanges($db, $res);
355a1e6784eSAndreas Gohr    }
356a1e6784eSAndreas Gohr
357*aa81d781SKlap-in}
358