xref: /plugin/strata/driver/driver.php (revision 5153720fcc1dd2b6e63035d45f7c2bc32e429371)
1*5153720fSfkaag71<?php
2*5153720fSfkaag71/**
3*5153720fSfkaag71 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4*5153720fSfkaag71 * @author     Brend Wanders <b.wanders@utwente.nl>
5*5153720fSfkaag71 */
6*5153720fSfkaag71// must be run within Dokuwiki
7*5153720fSfkaag71if(!defined('DOKU_INC')) die('Meh.');
8*5153720fSfkaag71
9*5153720fSfkaag71// Define the location of the local credentials file.
10*5153720fSfkaag71if(!defined('STRATA_CREDENTIALS')) define('STRATA_CREDENTIALS', DOKU_PLUGIN.'strata/credentials.local.php');
11*5153720fSfkaag71
12*5153720fSfkaag71/**
13*5153720fSfkaag71 * The base class for database drivers.
14*5153720fSfkaag71 */
15*5153720fSfkaag71abstract class plugin_strata_driver {
16*5153720fSfkaag71
17*5153720fSfkaag71    /**
18*5153720fSfkaag71     * Whether the driver should generate debug output.
19*5153720fSfkaag71     */
20*5153720fSfkaag71    var $_debug;
21*5153720fSfkaag71
22*5153720fSfkaag71    /**
23*5153720fSfkaag71     * The dsn.
24*5153720fSfkaag71     */
25*5153720fSfkaag71    var $_dsn;
26*5153720fSfkaag71
27*5153720fSfkaag71    /**
28*5153720fSfkaag71     * The PDO database object.
29*5153720fSfkaag71     */
30*5153720fSfkaag71    var $_db;
31*5153720fSfkaag71
32*5153720fSfkaag71    /**
33*5153720fSfkaag71     * Create a new database driver.
34*5153720fSfkaag71     *
35*5153720fSfkaag71     * @param debug boolean whether the created driver should generate debug output.
36*5153720fSfkaag71     */
37*5153720fSfkaag71    function __construct($debug=false) {
38*5153720fSfkaag71        $this->_debug = $debug;
39*5153720fSfkaag71        $this->util =& plugin_load('helper', 'strata_util');
40*5153720fSfkaag71    }
41*5153720fSfkaag71
42*5153720fSfkaag71    /**
43*5153720fSfkaag71     * Produces the syntax to cast something to a number.
44*5153720fSfkaag71     *
45*5153720fSfkaag71     * @param val string the thing to cast
46*5153720fSfkaag71     */
47*5153720fSfkaag71    public function castToNumber($val) {
48*5153720fSfkaag71        return "CAST($val AS NUMERIC)";
49*5153720fSfkaag71    }
50*5153720fSfkaag71
51*5153720fSfkaag71    /**
52*5153720fSfkaag71     * Casts the given value to a case insensitive variant.
53*5153720fSfkaag71     *
54*5153720fSfkaag71     * This cast can, for example, be using a case insensitive collation or using the function 'lower' (default).
55*5153720fSfkaag71     * @param val string the thing make case insensitive
56*5153720fSfkaag71     */
57*5153720fSfkaag71    public function ci($val='?') {
58*5153720fSfkaag71        return "lower($val)";
59*5153720fSfkaag71    }
60*5153720fSfkaag71
61*5153720fSfkaag71    /**
62*5153720fSfkaag71     * Returns the syntax for case-insensitive string comparison.
63*5153720fSfkaag71     *
64*5153720fSfkaag71     * Preferably, this syntax should allow % as a wildcard (e.g. as done by LIKE).
65*5153720fSfkaag71     */
66*5153720fSfkaag71    public function stringCompare() {
67*5153720fSfkaag71        return 'LIKE';
68*5153720fSfkaag71    }
69*5153720fSfkaag71
70*5153720fSfkaag71    /**
71*5153720fSfkaag71     * Returns the terms on which we should order.
72*5153720fSfkaag71     *
73*5153720fSfkaag71     * Ideally, the ordering should be natural, that is '2 apples' is sorted before '10 pears'.
74*5153720fSfkaag71     * However, depending on the supported database, ordering can vary between string and natural ordering, including any compromises.
75*5153720fSfkaag71     * @param val string the thing to sort on
76*5153720fSfkaag71     * @return an array of terms to sort on
77*5153720fSfkaag71     */
78*5153720fSfkaag71    public function orderBy($val) {
79*5153720fSfkaag71        return array(
80*5153720fSfkaag71            $this->castToNumber($val),
81*5153720fSfkaag71            $this->ci($val)
82*5153720fSfkaag71        );
83*5153720fSfkaag71    }
84*5153720fSfkaag71
85*5153720fSfkaag71    /**
86*5153720fSfkaag71     * Open the database connection.
87*5153720fSfkaag71     *
88*5153720fSfkaag71     * @param dsn string the dsn to use for connecting
89*5153720fSfkaag71     * @return boolean true when connecting was successful
90*5153720fSfkaag71     */
91*5153720fSfkaag71    public function connect($dsn) {
92*5153720fSfkaag71        $this->_dsn = $dsn;
93*5153720fSfkaag71        try {
94*5153720fSfkaag71            $this->_db = $this->initializePDO($dsn);
95*5153720fSfkaag71        } catch(PDOException $e) {
96*5153720fSfkaag71            if ($this->_debug) {
97*5153720fSfkaag71                msg(sprintf($this->util->getLang('driver_failed_detail'), hsc($dsn), hsc($e->getMessage())), -1);
98*5153720fSfkaag71            } else {
99*5153720fSfkaag71                msg($this->util->getLang('driver_failed'), -1);
100*5153720fSfkaag71            }
101*5153720fSfkaag71            return false;
102*5153720fSfkaag71        }
103*5153720fSfkaag71        $this->initializeConnection();
104*5153720fSfkaag71        return true;
105*5153720fSfkaag71    }
106*5153720fSfkaag71
107*5153720fSfkaag71    /**
108*5153720fSfkaag71     * Initialize the PDO object.
109*5153720fSfkaag71     *
110*5153720fSfkaag71     * @param dsn string the dsn to use for construction
111*5153720fSfkaag71     * @return the PDO object
112*5153720fSfkaag71     */
113*5153720fSfkaag71    protected function initializePDO($dsn) {
114*5153720fSfkaag71        $credentials = array(null,null);
115*5153720fSfkaag71        if(@file_exists(STRATA_CREDENTIALS)) {
116*5153720fSfkaag71            $credentials = include(STRATA_CREDENTIALS);
117*5153720fSfkaag71        }
118*5153720fSfkaag71        return new PDO($dsn, $credentials[0], $credentials[1]);
119*5153720fSfkaag71    }
120*5153720fSfkaag71
121*5153720fSfkaag71
122*5153720fSfkaag71    /**
123*5153720fSfkaag71     * Initialises a connection directly after the connection was made (e.g. by setting the character set of the connection).
124*5153720fSfkaag71     */
125*5153720fSfkaag71    protected function initializeConnection() {}
126*5153720fSfkaag71
127*5153720fSfkaag71    /**
128*5153720fSfkaag71     * Determines whether the database is initialised.
129*5153720fSfkaag71     *
130*5153720fSfkaag71     * @return boolean true if the database is initialised
131*5153720fSfkaag71     */
132*5153720fSfkaag71    public abstract function isInitialized();
133*5153720fSfkaag71
134*5153720fSfkaag71    /**
135*5153720fSfkaag71     * Initialises the database by setting up all tables.
136*5153720fSfkaag71     *
137*5153720fSfkaag71     * This implementation looks for a file called 'setup-@driver@.sql' and executes all SQL statements in that file.
138*5153720fSfkaag71     * Here, '@driver@' represents the database driver, such as 'sqlite'.
139*5153720fSfkaag71     *
140*5153720fSfkaag71     * @return boolean true if the database was initialised successfully
141*5153720fSfkaag71     */
142*5153720fSfkaag71    public function initializeDatabase() {
143*5153720fSfkaag71        if($this->_db == false) return false;
144*5153720fSfkaag71
145*5153720fSfkaag71        // determine driver
146*5153720fSfkaag71        list($driver, $connection) = explode(':', $this->_dsn, 2);
147*5153720fSfkaag71        if ($this->_debug) msg(sprintf($this->util->getLang('driver_setup_start'), hsc($driver)));
148*5153720fSfkaag71
149*5153720fSfkaag71        // load SQL script
150*5153720fSfkaag71        $sqlfile = DOKU_PLUGIN . "strata/sql/setup-$driver.sql";
151*5153720fSfkaag71
152*5153720fSfkaag71        $sql = io_readFile($sqlfile, false);
153*5153720fSfkaag71        $lines = explode("\n",$sql);
154*5153720fSfkaag71
155*5153720fSfkaag71        // remove empty lines and comment lines
156*5153720fSfkaag71        // (this makes sure that a semicolon in the comment doesn't break the script)
157*5153720fSfkaag71        $sql = '';
158*5153720fSfkaag71        foreach($lines as $line) {
159*5153720fSfkaag71            $line = preg_replace('/--.*$/','',$line);
160*5153720fSfkaag71            if(trim($line," \t\n\r") == '') continue;
161*5153720fSfkaag71            $sql .= $line;
162*5153720fSfkaag71        }
163*5153720fSfkaag71
164*5153720fSfkaag71        // split the script into distinct statements
165*5153720fSfkaag71        $sql = explode(';', $sql);
166*5153720fSfkaag71
167*5153720fSfkaag71        // execute the database initialisation script in a transaction
168*5153720fSfkaag71        // (doesn't work in all databases, but provides some failsafe where it works)
169*5153720fSfkaag71        $this->beginTransaction();
170*5153720fSfkaag71        foreach($sql as $s) {
171*5153720fSfkaag71            // skip empty lines (usually the last line is empty, due to the final semicolon)
172*5153720fSfkaag71            if(trim($s) == '') continue;
173*5153720fSfkaag71
174*5153720fSfkaag71            if ($this->_debug) msg(sprintf($this->util->getLang('driver_setup_statement'),hsc($s)));
175*5153720fSfkaag71            if(!$this->query($s, $this->util->getLang('driver_setup_failed'))) {
176*5153720fSfkaag71                $this->rollBack();
177*5153720fSfkaag71                return false;
178*5153720fSfkaag71            }
179*5153720fSfkaag71        }
180*5153720fSfkaag71        $this->commit();
181*5153720fSfkaag71
182*5153720fSfkaag71        if($this->_debug) msg($this->util->getLang('driver_setup_succes'), 1);
183*5153720fSfkaag71
184*5153720fSfkaag71        return true;
185*5153720fSfkaag71    }
186*5153720fSfkaag71
187*5153720fSfkaag71    /**
188*5153720fSfkaag71     * Removes a database that was initialized before.
189*5153720fSfkaag71     *
190*5153720fSfkaag71     * @return whether the database was removed successfully
191*5153720fSfkaag71     */
192*5153720fSfkaag71    public function removeDatabase() {
193*5153720fSfkaag71        return $this->query('DROP TABLE data', $this->util->getLang('driver_remove_failed'));
194*5153720fSfkaag71    }
195*5153720fSfkaag71
196*5153720fSfkaag71    /**
197*5153720fSfkaag71     * Prepares a query and reports any problems to Dokuwiki.
198*5153720fSfkaag71     *
199*5153720fSfkaag71     * @param query string the query to prepare
200*5153720fSfkaag71     * @return the prepared statement
201*5153720fSfkaag71     */
202*5153720fSfkaag71    public function prepare($query) {
203*5153720fSfkaag71        if($this->_db == false) return false;
204*5153720fSfkaag71
205*5153720fSfkaag71        $result = $this->_db->prepare($query);
206*5153720fSfkaag71        if ($result === false) {
207*5153720fSfkaag71            $error = $this->_db->errorInfo();
208*5153720fSfkaag71            msg(sprintf($this->util->getLang('driver_prepare_failed'),hsc($query), hsc($error[2])),-1);
209*5153720fSfkaag71            return false;
210*5153720fSfkaag71        }
211*5153720fSfkaag71
212*5153720fSfkaag71        return $result;
213*5153720fSfkaag71    }
214*5153720fSfkaag71
215*5153720fSfkaag71     /**
216*5153720fSfkaag71      * Executes a query and reports any problems to Dokuwiki.
217*5153720fSfkaag71      *
218*5153720fSfkaag71      * @param query string the query to execute
219*5153720fSfkaag71      * @param message string message to report when executing the query fails
220*5153720fSfkaag71      * @return whether querying succeeded
221*5153720fSfkaag71      */
222*5153720fSfkaag71    public function query($query, $message=false) {
223*5153720fSfkaag71        if($this->_db == false) return false;
224*5153720fSfkaag71
225*5153720fSfkaag71        if($message === false) {
226*5153720fSfkaag71            $message = $this->util->getLang('driver_query_failed_default');
227*5153720fSfkaag71        }
228*5153720fSfkaag71
229*5153720fSfkaag71        $res = $this->_db->query($query);
230*5153720fSfkaag71        if ($res === false) {
231*5153720fSfkaag71            $error = $this->_db->errorInfo();
232*5153720fSfkaag71            msg(sprintf($this->utiutil->getLang('driver_query_failed'), $message, hsc($query), hsc($error[2])),-1);
233*5153720fSfkaag71            return false;
234*5153720fSfkaag71        }
235*5153720fSfkaag71        return true;
236*5153720fSfkaag71    }
237*5153720fSfkaag71
238*5153720fSfkaag71    private $transactions = array();
239*5153720fSfkaag71    private $transactionCount = 0;
240*5153720fSfkaag71
241*5153720fSfkaag71    private function _transactionId() {
242*5153720fSfkaag71        return "t".$this->transactionCount++;
243*5153720fSfkaag71    }
244*5153720fSfkaag71
245*5153720fSfkaag71    /**
246*5153720fSfkaag71     * Begins a transaction.
247*5153720fSfkaag71     */
248*5153720fSfkaag71    public function beginTransaction() {
249*5153720fSfkaag71        if($this->_db == false) return false;
250*5153720fSfkaag71
251*5153720fSfkaag71        if(count($this->transactions)) {
252*5153720fSfkaag71            $t = $this->_transactionId();
253*5153720fSfkaag71            array_push($this->transactions, $t);
254*5153720fSfkaag71            $this->_db->query('SAVEPOINT '.$t.';');
255*5153720fSfkaag71            return true;
256*5153720fSfkaag71        } else {
257*5153720fSfkaag71            array_push($this->transactions, 'work');
258*5153720fSfkaag71            return $this->_db->beginTransaction();
259*5153720fSfkaag71        }
260*5153720fSfkaag71    }
261*5153720fSfkaag71
262*5153720fSfkaag71    /**
263*5153720fSfkaag71     * Commits the current transaction.
264*5153720fSfkaag71     */
265*5153720fSfkaag71    public function commit() {
266*5153720fSfkaag71        if($this->_db == false) return false;
267*5153720fSfkaag71
268*5153720fSfkaag71        array_pop($this->transactions);
269*5153720fSfkaag71        if(count($this->transactions)) {
270*5153720fSfkaag71            return true;
271*5153720fSfkaag71        } else {
272*5153720fSfkaag71            return $this->_db->commit();
273*5153720fSfkaag71        }
274*5153720fSfkaag71    }
275*5153720fSfkaag71
276*5153720fSfkaag71    /**
277*5153720fSfkaag71     * Rolls back the current transaction.
278*5153720fSfkaag71     */
279*5153720fSfkaag71    public function rollBack() {
280*5153720fSfkaag71        if($this->_db == false) return false;
281*5153720fSfkaag71
282*5153720fSfkaag71        $t = array_pop($this->transactions);
283*5153720fSfkaag71        if(count($this->transactions)) {
284*5153720fSfkaag71            $this->_db->query('ROLLBACK TO '.$t.';');
285*5153720fSfkaag71            return true;
286*5153720fSfkaag71        } else {
287*5153720fSfkaag71            return $this->_db->rollBack();
288*5153720fSfkaag71        }
289*5153720fSfkaag71    }
290*5153720fSfkaag71}
291