1a1e6784eSAndreas Gohr<?php 28da7d805SAndreas Gohr 38da7d805SAndreas Gohr/** 48da7d805SAndreas Gohr * @noinspection SqlNoDataSourceInspection 58da7d805SAndreas Gohr * @noinspection SqlDialectInspection 68da7d805SAndreas Gohr * @noinspection PhpComposerExtensionStubsInspection 78da7d805SAndreas Gohr */ 88da7d805SAndreas Gohr 98da7d805SAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 108da7d805SAndreas Gohruse dokuwiki\plugin\sqlite\Tools; 118da7d805SAndreas Gohr 128da7d805SAndreas Gohr 13801b921eSSzymon Olewniczak 14801b921eSSzymon Olewniczak/** 15801b921eSSzymon Olewniczak * For compatibility with previous adapter implementation. 16801b921eSSzymon Olewniczak */ 17801b921eSSzymon Olewniczakif(!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo'); 18801b921eSSzymon Olewniczakclass helper_plugin_sqlite_adapter_dummy 19801b921eSSzymon Olewniczak{ 20801b921eSSzymon Olewniczak public function getName() { 21801b921eSSzymon Olewniczak return DOKU_EXT_PDO; 22801b921eSSzymon Olewniczak } 23801b921eSSzymon Olewniczak 24801b921eSSzymon Olewniczak public function setUseNativeAlter($set) {} 25801b921eSSzymon Olewniczak} 26801b921eSSzymon Olewniczak 27a1e6784eSAndreas Gohr/** 28a1e6784eSAndreas Gohr * DokuWiki Plugin sqlite (Helper Component) 29a1e6784eSAndreas Gohr * 30a1e6784eSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 31a1e6784eSAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 328da7d805SAndreas Gohr * @deprecated 2023-03-15 33a1e6784eSAndreas Gohr */ 348da7d805SAndreas Gohrclass helper_plugin_sqlite extends DokuWiki_Plugin 358da7d805SAndreas Gohr{ 368da7d805SAndreas Gohr /** @var SQLiteDB|null */ 376c7ee3f2SAndreas Gohr protected $adapter = null; 38aa81d781SKlap-in 398da7d805SAndreas Gohr /** @var array result cache */ 408da7d805SAndreas Gohr protected $data; 418da7d805SAndreas Gohr 423e9ac593SGerrit Uitslag /** 438da7d805SAndreas Gohr * constructor 443e9ac593SGerrit Uitslag */ 458da7d805SAndreas Gohr public function __construct() 468da7d805SAndreas Gohr { 478da7d805SAndreas Gohr if (!$this->existsPDOSqlite()) { 488da7d805SAndreas Gohr msg('PDO SQLite support missing in this PHP install - The sqlite plugin will not work', -1); 498da7d805SAndreas Gohr } 50801b921eSSzymon Olewniczak $this->adapter = new helper_plugin_sqlite_adapter_dummy(); 518da7d805SAndreas Gohr } 528da7d805SAndreas Gohr 538da7d805SAndreas Gohr /** 548da7d805SAndreas Gohr * Get the current Adapter 558da7d805SAndreas Gohr * @return SQLiteDB|null 568da7d805SAndreas Gohr */ 578da7d805SAndreas Gohr public function getAdapter() 588da7d805SAndreas Gohr { 59ca2f2adaSKlap-in return $this->adapter; 60ca2f2adaSKlap-in } 61ca2f2adaSKlap-in 62a1e6784eSAndreas Gohr /** 63aa81d781SKlap-in * Keep separate instances for every call to keep database connections 64aa81d781SKlap-in */ 658da7d805SAndreas Gohr public function isSingleton() 668da7d805SAndreas Gohr { 67aa81d781SKlap-in return false; 68aa81d781SKlap-in } 69aa81d781SKlap-in 70aa81d781SKlap-in /** 71aa81d781SKlap-in * check availabilty of PHP PDO sqlite3 72aa81d781SKlap-in */ 738da7d805SAndreas Gohr public function existsPDOSqlite() 748da7d805SAndreas Gohr { 7587fa2c18Sstretchyboy if (class_exists('pdo')) { 768da7d805SAndreas Gohr return in_array('sqlite', \PDO::getAvailableDrivers()); 777ed6069fSAdrian Lang } 78aa81d781SKlap-in return false; 79a1e6784eSAndreas Gohr } 80a1e6784eSAndreas Gohr 81a1e6784eSAndreas Gohr /** 82a1e6784eSAndreas Gohr * Initializes and opens the database 83a1e6784eSAndreas Gohr * 84a1e6784eSAndreas Gohr * Needs to be called right after loading this helper plugin 85aa81d781SKlap-in * 86aa81d781SKlap-in * @param string $dbname 87aa81d781SKlap-in * @param string $updatedir - Database update infos 88aa81d781SKlap-in * @return bool 89a1e6784eSAndreas Gohr */ 908da7d805SAndreas Gohr public function init($dbname, $updatedir) 918da7d805SAndreas Gohr { 92801b921eSSzymon Olewniczak if(!defined('DOKU_UNITTEST')) { // for now we don't want to trigger the deprecation warning in the tests 933a56750bSAndreas Gohr dbg_deprecated(SQLiteDB::class); 94d0a5ba7aSAndreas Gohr } 953a56750bSAndreas Gohr 968da7d805SAndreas Gohr try { 978da7d805SAndreas Gohr $this->adapter = new SQLiteDB($dbname, $updatedir, $this); 988da7d805SAndreas Gohr } catch (Exception $e) { 998da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 100c137e95fSAndreas Gohr return false; 101c137e95fSAndreas Gohr } 102a1e6784eSAndreas Gohr return true; 103a1e6784eSAndreas Gohr } 104a1e6784eSAndreas Gohr 105a1e6784eSAndreas Gohr /** 1068da7d805SAndreas Gohr * This is called from the adapter itself for backwards compatibility 1078da7d805SAndreas Gohr * 1088da7d805SAndreas Gohr * @param SQLiteDB $adapter 1098da7d805SAndreas Gohr * @return void 110a1e6784eSAndreas Gohr */ 1118da7d805SAndreas Gohr function setAdapter($adapter) 1128da7d805SAndreas Gohr { 1138da7d805SAndreas Gohr $this->adapter = $adapter; 114a34ef333SKlap-in } 115f10ea6c1SKlap-in 116a1e6784eSAndreas Gohr /** 1173ae3f79eSKlap-in * Registers a User Defined Function for use in SQL statements 1183ae3f79eSKlap-in */ 1198da7d805SAndreas Gohr public function create_function($function_name, $callback, $num_args) 1208da7d805SAndreas Gohr { 12154f742ccSSzymon Olewniczak $this->adapter->getDb()->sqliteCreateFunction($function_name, $callback, $num_args); 1223ae3f79eSKlap-in } 1233ae3f79eSKlap-in 1248da7d805SAndreas Gohr // region query and result handling functions 1258da7d805SAndreas Gohr 1263ae3f79eSKlap-in /** 127e7b0736cSAndreas Gohr * Convenience function to run an INSERT OR REPLACE operation 128e7b0736cSAndreas Gohr * 129e7b0736cSAndreas Gohr * The function takes a key-value array with the column names in the key and the actual value in the value, 130e7b0736cSAndreas Gohr * build the appropriate query and executes it. 131e7b0736cSAndreas Gohr * 132e7b0736cSAndreas Gohr * @param string $table the table the entry should be saved to (will not be escaped) 133e7b0736cSAndreas Gohr * @param array $entry A simple key-value pair array (only values will be escaped) 1348da7d805SAndreas Gohr * @return bool 135e7b0736cSAndreas Gohr */ 1368da7d805SAndreas Gohr public function storeEntry($table, $entry) 1378da7d805SAndreas Gohr { 1388da7d805SAndreas Gohr try { 1398da7d805SAndreas Gohr $this->adapter->saveRecord($table, $entry); 1408da7d805SAndreas Gohr } catch (\Exception $e) { 1418da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 1428da7d805SAndreas Gohr return false; 143e7b0736cSAndreas Gohr } 144e7b0736cSAndreas Gohr 1458da7d805SAndreas Gohr return true; 1468da7d805SAndreas Gohr } 147e7b0736cSAndreas Gohr 148e7b0736cSAndreas Gohr /** 149a1e6784eSAndreas Gohr * Execute a query with the given parameters. 150a1e6784eSAndreas Gohr * 151a1e6784eSAndreas Gohr * Takes care of escaping 152a1e6784eSAndreas Gohr * 153a2a82480SAndreas Gohr * 154a2a82480SAndreas Gohr * @param string ...$args - the arguments of query(), the first is the sql and others are values 155a1e6784eSAndreas Gohr */ 1568da7d805SAndreas Gohr public function query() 1578da7d805SAndreas Gohr { 158a1e6784eSAndreas Gohr // get function arguments 159a1e6784eSAndreas Gohr $args = func_get_args(); 1606917135cSSzymon Olewniczak 1616917135cSSzymon Olewniczak // clear the cache 1626917135cSSzymon Olewniczak $this->data = null; 1636917135cSSzymon Olewniczak 1648da7d805SAndreas Gohr try { 165*1973f122SSzymon Olewniczak $sql = $this->prepareSql($args); 166*1973f122SSzymon Olewniczak return $this->adapter->query($sql); 1678da7d805SAndreas Gohr } catch (\Exception $e) { 1688da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 1698da7d805SAndreas Gohr return false; 1708da7d805SAndreas Gohr } 17187fa2c18Sstretchyboy } 172a1e6784eSAndreas Gohr 173*1973f122SSzymon Olewniczak /** 174*1973f122SSzymon Olewniczak * Prepare a query with the given arguments. 175*1973f122SSzymon Olewniczak * 176*1973f122SSzymon Olewniczak * Takes care of escaping 177*1973f122SSzymon Olewniczak * 178*1973f122SSzymon Olewniczak * @param array $args 179*1973f122SSzymon Olewniczak * array of arguments: 180*1973f122SSzymon Olewniczak * - string $sql - the statement 181*1973f122SSzymon Olewniczak * - arguments... 182*1973f122SSzymon Olewniczak * @return bool|string 183*1973f122SSzymon Olewniczak * @throws Exception 184*1973f122SSzymon Olewniczak */ 185*1973f122SSzymon Olewniczak public function prepareSql($args) { 186*1973f122SSzymon Olewniczak 187*1973f122SSzymon Olewniczak $sql = trim(array_shift($args)); 188*1973f122SSzymon Olewniczak $sql = rtrim($sql, ';'); 189*1973f122SSzymon Olewniczak 190*1973f122SSzymon Olewniczak if(!$sql) { 191*1973f122SSzymon Olewniczak throw new \Exception('No SQL statement given', -1); 192*1973f122SSzymon Olewniczak } 193*1973f122SSzymon Olewniczak 194*1973f122SSzymon Olewniczak $argc = count($args); 195*1973f122SSzymon Olewniczak if($argc > 0 && is_array($args[0])) { 196*1973f122SSzymon Olewniczak $args = $args[0]; 197*1973f122SSzymon Olewniczak $argc = count($args); 198*1973f122SSzymon Olewniczak } 199*1973f122SSzymon Olewniczak 200*1973f122SSzymon Olewniczak // check number of arguments 201*1973f122SSzymon Olewniczak $qmc = substr_count($sql, '?'); 202*1973f122SSzymon Olewniczak if ($argc < $qmc) { 203*1973f122SSzymon Olewniczak throw new \Exception('Not enough arguments passed for statement. ' . 204*1973f122SSzymon Olewniczak 'Expected '.$qmc.' got '. $argc.' - '.hsc($sql)); 205*1973f122SSzymon Olewniczak } elseif($argc > $qmc) { 206*1973f122SSzymon Olewniczak throw new \Exception('Too much arguments passed for statement. ' . 207*1973f122SSzymon Olewniczak 'Expected '.$qmc.' got '. $argc.' - '.hsc($sql)); 208*1973f122SSzymon Olewniczak } 209*1973f122SSzymon Olewniczak 210*1973f122SSzymon Olewniczak // explode at wildcard, then join again 211*1973f122SSzymon Olewniczak $parts = explode('?', $sql, $argc + 1); 212*1973f122SSzymon Olewniczak $args = array_map([$this->adapter->getDb(), 'quote'], $args); 213*1973f122SSzymon Olewniczak $sql = ''; 214*1973f122SSzymon Olewniczak 215*1973f122SSzymon Olewniczak while(($part = array_shift($parts)) !== null) { 216*1973f122SSzymon Olewniczak $sql .= $part; 217*1973f122SSzymon Olewniczak $sql .= array_shift($args); 218*1973f122SSzymon Olewniczak } 219*1973f122SSzymon Olewniczak 220*1973f122SSzymon Olewniczak return $sql; 221*1973f122SSzymon Olewniczak } 222*1973f122SSzymon Olewniczak 223ff97cc8fSstretchyboy 224ff97cc8fSstretchyboy /** 225b122d121SAndreas Gohr * Closes the result set (and it's cursors) 226b122d121SAndreas Gohr * 227b122d121SAndreas Gohr * If you're doing SELECT queries inside a TRANSACTION, be sure to call this 228b122d121SAndreas Gohr * function on all your results sets, before COMMITing the transaction. 229b122d121SAndreas Gohr * 2304b4b2db0SGerrit Uitslag * Also required when not all rows of a result are fetched 2314b4b2db0SGerrit Uitslag * 2328da7d805SAndreas Gohr * @param \PDOStatement $res 233b122d121SAndreas Gohr * @return bool 234b122d121SAndreas Gohr */ 2358da7d805SAndreas Gohr public function res_close($res) 2368da7d805SAndreas Gohr { 2378da7d805SAndreas Gohr if (!$res) return false; 2388da7d805SAndreas Gohr 2398da7d805SAndreas Gohr return $res->closeCursor(); 240b122d121SAndreas Gohr } 241b122d121SAndreas Gohr 242b122d121SAndreas Gohr /** 243aa81d781SKlap-in * Returns a complete result set as array 2448da7d805SAndreas Gohr * 2458da7d805SAndreas Gohr * @param \PDOStatement $res 2468da7d805SAndreas Gohr * @return array 247ff97cc8fSstretchyboy */ 2488da7d805SAndreas Gohr public function res2arr($res, $assoc = true) 2498da7d805SAndreas Gohr { 2508da7d805SAndreas Gohr if (!$res) return []; 2518da7d805SAndreas Gohr 2528da7d805SAndreas Gohr // this is a bullshit workaround for having res2arr and res2count work on one result 2538da7d805SAndreas Gohr if (!$this->data) { 2548da7d805SAndreas Gohr $mode = $assoc ? PDO::FETCH_ASSOC : PDO::FETCH_NUM; 2558da7d805SAndreas Gohr $this->data = $res->fetchAll($mode); 2568da7d805SAndreas Gohr } 2578da7d805SAndreas Gohr return $this->data; 258b5b947d7SAndreas Gohr } 259b5b947d7SAndreas Gohr 260b5b947d7SAndreas Gohr /** 2618da7d805SAndreas Gohr * Return the next row from the result set as associative array 2628da7d805SAndreas Gohr * 2638da7d805SAndreas Gohr * @param \PDOStatement $res 2648da7d805SAndreas Gohr * @param int $rownum will be ignored 265b5b947d7SAndreas Gohr */ 2668da7d805SAndreas Gohr public function res2row($res, $rownum = 0) 2678da7d805SAndreas Gohr { 2688da7d805SAndreas Gohr if (!$res) return false; 2698da7d805SAndreas Gohr 2708da7d805SAndreas Gohr return $res->fetch(\PDO::FETCH_ASSOC); 271b5b947d7SAndreas Gohr } 272b5b947d7SAndreas Gohr 273e7112ccbSAdrian Lang /** 27444685fc6SKlap-in * Return the first value from the next row. 2758da7d805SAndreas Gohr * 2768da7d805SAndreas Gohr * @param \PDOStatement $res 2778da7d805SAndreas Gohr * @return mixed 278e7112ccbSAdrian Lang */ 2798da7d805SAndreas Gohr public function res2single($res) 2808da7d805SAndreas Gohr { 2818da7d805SAndreas Gohr if (!$res) return false; 2828da7d805SAndreas Gohr 2838da7d805SAndreas Gohr $data = $res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 0); 2848da7d805SAndreas Gohr if (empty($data)) { 2858da7d805SAndreas Gohr return false; 2868da7d805SAndreas Gohr } 2878da7d805SAndreas Gohr return $data[0]; 288e7112ccbSAdrian Lang } 289fee3b689Sstretchyboy 290fee3b689Sstretchyboy /** 291fee3b689Sstretchyboy * fetch the next row as zero indexed array 2928da7d805SAndreas Gohr * 2938da7d805SAndreas Gohr * @param \PDOStatement $res 2948da7d805SAndreas Gohr * @return array|bool 295fee3b689Sstretchyboy */ 2968da7d805SAndreas Gohr public function res_fetch_array($res) 2978da7d805SAndreas Gohr { 2988da7d805SAndreas Gohr if (!$res) return false; 2998da7d805SAndreas Gohr 3008da7d805SAndreas Gohr return $res->fetch(PDO::FETCH_NUM); 30187fa2c18Sstretchyboy } 302fee3b689Sstretchyboy 303fee3b689Sstretchyboy /** 304fee3b689Sstretchyboy * fetch the next row as assocative array 3058da7d805SAndreas Gohr * 3068da7d805SAndreas Gohr * @param \PDOStatement $res 3078da7d805SAndreas Gohr * @return array|bool 308fee3b689Sstretchyboy */ 3098da7d805SAndreas Gohr public function res_fetch_assoc($res) 3108da7d805SAndreas Gohr { 3118da7d805SAndreas Gohr if (!$res) return false; 3128da7d805SAndreas Gohr 3138da7d805SAndreas Gohr return $res->fetch(PDO::FETCH_ASSOC); 314fee3b689Sstretchyboy } 315fee3b689Sstretchyboy 316fee3b689Sstretchyboy /** 31778977d74SKlap-in * Count the number of records in result 3183157674bSAndreas Gohr * 319db58e525SKlap-in * This function is really inperformant in PDO and should be avoided! 3208da7d805SAndreas Gohr * 3218da7d805SAndreas Gohr * @param \PDOStatement $res 3228da7d805SAndreas Gohr * @return int 323fee3b689Sstretchyboy */ 3248da7d805SAndreas Gohr public function res2count($res) 3258da7d805SAndreas Gohr { 3268da7d805SAndreas Gohr if (!$res) return 0; 3278da7d805SAndreas Gohr 3288da7d805SAndreas Gohr // this is a bullshit workaround for having res2arr and res2count work on one result 3298da7d805SAndreas Gohr if (!$this->data) { 3308da7d805SAndreas Gohr $this->data = $this->res2arr($res); 3318da7d805SAndreas Gohr } 3328da7d805SAndreas Gohr 3338da7d805SAndreas Gohr return count($this->data); 334fee3b689Sstretchyboy } 33524a03f6cSstretchyboy 33624a03f6cSstretchyboy /** 33724a03f6cSstretchyboy * Count the number of records changed last time 3388da7d805SAndreas Gohr * 3398da7d805SAndreas Gohr * @param \PDOStatement $res 3408da7d805SAndreas Gohr * @return int 34124a03f6cSstretchyboy */ 3428da7d805SAndreas Gohr public function countChanges($res) 3438da7d805SAndreas Gohr { 3448da7d805SAndreas Gohr if (!$res) return 0; 3458da7d805SAndreas Gohr 3468da7d805SAndreas Gohr return $res->rowCount(); 347a1e6784eSAndreas Gohr } 348a1e6784eSAndreas Gohr 3498da7d805SAndreas Gohr // endregion 3508da7d805SAndreas Gohr 3518da7d805SAndreas Gohr // region quoting/escaping functions 3528da7d805SAndreas Gohr 3538da7d805SAndreas Gohr /** 3548da7d805SAndreas Gohr * Join the given values and quote them for SQL insertion 3558da7d805SAndreas Gohr */ 3568da7d805SAndreas Gohr public function quote_and_join($vals, $sep = ',') 3578da7d805SAndreas Gohr { 35854f742ccSSzymon Olewniczak $vals = array_map([$this->adapter->getDb(), 'quote'], $vals); 3598da7d805SAndreas Gohr return join($sep, $vals); 3608da7d805SAndreas Gohr } 3618da7d805SAndreas Gohr 3628da7d805SAndreas Gohr /** 3638da7d805SAndreas Gohr * Quotes a string, by escaping it and adding quotes 3648da7d805SAndreas Gohr */ 3658da7d805SAndreas Gohr public function quote_string($string) 3668da7d805SAndreas Gohr { 36754f742ccSSzymon Olewniczak return $this->adapter->getDb()->quote($string); 3688da7d805SAndreas Gohr } 3698da7d805SAndreas Gohr 3708da7d805SAndreas Gohr /** 3718da7d805SAndreas Gohr * Similar to quote_string, but without the quotes, useful to construct LIKE patterns 3728da7d805SAndreas Gohr */ 3738da7d805SAndreas Gohr public function escape_string($str) 3748da7d805SAndreas Gohr { 37554f742ccSSzymon Olewniczak return trim($this->adapter->getDb()->quote($str), "'"); 3768da7d805SAndreas Gohr } 3778da7d805SAndreas Gohr 3788da7d805SAndreas Gohr // endregion 3798da7d805SAndreas Gohr 3808da7d805SAndreas Gohr // region speciality functions 3818da7d805SAndreas Gohr 3828da7d805SAndreas Gohr /** 3838da7d805SAndreas Gohr * Split sql queries on semicolons, unless when semicolons are quoted 3848da7d805SAndreas Gohr * 3858da7d805SAndreas Gohr * Usually you don't need this. It's only really needed if you need individual results for 3868da7d805SAndreas Gohr * multiple queries. For example in the admin interface. 3878da7d805SAndreas Gohr * 3888da7d805SAndreas Gohr * @param string $sql 3898da7d805SAndreas Gohr * @return array sql queries 3908da7d805SAndreas Gohr * @deprecated 3918da7d805SAndreas Gohr */ 3928da7d805SAndreas Gohr public function SQLstring2array($sql) 3938da7d805SAndreas Gohr { 394d0a5ba7aSAndreas Gohr if(!DOKU_UNITTEST) { // for now we don't want to trigger the deprecation warning in the tests 3958da7d805SAndreas Gohr dbg_deprecated(Tools::class . '::SQLstring2array'); 396d0a5ba7aSAndreas Gohr } 3978da7d805SAndreas Gohr return Tools::SQLstring2array($sql); 3988da7d805SAndreas Gohr } 3998da7d805SAndreas Gohr 4008da7d805SAndreas Gohr /** 4018da7d805SAndreas Gohr * @deprecated needs to be fixed in stuct and structpublish 4028da7d805SAndreas Gohr */ 4038da7d805SAndreas Gohr public function doTransaction($sql, $sqlpreparing = true) { 4048da7d805SAndreas Gohr throw new \Exception( 4058da7d805SAndreas Gohr 'This method seems to never have done what it suggests. Please use the query() function instead.' 4068da7d805SAndreas Gohr ); 4078da7d805SAndreas Gohr } 4088da7d805SAndreas Gohr 4098da7d805SAndreas Gohr // endregion 410aa81d781SKlap-in} 411