1a1e6784eSAndreas Gohr<?php 28da7d805SAndreas Gohr 38da7d805SAndreas Gohr/** 48da7d805SAndreas Gohr * @noinspection SqlNoDataSourceInspection 58da7d805SAndreas Gohr * @noinspection SqlDialectInspection 68da7d805SAndreas Gohr * @noinspection PhpComposerExtensionStubsInspection 78da7d805SAndreas Gohr */ 88da7d805SAndreas Gohr 9*a7a40fb2SAnna Dabrowskause dokuwiki\Extension\Plugin; 108da7d805SAndreas Gohruse dokuwiki\plugin\sqlite\SQLiteDB; 118da7d805SAndreas Gohruse dokuwiki\plugin\sqlite\Tools; 128da7d805SAndreas Gohr 13801b921eSSzymon Olewniczak/** 14801b921eSSzymon Olewniczak * For compatibility with previous adapter implementation. 15801b921eSSzymon Olewniczak */ 16801b921eSSzymon Olewniczakif (!defined('DOKU_EXT_PDO')) define('DOKU_EXT_PDO', 'pdo'); 17801b921eSSzymon Olewniczakclass helper_plugin_sqlite_adapter_dummy 18801b921eSSzymon Olewniczak{ 19*a7a40fb2SAnna Dabrowska public function getName() 20*a7a40fb2SAnna Dabrowska { 21801b921eSSzymon Olewniczak return DOKU_EXT_PDO; 22801b921eSSzymon Olewniczak } 23801b921eSSzymon Olewniczak 24*a7a40fb2SAnna Dabrowska public function setUseNativeAlter($set) 25*a7a40fb2SAnna Dabrowska { 26*a7a40fb2SAnna Dabrowska } 27801b921eSSzymon Olewniczak} 28801b921eSSzymon Olewniczak 29a1e6784eSAndreas Gohr/** 30a1e6784eSAndreas Gohr * DokuWiki Plugin sqlite (Helper Component) 31a1e6784eSAndreas Gohr * 32a1e6784eSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 33a1e6784eSAndreas Gohr * @author Andreas Gohr <gohr@cosmocode.de> 348da7d805SAndreas Gohr * @deprecated 2023-03-15 35a1e6784eSAndreas Gohr */ 36*a7a40fb2SAnna Dabrowskaclass helper_plugin_sqlite extends Plugin 378da7d805SAndreas Gohr{ 388da7d805SAndreas Gohr /** @var SQLiteDB|null */ 39*a7a40fb2SAnna Dabrowska protected $adapter; 40aa81d781SKlap-in 418da7d805SAndreas Gohr /** @var array result cache */ 428da7d805SAndreas Gohr protected $data; 438da7d805SAndreas Gohr 443e9ac593SGerrit Uitslag /** 458da7d805SAndreas Gohr * constructor 463e9ac593SGerrit Uitslag */ 478da7d805SAndreas Gohr public function __construct() 488da7d805SAndreas Gohr { 498da7d805SAndreas Gohr if (!$this->existsPDOSqlite()) { 508da7d805SAndreas Gohr msg('PDO SQLite support missing in this PHP install - The sqlite plugin will not work', -1); 518da7d805SAndreas Gohr } 52801b921eSSzymon Olewniczak $this->adapter = new helper_plugin_sqlite_adapter_dummy(); 538da7d805SAndreas Gohr } 548da7d805SAndreas Gohr 558da7d805SAndreas Gohr /** 568da7d805SAndreas Gohr * Get the current Adapter 578da7d805SAndreas Gohr * @return SQLiteDB|null 588da7d805SAndreas Gohr */ 598da7d805SAndreas Gohr public function getAdapter() 608da7d805SAndreas Gohr { 61ca2f2adaSKlap-in return $this->adapter; 62ca2f2adaSKlap-in } 63ca2f2adaSKlap-in 64a1e6784eSAndreas Gohr /** 65aa81d781SKlap-in * Keep separate instances for every call to keep database connections 66aa81d781SKlap-in */ 678da7d805SAndreas Gohr public function isSingleton() 688da7d805SAndreas Gohr { 69aa81d781SKlap-in return false; 70aa81d781SKlap-in } 71aa81d781SKlap-in 72aa81d781SKlap-in /** 73aa81d781SKlap-in * check availabilty of PHP PDO sqlite3 74aa81d781SKlap-in */ 758da7d805SAndreas Gohr public function existsPDOSqlite() 768da7d805SAndreas Gohr { 7787fa2c18Sstretchyboy if (class_exists('pdo')) { 788da7d805SAndreas Gohr return in_array('sqlite', \PDO::getAvailableDrivers()); 797ed6069fSAdrian Lang } 80aa81d781SKlap-in return false; 81a1e6784eSAndreas Gohr } 82a1e6784eSAndreas Gohr 83a1e6784eSAndreas Gohr /** 84a1e6784eSAndreas Gohr * Initializes and opens the database 85a1e6784eSAndreas Gohr * 86a1e6784eSAndreas Gohr * Needs to be called right after loading this helper plugin 87aa81d781SKlap-in * 88aa81d781SKlap-in * @param string $dbname 89aa81d781SKlap-in * @param string $updatedir - Database update infos 90aa81d781SKlap-in * @return bool 91a1e6784eSAndreas Gohr */ 928da7d805SAndreas Gohr public function init($dbname, $updatedir) 938da7d805SAndreas Gohr { 94801b921eSSzymon Olewniczak if (!defined('DOKU_UNITTEST')) { // for now we don't want to trigger the deprecation warning in the tests 953a56750bSAndreas Gohr dbg_deprecated(SQLiteDB::class); 96d0a5ba7aSAndreas Gohr } 973a56750bSAndreas Gohr 988da7d805SAndreas Gohr try { 998da7d805SAndreas Gohr $this->adapter = new SQLiteDB($dbname, $updatedir, $this); 1008da7d805SAndreas Gohr } catch (Exception $e) { 1018da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 102c137e95fSAndreas Gohr return false; 103c137e95fSAndreas Gohr } 104a1e6784eSAndreas Gohr return true; 105a1e6784eSAndreas Gohr } 106a1e6784eSAndreas Gohr 107a1e6784eSAndreas Gohr /** 1088da7d805SAndreas Gohr * This is called from the adapter itself for backwards compatibility 1098da7d805SAndreas Gohr * 1108da7d805SAndreas Gohr * @param SQLiteDB $adapter 1118da7d805SAndreas Gohr * @return void 112a1e6784eSAndreas Gohr */ 113*a7a40fb2SAnna Dabrowska public function setAdapter($adapter) 1148da7d805SAndreas Gohr { 1158da7d805SAndreas Gohr $this->adapter = $adapter; 116a34ef333SKlap-in } 117f10ea6c1SKlap-in 118a1e6784eSAndreas Gohr /** 1193ae3f79eSKlap-in * Registers a User Defined Function for use in SQL statements 1203ae3f79eSKlap-in */ 1218da7d805SAndreas Gohr public function create_function($function_name, $callback, $num_args) 1228da7d805SAndreas Gohr { 12310cb854aSAndreas Gohr $this->adapter->getPdo()->sqliteCreateFunction($function_name, $callback, $num_args); 1243ae3f79eSKlap-in } 1253ae3f79eSKlap-in 1268da7d805SAndreas Gohr // region query and result handling functions 1278da7d805SAndreas Gohr 1283ae3f79eSKlap-in /** 129e7b0736cSAndreas Gohr * Convenience function to run an INSERT OR REPLACE operation 130e7b0736cSAndreas Gohr * 131e7b0736cSAndreas Gohr * The function takes a key-value array with the column names in the key and the actual value in the value, 132e7b0736cSAndreas Gohr * build the appropriate query and executes it. 133e7b0736cSAndreas Gohr * 134e7b0736cSAndreas Gohr * @param string $table the table the entry should be saved to (will not be escaped) 135e7b0736cSAndreas Gohr * @param array $entry A simple key-value pair array (only values will be escaped) 1368da7d805SAndreas Gohr * @return bool 137e7b0736cSAndreas Gohr */ 1388da7d805SAndreas Gohr public function storeEntry($table, $entry) 1398da7d805SAndreas Gohr { 1408da7d805SAndreas Gohr try { 1418da7d805SAndreas Gohr $this->adapter->saveRecord($table, $entry); 1428da7d805SAndreas Gohr } catch (\Exception $e) { 1438da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 1448da7d805SAndreas Gohr return false; 145e7b0736cSAndreas Gohr } 146e7b0736cSAndreas Gohr 1478da7d805SAndreas Gohr return true; 1488da7d805SAndreas Gohr } 149e7b0736cSAndreas Gohr 150e7b0736cSAndreas Gohr /** 151a1e6784eSAndreas Gohr * Execute a query with the given parameters. 152a1e6784eSAndreas Gohr * 153a1e6784eSAndreas Gohr * Takes care of escaping 154a1e6784eSAndreas Gohr * 155a2a82480SAndreas Gohr * 156a2a82480SAndreas Gohr * @param string ...$args - the arguments of query(), the first is the sql and others are values 157a1e6784eSAndreas Gohr */ 1588da7d805SAndreas Gohr public function query() 1598da7d805SAndreas Gohr { 160a1e6784eSAndreas Gohr // get function arguments 161a1e6784eSAndreas Gohr $args = func_get_args(); 1626917135cSSzymon Olewniczak 1636917135cSSzymon Olewniczak // clear the cache 1646917135cSSzymon Olewniczak $this->data = null; 1656917135cSSzymon Olewniczak 1668da7d805SAndreas Gohr try { 1671973f122SSzymon Olewniczak $sql = $this->prepareSql($args); 1681973f122SSzymon Olewniczak return $this->adapter->query($sql); 1698da7d805SAndreas Gohr } catch (\Exception $e) { 1708da7d805SAndreas Gohr msg('SQLite: ' . $e->getMessage(), -1); 1718da7d805SAndreas Gohr return false; 1728da7d805SAndreas Gohr } 17387fa2c18Sstretchyboy } 174a1e6784eSAndreas Gohr 1751973f122SSzymon Olewniczak /** 1761973f122SSzymon Olewniczak * Prepare a query with the given arguments. 1771973f122SSzymon Olewniczak * 1781973f122SSzymon Olewniczak * Takes care of escaping 1791973f122SSzymon Olewniczak * 1801973f122SSzymon Olewniczak * @param array $args 1811973f122SSzymon Olewniczak * array of arguments: 1821973f122SSzymon Olewniczak * - string $sql - the statement 1831973f122SSzymon Olewniczak * - arguments... 1841973f122SSzymon Olewniczak * @return bool|string 1851973f122SSzymon Olewniczak * @throws Exception 1861973f122SSzymon Olewniczak */ 187*a7a40fb2SAnna Dabrowska public function prepareSql($args) 188*a7a40fb2SAnna Dabrowska { 1891973f122SSzymon Olewniczak 1901973f122SSzymon Olewniczak $sql = trim(array_shift($args)); 1911973f122SSzymon Olewniczak $sql = rtrim($sql, ';'); 1921973f122SSzymon Olewniczak 1931973f122SSzymon Olewniczak if (!$sql) { 1941973f122SSzymon Olewniczak throw new \Exception('No SQL statement given', -1); 1951973f122SSzymon Olewniczak } 1961973f122SSzymon Olewniczak 1971973f122SSzymon Olewniczak $argc = count($args); 1981973f122SSzymon Olewniczak if ($argc > 0 && is_array($args[0])) { 1991973f122SSzymon Olewniczak $args = $args[0]; 2001973f122SSzymon Olewniczak $argc = count($args); 2011973f122SSzymon Olewniczak } 2021973f122SSzymon Olewniczak 2031973f122SSzymon Olewniczak // check number of arguments 2041973f122SSzymon Olewniczak $qmc = substr_count($sql, '?'); 2051973f122SSzymon Olewniczak if ($argc < $qmc) { 2061973f122SSzymon Olewniczak throw new \Exception('Not enough arguments passed for statement. ' . 2071973f122SSzymon Olewniczak 'Expected ' . $qmc . ' got ' . $argc . ' - ' . hsc($sql)); 2081973f122SSzymon Olewniczak } elseif ($argc > $qmc) { 2091973f122SSzymon Olewniczak throw new \Exception('Too much arguments passed for statement. ' . 2101973f122SSzymon Olewniczak 'Expected ' . $qmc . ' got ' . $argc . ' - ' . hsc($sql)); 2111973f122SSzymon Olewniczak } 2121973f122SSzymon Olewniczak 2131973f122SSzymon Olewniczak // explode at wildcard, then join again 2141973f122SSzymon Olewniczak $parts = explode('?', $sql, $argc + 1); 21510cb854aSAndreas Gohr $args = array_map([$this->adapter->getPdo(), 'quote'], $args); 2161973f122SSzymon Olewniczak $sql = ''; 2171973f122SSzymon Olewniczak 2181973f122SSzymon Olewniczak while (($part = array_shift($parts)) !== null) { 2191973f122SSzymon Olewniczak $sql .= $part; 2201973f122SSzymon Olewniczak $sql .= array_shift($args); 2211973f122SSzymon Olewniczak } 2221973f122SSzymon Olewniczak 2231973f122SSzymon Olewniczak return $sql; 2241973f122SSzymon Olewniczak } 2251973f122SSzymon Olewniczak 226ff97cc8fSstretchyboy 227ff97cc8fSstretchyboy /** 228b122d121SAndreas Gohr * Closes the result set (and it's cursors) 229b122d121SAndreas Gohr * 230b122d121SAndreas Gohr * If you're doing SELECT queries inside a TRANSACTION, be sure to call this 231b122d121SAndreas Gohr * function on all your results sets, before COMMITing the transaction. 232b122d121SAndreas Gohr * 2334b4b2db0SGerrit Uitslag * Also required when not all rows of a result are fetched 2344b4b2db0SGerrit Uitslag * 2358da7d805SAndreas Gohr * @param \PDOStatement $res 236b122d121SAndreas Gohr * @return bool 237b122d121SAndreas Gohr */ 2388da7d805SAndreas Gohr public function res_close($res) 2398da7d805SAndreas Gohr { 2408da7d805SAndreas Gohr if (!$res) return false; 2418da7d805SAndreas Gohr 2428da7d805SAndreas Gohr return $res->closeCursor(); 243b122d121SAndreas Gohr } 244b122d121SAndreas Gohr 245b122d121SAndreas Gohr /** 246aa81d781SKlap-in * Returns a complete result set as array 2478da7d805SAndreas Gohr * 2488da7d805SAndreas Gohr * @param \PDOStatement $res 2498da7d805SAndreas Gohr * @return array 250ff97cc8fSstretchyboy */ 2518da7d805SAndreas Gohr public function res2arr($res, $assoc = true) 2528da7d805SAndreas Gohr { 2538da7d805SAndreas Gohr if (!$res) return []; 2548da7d805SAndreas Gohr 2558da7d805SAndreas Gohr // this is a bullshit workaround for having res2arr and res2count work on one result 2568da7d805SAndreas Gohr if (!$this->data) { 2578da7d805SAndreas Gohr $mode = $assoc ? PDO::FETCH_ASSOC : PDO::FETCH_NUM; 2588da7d805SAndreas Gohr $this->data = $res->fetchAll($mode); 2598da7d805SAndreas Gohr } 2608da7d805SAndreas Gohr return $this->data; 261b5b947d7SAndreas Gohr } 262b5b947d7SAndreas Gohr 263b5b947d7SAndreas Gohr /** 2648da7d805SAndreas Gohr * Return the next row from the result set as associative array 2658da7d805SAndreas Gohr * 2668da7d805SAndreas Gohr * @param \PDOStatement $res 2678da7d805SAndreas Gohr * @param int $rownum will be ignored 268b5b947d7SAndreas Gohr */ 2698da7d805SAndreas Gohr public function res2row($res, $rownum = 0) 2708da7d805SAndreas Gohr { 2718da7d805SAndreas Gohr if (!$res) return false; 2728da7d805SAndreas Gohr 2738da7d805SAndreas Gohr return $res->fetch(\PDO::FETCH_ASSOC); 274b5b947d7SAndreas Gohr } 275b5b947d7SAndreas Gohr 276e7112ccbSAdrian Lang /** 27744685fc6SKlap-in * Return the first value from the next row. 2788da7d805SAndreas Gohr * 2798da7d805SAndreas Gohr * @param \PDOStatement $res 2808da7d805SAndreas Gohr * @return mixed 281e7112ccbSAdrian Lang */ 2828da7d805SAndreas Gohr public function res2single($res) 2838da7d805SAndreas Gohr { 2848da7d805SAndreas Gohr if (!$res) return false; 2858da7d805SAndreas Gohr 2868da7d805SAndreas Gohr $data = $res->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_ABS, 0); 2878da7d805SAndreas Gohr if (empty($data)) { 2888da7d805SAndreas Gohr return false; 2898da7d805SAndreas Gohr } 2908da7d805SAndreas Gohr return $data[0]; 291e7112ccbSAdrian Lang } 292fee3b689Sstretchyboy 293fee3b689Sstretchyboy /** 294fee3b689Sstretchyboy * fetch the next row as zero indexed array 2958da7d805SAndreas Gohr * 2968da7d805SAndreas Gohr * @param \PDOStatement $res 2978da7d805SAndreas Gohr * @return array|bool 298fee3b689Sstretchyboy */ 2998da7d805SAndreas Gohr public function res_fetch_array($res) 3008da7d805SAndreas Gohr { 3018da7d805SAndreas Gohr if (!$res) return false; 3028da7d805SAndreas Gohr 3038da7d805SAndreas Gohr return $res->fetch(PDO::FETCH_NUM); 30487fa2c18Sstretchyboy } 305fee3b689Sstretchyboy 306fee3b689Sstretchyboy /** 307fee3b689Sstretchyboy * fetch the next row as assocative array 3088da7d805SAndreas Gohr * 3098da7d805SAndreas Gohr * @param \PDOStatement $res 3108da7d805SAndreas Gohr * @return array|bool 311fee3b689Sstretchyboy */ 3128da7d805SAndreas Gohr public function res_fetch_assoc($res) 3138da7d805SAndreas Gohr { 3148da7d805SAndreas Gohr if (!$res) return false; 3158da7d805SAndreas Gohr 3168da7d805SAndreas Gohr return $res->fetch(PDO::FETCH_ASSOC); 317fee3b689Sstretchyboy } 318fee3b689Sstretchyboy 319fee3b689Sstretchyboy /** 32078977d74SKlap-in * Count the number of records in result 3213157674bSAndreas Gohr * 322db58e525SKlap-in * This function is really inperformant in PDO and should be avoided! 3238da7d805SAndreas Gohr * 3248da7d805SAndreas Gohr * @param \PDOStatement $res 3258da7d805SAndreas Gohr * @return int 326fee3b689Sstretchyboy */ 3278da7d805SAndreas Gohr public function res2count($res) 3288da7d805SAndreas Gohr { 3298da7d805SAndreas Gohr if (!$res) return 0; 3308da7d805SAndreas Gohr 3318da7d805SAndreas Gohr // this is a bullshit workaround for having res2arr and res2count work on one result 3328da7d805SAndreas Gohr if (!$this->data) { 3338da7d805SAndreas Gohr $this->data = $this->res2arr($res); 3348da7d805SAndreas Gohr } 3358da7d805SAndreas Gohr 3368da7d805SAndreas Gohr return count($this->data); 337fee3b689Sstretchyboy } 33824a03f6cSstretchyboy 33924a03f6cSstretchyboy /** 34024a03f6cSstretchyboy * Count the number of records changed last time 3418da7d805SAndreas Gohr * 3428da7d805SAndreas Gohr * @param \PDOStatement $res 3438da7d805SAndreas Gohr * @return int 34424a03f6cSstretchyboy */ 3458da7d805SAndreas Gohr public function countChanges($res) 3468da7d805SAndreas Gohr { 3478da7d805SAndreas Gohr if (!$res) return 0; 3488da7d805SAndreas Gohr 3498da7d805SAndreas Gohr return $res->rowCount(); 350a1e6784eSAndreas Gohr } 351a1e6784eSAndreas Gohr 3528da7d805SAndreas Gohr // endregion 3538da7d805SAndreas Gohr 3548da7d805SAndreas Gohr // region quoting/escaping functions 3558da7d805SAndreas Gohr 3568da7d805SAndreas Gohr /** 3578da7d805SAndreas Gohr * Join the given values and quote them for SQL insertion 3588da7d805SAndreas Gohr */ 3598da7d805SAndreas Gohr public function quote_and_join($vals, $sep = ',') 3608da7d805SAndreas Gohr { 36110cb854aSAndreas Gohr $vals = array_map([$this->adapter->getPdo(), 'quote'], $vals); 362*a7a40fb2SAnna Dabrowska return implode($sep, $vals); 3638da7d805SAndreas Gohr } 3648da7d805SAndreas Gohr 3658da7d805SAndreas Gohr /** 3668da7d805SAndreas Gohr * Quotes a string, by escaping it and adding quotes 3678da7d805SAndreas Gohr */ 3688da7d805SAndreas Gohr public function quote_string($string) 3698da7d805SAndreas Gohr { 37010cb854aSAndreas Gohr return $this->adapter->getPdo()->quote($string); 3718da7d805SAndreas Gohr } 3728da7d805SAndreas Gohr 3738da7d805SAndreas Gohr /** 3748da7d805SAndreas Gohr * Similar to quote_string, but without the quotes, useful to construct LIKE patterns 3758da7d805SAndreas Gohr */ 3768da7d805SAndreas Gohr public function escape_string($str) 3778da7d805SAndreas Gohr { 37810cb854aSAndreas Gohr return trim($this->adapter->getPdo()->quote($str), "'"); 3798da7d805SAndreas Gohr } 3808da7d805SAndreas Gohr 3818da7d805SAndreas Gohr // endregion 3828da7d805SAndreas Gohr 3838da7d805SAndreas Gohr // region speciality functions 3848da7d805SAndreas Gohr 3858da7d805SAndreas Gohr /** 3868da7d805SAndreas Gohr * Split sql queries on semicolons, unless when semicolons are quoted 3878da7d805SAndreas Gohr * 3888da7d805SAndreas Gohr * Usually you don't need this. It's only really needed if you need individual results for 3898da7d805SAndreas Gohr * multiple queries. For example in the admin interface. 3908da7d805SAndreas Gohr * 3918da7d805SAndreas Gohr * @param string $sql 3928da7d805SAndreas Gohr * @return array sql queries 3938da7d805SAndreas Gohr * @deprecated 3948da7d805SAndreas Gohr */ 3958da7d805SAndreas Gohr public function SQLstring2array($sql) 3968da7d805SAndreas Gohr { 397d0a5ba7aSAndreas Gohr if (!DOKU_UNITTEST) { // for now we don't want to trigger the deprecation warning in the tests 3988da7d805SAndreas Gohr dbg_deprecated(Tools::class . '::SQLstring2array'); 399d0a5ba7aSAndreas Gohr } 4008da7d805SAndreas Gohr return Tools::SQLstring2array($sql); 4018da7d805SAndreas Gohr } 4028da7d805SAndreas Gohr 4038da7d805SAndreas Gohr /** 4048da7d805SAndreas Gohr * @deprecated needs to be fixed in stuct and structpublish 4058da7d805SAndreas Gohr */ 406*a7a40fb2SAnna Dabrowska public function doTransaction($sql, $sqlpreparing = true) 407*a7a40fb2SAnna Dabrowska { 4088da7d805SAndreas Gohr throw new \Exception( 4098da7d805SAndreas Gohr 'This method seems to never have done what it suggests. Please use the query() function instead.' 4108da7d805SAndreas Gohr ); 4118da7d805SAndreas Gohr } 4128da7d805SAndreas Gohr 4138da7d805SAndreas Gohr // endregion 414aa81d781SKlap-in} 415