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