xref: /plugin/sqlite/helper.php (revision 1389625928e50f93227f5dce96d45451c3173c89)
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
19aa81d781SKlap-inrequire_once(DOKU_PLUGIN.'sqlite/classes/adapter.php');
2087fa2c18Sstretchyboy
21a1e6784eSAndreas Gohrclass helper_plugin_sqlite extends DokuWiki_Plugin {
22aa81d781SKlap-in    var $adapter = null;
23aa81d781SKlap-in
24aa81d781SKlap-in    public function getInfo() {
25a1e6784eSAndreas Gohr        return confToHash(dirname(__FILE__).'plugin.info.txt');
26a1e6784eSAndreas Gohr    }
27a1e6784eSAndreas Gohr
28a1e6784eSAndreas Gohr    /**
29aa81d781SKlap-in     * Keep separate instances for every call to keep database connections
30aa81d781SKlap-in     */
31aa81d781SKlap-in    public function isSingleton() {
32aa81d781SKlap-in        return false;
33aa81d781SKlap-in    }
34aa81d781SKlap-in
35aa81d781SKlap-in    /**
36a1e6784eSAndreas Gohr     * constructor
37a1e6784eSAndreas Gohr     */
38aa81d781SKlap-in    public function helper_plugin_sqlite() {
3987fa2c18Sstretchyboy
40*13896259SKlap-in        if(!$this->adapter) {
41aa81d781SKlap-in            if($this->existsPDOSqlite()) {
42aa81d781SKlap-in                require_once(DOKU_PLUGIN.'sqlite/classes/adapter_pdosqlite.php');
43aa81d781SKlap-in                $this->adapter = new helper_plugin_sqlite_adapter_pdosqlite();
44aa81d781SKlap-in            }
45aa81d781SKlap-in        }
46aa81d781SKlap-in
47*13896259SKlap-in        if(!$this->adapter) {
48aa81d781SKlap-in            if($this->existsSqlite2()) {
49aa81d781SKlap-in                require_once(DOKU_PLUGIN.'sqlite/classes/adapter_sqlite2.php');
50aa81d781SKlap-in                $this->adapter = new helper_plugin_sqlite_adapter_sqlite2();
51aa81d781SKlap-in            }
52aa81d781SKlap-in        }
53aa81d781SKlap-in
54*13896259SKlap-in        if(!$this->adapter) {
55aa81d781SKlap-in            msg('SQLite & PDO SQLite support missing in this PHP install - plugin will not work', -1);
56aa81d781SKlap-in        }
57aa81d781SKlap-in    }
58aa81d781SKlap-in
59aa81d781SKlap-in    /**
60aa81d781SKlap-in     * check availabilty of PHPs sqlite extension (for sqlite2 support)
61aa81d781SKlap-in     */
62aa81d781SKlap-in    public function existsSqlite2() {
63aa81d781SKlap-in        if(!extension_loaded('sqlite')) {
64aa81d781SKlap-in            $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : '';
65aa81d781SKlap-in            if(function_exists('dl')) @dl($prefix.'sqlite.'.PHP_SHLIB_SUFFIX);
66aa81d781SKlap-in        }
67aa81d781SKlap-in
68aa81d781SKlap-in        return function_exists('sqlite_open');
69aa81d781SKlap-in    }
70aa81d781SKlap-in
71aa81d781SKlap-in    /**
72aa81d781SKlap-in     * check availabilty of PHP PDO sqlite3
73aa81d781SKlap-in     */
74aa81d781SKlap-in    public function existsPDOSqlite() {
7587fa2c18Sstretchyboy        if(!extension_loaded('pdo_sqlite')) {
7687fa2c18Sstretchyboy            $prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' : '';
7787fa2c18Sstretchyboy            if(function_exists('dl')) @dl($prefix.'pdo_sqlite.'.PHP_SHLIB_SUFFIX);
7887fa2c18Sstretchyboy        }
7987fa2c18Sstretchyboy
8087fa2c18Sstretchyboy        if(class_exists('pdo')) {
81aa81d781SKlap-in            foreach(PDO::getAvailableDrivers() as $driver) {
82aa81d781SKlap-in                if($driver == 'sqlite') {
83aa81d781SKlap-in                    return true;
8487fa2c18Sstretchyboy                }
8587fa2c18Sstretchyboy            }
867ed6069fSAdrian Lang        }
87aa81d781SKlap-in        return false;
88a1e6784eSAndreas Gohr    }
89a1e6784eSAndreas Gohr
90a1e6784eSAndreas Gohr    /**
91a1e6784eSAndreas Gohr     * Initializes and opens the database
92a1e6784eSAndreas Gohr     *
93a1e6784eSAndreas Gohr     * Needs to be called right after loading this helper plugin
94aa81d781SKlap-in     *
95aa81d781SKlap-in     * @param string $dbname
96aa81d781SKlap-in     * @param string $updatedir - Database update infos
97aa81d781SKlap-in     * @return bool
98a1e6784eSAndreas Gohr     */
99aa81d781SKlap-in    public function init($dbname, $updatedir) {
100a1e6784eSAndreas Gohr
101aa81d781SKlap-in        $init = null; // set by initdb()
102aa81d781SKlap-in        if(!$this->adapter->initdb($dbname, $init)) return false;
103a1e6784eSAndreas Gohr
104aa81d781SKlap-in        return $this->_updatedb($init, $updatedir);
105a1e6784eSAndreas Gohr    }
106a1e6784eSAndreas Gohr
107a1e6784eSAndreas Gohr    /**
108a1e6784eSAndreas Gohr     * Return the current Database Version
109a1e6784eSAndreas Gohr     */
110aa81d781SKlap-in    private function _currentDBversion() {
111a1e6784eSAndreas Gohr        $sql = "SELECT val FROM opts WHERE opt = 'dbversion';";
112a1e6784eSAndreas Gohr        $res = $this->query($sql);
113a1e6784eSAndreas Gohr        if(!$res) return false;
114a1e6784eSAndreas Gohr        $row = $this->res2row($res, 0);
115a1e6784eSAndreas Gohr        return (int) $row['val'];
116a1e6784eSAndreas Gohr    }
117aa81d781SKlap-in
118a1e6784eSAndreas Gohr    /**
119a1e6784eSAndreas Gohr     * Update the database if needed
120a1e6784eSAndreas Gohr     *
121a1e6784eSAndreas Gohr     * @param bool   $init      - true if this is a new database to initialize
122a1e6784eSAndreas Gohr     * @param string $updatedir - Database update infos
123aa81d781SKlap-in     * @return bool
124a1e6784eSAndreas Gohr     */
125*13896259SKlap-in    private function _updatedb($init, $updatedir) {
126a1e6784eSAndreas Gohr        if($init) {
1274d9093b4Sstretchyboy
128a1e6784eSAndreas Gohr            $current = 0;
129a1e6784eSAndreas Gohr        } else {
130a1e6784eSAndreas Gohr            $current = $this->_currentDBversion();
131a1e6784eSAndreas Gohr            if(!$current) {
132aa81d781SKlap-in                msg("SQLite: no DB version found. '".$this->adapter->getDbname()."' DB probably broken.", -1);
133a1e6784eSAndreas Gohr                return false;
134a1e6784eSAndreas Gohr            }
135a1e6784eSAndreas Gohr        }
136a1e6784eSAndreas Gohr
137a1e6784eSAndreas Gohr        // in case of init, add versioning table
138a1e6784eSAndreas Gohr        if($init) {
139a1e6784eSAndreas Gohr            if(!$this->_runupdatefile(dirname(__FILE__).'/db.sql', 0)) {
140aa81d781SKlap-in                msg("SQLite: '".$this->adapter->getDbname()."' database upgrade failed for version ", -1);
141a1e6784eSAndreas Gohr                return false;
142a1e6784eSAndreas Gohr            }
143a1e6784eSAndreas Gohr        }
144a1e6784eSAndreas Gohr
145a1e6784eSAndreas Gohr        $latest = (int) trim(io_readFile($updatedir.'/latest.version'));
146a1e6784eSAndreas Gohr
147a1e6784eSAndreas Gohr        // all up to date?
148a1e6784eSAndreas Gohr        if($current >= $latest) return true;
149a1e6784eSAndreas Gohr        for($i = $current + 1; $i <= $latest; $i++) {
150a1e6784eSAndreas Gohr            $file = sprintf($updatedir.'/update%04d.sql', $i);
151a1e6784eSAndreas Gohr            if(file_exists($file)) {
152a1e6784eSAndreas Gohr                if(!$this->_runupdatefile($file, $i)) {
153aa81d781SKlap-in                    msg("SQLite: '".$this->adapter->getDbname()."' database upgrade failed for version ".$i, -1);
154a1e6784eSAndreas Gohr                    return false;
155a1e6784eSAndreas Gohr                }
156a1e6784eSAndreas Gohr            }
157a1e6784eSAndreas Gohr        }
158a1e6784eSAndreas Gohr        return true;
159a1e6784eSAndreas Gohr    }
160a1e6784eSAndreas Gohr
161a1e6784eSAndreas Gohr    /**
162a1e6784eSAndreas Gohr     * Updates the database structure using the given file to
163a1e6784eSAndreas Gohr     * the given version.
164a1e6784eSAndreas Gohr     */
165aa81d781SKlap-in    private function _runupdatefile($file, $version) {
166a1e6784eSAndreas Gohr        $sql = io_readFile($file, false);
167a1e6784eSAndreas Gohr
168a1e6784eSAndreas Gohr        $sql = explode(";", $sql);
169a1e6784eSAndreas Gohr        array_unshift($sql, 'BEGIN TRANSACTION');
170a1e6784eSAndreas Gohr        array_push($sql, "INSERT OR REPLACE INTO opts (val,opt) VALUES ($version,'dbversion')");
171a1e6784eSAndreas Gohr        array_push($sql, "COMMIT TRANSACTION");
172a1e6784eSAndreas Gohr
173a1e6784eSAndreas Gohr        foreach($sql as $s) {
174a1e6784eSAndreas Gohr            $s = preg_replace('!^\s*--.*$!m', '', $s);
175a1e6784eSAndreas Gohr            $s = trim($s);
176a1e6784eSAndreas Gohr            if(!$s) continue;
177fd69a32cSAndreas Gohr
178a1e6784eSAndreas Gohr            $res = $this->query("$s;");
179a1e6784eSAndreas Gohr            if($res === false) {
180*13896259SKlap-in                if($this->adapter->getName() == DOKU_EXT_SQLITE) {
181a1e6784eSAndreas Gohr                    sqlite_query($this->db, 'ROLLBACK TRANSACTION');
182a1e6784eSAndreas Gohr                }
18305f176edSstretchyboy                return false;
18405f176edSstretchyboy            }
185a1e6784eSAndreas Gohr        }
186a1e6784eSAndreas Gohr
187a1e6784eSAndreas Gohr        return ($version == $this->_currentDBversion());
188a1e6784eSAndreas Gohr    }
189a1e6784eSAndreas Gohr
190a1e6784eSAndreas Gohr    /**
191aa81d781SKlap-in     * Dump db into a file in meta directory
192fd69a32cSAndreas Gohr     *
193fd69a32cSAndreas Gohr     */
194aa81d781SKlap-in    public function dumpDatabase($dbname) {
195aa81d781SKlap-in        global $conf;
196fb394683SAndreas Gohr
197aa81d781SKlap-in        $res    = $this->query("SELECT name,sql FROM sqlite_master WHERE type='table'");
198aa81d781SKlap-in        $tables = $this->res2arr($res);
199fb394683SAndreas Gohr
200aa81d781SKlap-in        $filename = 'dumpfile_'.$dbname.'.sql';
201aa81d781SKlap-in        if($fp = fopen($conf['metadir'].'/'.$filename, 'w')) {
202aa81d781SKlap-in
203aa81d781SKlap-in            fwrite($fp, 'BEGIN TRANSACTION;'."\n");
204aa81d781SKlap-in
205aa81d781SKlap-in            foreach($tables as $table) {
206aa81d781SKlap-in
207aa81d781SKlap-in                fwrite($fp, $table['sql'].";\n");
208aa81d781SKlap-in
209aa81d781SKlap-in                $sql = "SELECT * FROM ".$table['name'];
210aa81d781SKlap-in                $res = $this->query($sql);
211aa81d781SKlap-in
212aa81d781SKlap-in                while($row = $this->res_fetch_array($res)) {
213aa81d781SKlap-in
214aa81d781SKlap-in                    $line = 'INSERT INTO '.$table['name'].' VALUES(';
215aa81d781SKlap-in                    foreach($row as $no_entry => $entry) {
216aa81d781SKlap-in                        if($no_entry !== 0) {
217aa81d781SKlap-in                            $line .= ',';
218fd69a32cSAndreas Gohr                        }
21987fa2c18Sstretchyboy
220aa81d781SKlap-in                        if(is_null($entry)) {
221aa81d781SKlap-in                            $line .= 'NULL';
222aa81d781SKlap-in                        } elseif(!is_numeric($entry)) {
223aa81d781SKlap-in                            $line .= $this->quote_string($entry);
224fb394683SAndreas Gohr                        } else {
225aa81d781SKlap-in                            //FIXME extra leading zeros are truncated e.g 1.300 (thousand three hunderd)-> 1.3
226aa81d781SKlap-in                            $line .= $entry;
227aa81d781SKlap-in                        }
228aa81d781SKlap-in                    }
229aa81d781SKlap-in                    $line .= ');'."\n";
230aa81d781SKlap-in
231aa81d781SKlap-in                    fwrite($fp, $line);
232aa81d781SKlap-in                }
233fb394683SAndreas Gohr            }
234fb394683SAndreas Gohr
235aa81d781SKlap-in            $res     = $this->query("SELECT name,sql FROM sqlite_master WHERE type='index'");
236aa81d781SKlap-in            $indexes = $this->res2arr($res);
237aa81d781SKlap-in            foreach($indexes as $index) {
238aa81d781SKlap-in                fwrite($fp, $index['sql'].";\n");
239fd69a32cSAndreas Gohr            }
240fd69a32cSAndreas Gohr
241aa81d781SKlap-in            fwrite($fp, 'COMMIT;'."\n");
242aa81d781SKlap-in
243aa81d781SKlap-in            fclose($fp);
244aa81d781SKlap-in            return true;
245fd69a32cSAndreas Gohr        } else {
246aa81d781SKlap-in            msg('Dumping "'.hsc($dbname).'" has failed. Could not open '.$filename);
247fd69a32cSAndreas Gohr            return false;
248fd69a32cSAndreas Gohr        }
249fd69a32cSAndreas Gohr    }
250fd69a32cSAndreas Gohr
251fd69a32cSAndreas Gohr    /**
2523ae3f79eSKlap-in     * Registers a User Defined Function for use in SQL statements
2533ae3f79eSKlap-in     */
254aa81d781SKlap-in    public function create_function($function_name, $callback, $num_args) {
255aa81d781SKlap-in        $this->adapter->create_function($function_name, $callback, $num_args);
2563ae3f79eSKlap-in    }
2573ae3f79eSKlap-in
2583ae3f79eSKlap-in    /**
259a1e6784eSAndreas Gohr     * Execute a query with the given parameters.
260a1e6784eSAndreas Gohr     *
261a1e6784eSAndreas Gohr     * Takes care of escaping
262a1e6784eSAndreas Gohr     *
263aa81d781SKlap-in     * @internal param string $sql - the statement
264aa81d781SKlap-in     * @internal param $arguments ...
265aa81d781SKlap-in     * @return bool|\SQLiteResult
266a1e6784eSAndreas Gohr     */
267aa81d781SKlap-in    public function query() {
268a1e6784eSAndreas Gohr        // get function arguments
269a1e6784eSAndreas Gohr        $args = func_get_args();
270a1e6784eSAndreas Gohr
271aa81d781SKlap-in        return $this->adapter->query($args);
27287fa2c18Sstretchyboy    }
273a1e6784eSAndreas Gohr
274a1e6784eSAndreas Gohr    /**
275a1e6784eSAndreas Gohr     * Join the given values and quote them for SQL insertion
276a1e6784eSAndreas Gohr     */
277aa81d781SKlap-in    public function quote_and_join($vals, $sep = ',') {
278aa81d781SKlap-in        return $this->adapter->quote_and_join($vals, $sep);
279a1e6784eSAndreas Gohr    }
280a1e6784eSAndreas Gohr
281a1e6784eSAndreas Gohr    /**
282a1e6784eSAndreas Gohr     * Run sqlite_escape_string() on the given string and surround it
283a1e6784eSAndreas Gohr     * with quotes
284a1e6784eSAndreas Gohr     */
285aa81d781SKlap-in    public function quote_string($string) {
286aa81d781SKlap-in        return $this->adapter->quote_string($string);
287a1e6784eSAndreas Gohr    }
288a1e6784eSAndreas Gohr
289b5b947d7SAndreas Gohr    /**
290fee3b689Sstretchyboy     * Escape string for sql
291fee3b689Sstretchyboy     */
292aa81d781SKlap-in    public function escape_string($str) {
293aa81d781SKlap-in        return $this->adapter->escape_string($str);
294ff97cc8fSstretchyboy    }
295ff97cc8fSstretchyboy
296ff97cc8fSstretchyboy    /**
297aa81d781SKlap-in     * Returns a complete result set as array
298ff97cc8fSstretchyboy     */
299*13896259SKlap-in    public function res2arr($res) {
300*13896259SKlap-in        return $this->adapter->res2arr($res);
301b5b947d7SAndreas Gohr    }
302b5b947d7SAndreas Gohr
303b5b947d7SAndreas Gohr    /**
304aa81d781SKlap-in     * Return the wanted row from a given result set as
305aa81d781SKlap-in     * associative array
306b5b947d7SAndreas Gohr     */
307aa81d781SKlap-in    public function res2row($res, $rownum = 0) {
308aa81d781SKlap-in        return $this->adapter->res2row($res, $rownum);
309b5b947d7SAndreas Gohr    }
310b5b947d7SAndreas Gohr
311e7112ccbSAdrian Lang    /**
312aa81d781SKlap-in     * Return the first value from the first row.
313e7112ccbSAdrian Lang     */
314aa81d781SKlap-in    public function res2single($res) {
315aa81d781SKlap-in        return $this->adapter->res2single($res);
316e7112ccbSAdrian Lang    }
317fee3b689Sstretchyboy
318fee3b689Sstretchyboy    /**
319fee3b689Sstretchyboy     * fetch the next row as zero indexed array
320fee3b689Sstretchyboy     */
321aa81d781SKlap-in    public function res_fetch_array($res) {
322aa81d781SKlap-in        return $this->adapter->res_fetch_array($res);
32387fa2c18Sstretchyboy    }
324fee3b689Sstretchyboy
325fee3b689Sstretchyboy    /**
326fee3b689Sstretchyboy     * fetch the next row as assocative array
327fee3b689Sstretchyboy     */
328aa81d781SKlap-in    public function res_fetch_assoc($res) {
329aa81d781SKlap-in        return $this->adapter->res_fetch_assoc($res);
330fee3b689Sstretchyboy    }
331fee3b689Sstretchyboy
332fee3b689Sstretchyboy    /**
33378977d74SKlap-in     * Count the number of records in result
3343157674bSAndreas Gohr     *
335db58e525SKlap-in     * This function is really inperformant in PDO and should be avoided!
336fee3b689Sstretchyboy     */
337aa81d781SKlap-in    public function res2count($res) {
338aa81d781SKlap-in        return $this->adapter->res2count($res);
339fee3b689Sstretchyboy    }
34024a03f6cSstretchyboy
34124a03f6cSstretchyboy    /**
34224a03f6cSstretchyboy     * Count the number of records changed last time
34324a03f6cSstretchyboy     */
344aa81d781SKlap-in    public function countChanges($db, $res) {
345aa81d781SKlap-in        return $this->adapter->countChanges($db, $res);
346a1e6784eSAndreas Gohr    }
347a1e6784eSAndreas Gohr
348aa81d781SKlap-in}
349