18da7d805SAndreas Gohr<?php 28da7d805SAndreas Gohr 38da7d805SAndreas Gohr/** 48da7d805SAndreas Gohr * @noinspection SqlNoDataSourceInspection 58da7d805SAndreas Gohr * @noinspection SqlDialectInspection 68da7d805SAndreas Gohr * @noinspection PhpComposerExtensionStubsInspection 78da7d805SAndreas Gohr */ 88da7d805SAndreas Gohr 98da7d805SAndreas Gohrnamespace dokuwiki\plugin\sqlite; 108da7d805SAndreas Gohr 11b35b734aSSzymon Olewniczakuse dokuwiki\Extension\Event; 128da7d805SAndreas Gohr 138da7d805SAndreas Gohr/** 148da7d805SAndreas Gohr * Helpers to access a SQLite Database with automatic schema migration 158da7d805SAndreas Gohr */ 168da7d805SAndreas Gohrclass SQLiteDB 178da7d805SAndreas Gohr{ 188da7d805SAndreas Gohr const FILE_EXTENSION = '.sqlite3'; 198da7d805SAndreas Gohr 208da7d805SAndreas Gohr /** @var \PDO */ 218da7d805SAndreas Gohr protected $pdo; 228da7d805SAndreas Gohr 238da7d805SAndreas Gohr /** @var string */ 248da7d805SAndreas Gohr protected $schemadir; 258da7d805SAndreas Gohr 268da7d805SAndreas Gohr /** @var string */ 278da7d805SAndreas Gohr protected $dbname; 288da7d805SAndreas Gohr 293a56750bSAndreas Gohr /** @var \helper_plugin_sqlite */ 308da7d805SAndreas Gohr protected $helper; 318da7d805SAndreas Gohr 328da7d805SAndreas Gohr /** 338da7d805SAndreas Gohr * Constructor 348da7d805SAndreas Gohr * 358da7d805SAndreas Gohr * @param string $dbname Database name 368da7d805SAndreas Gohr * @param string $schemadir directory with schema migration files 378da7d805SAndreas Gohr * @param \helper_plugin_sqlite $sqlitehelper for backwards compatibility 388da7d805SAndreas Gohr * @throws \Exception 398da7d805SAndreas Gohr */ 408da7d805SAndreas Gohr public function __construct($dbname, $schemadir, $sqlitehelper = null) 418da7d805SAndreas Gohr { 428da7d805SAndreas Gohr if (!class_exists('pdo') || !in_array('sqlite', \PDO::getAvailableDrivers())) { 438da7d805SAndreas Gohr throw new \Exception('SQLite PDO driver not available'); 448da7d805SAndreas Gohr } 458da7d805SAndreas Gohr 468da7d805SAndreas Gohr // backwards compatibility, circular dependency 478da7d805SAndreas Gohr $this->helper = $sqlitehelper; 483a56750bSAndreas Gohr if (!$this->helper) { 493a56750bSAndreas Gohr $this->helper = new \helper_plugin_sqlite(); 503a56750bSAndreas Gohr } 513a56750bSAndreas Gohr $this->helper->setAdapter($this); 528da7d805SAndreas Gohr 538da7d805SAndreas Gohr $this->schemadir = $schemadir; 548da7d805SAndreas Gohr $this->dbname = $dbname; 558da7d805SAndreas Gohr $file = $this->getDbFile(); 568da7d805SAndreas Gohr 578da7d805SAndreas Gohr $this->pdo = new \PDO( 588da7d805SAndreas Gohr 'sqlite:' . $file, 598da7d805SAndreas Gohr null, 608da7d805SAndreas Gohr null, 618da7d805SAndreas Gohr [ 628da7d805SAndreas Gohr \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION 638da7d805SAndreas Gohr ] 648da7d805SAndreas Gohr ); 658da7d805SAndreas Gohr 668da7d805SAndreas Gohr if ($schemadir !== '') { 678da7d805SAndreas Gohr // schema dir is empty, when accessing the DB from Admin interface instead of plugin context 688da7d805SAndreas Gohr $this->applyMigrations(); 698da7d805SAndreas Gohr } 708da7d805SAndreas Gohr Functions::register($this->pdo); 718da7d805SAndreas Gohr } 728da7d805SAndreas Gohr 73aae177f9SAndreas Gohr /** 74aae177f9SAndreas Gohr * Do not serialize the DB connection 75aae177f9SAndreas Gohr * 76aae177f9SAndreas Gohr * @return array 77aae177f9SAndreas Gohr */ 78c9d29defSAndreas Gohr public function __sleep() 79c9d29defSAndreas Gohr { 80aae177f9SAndreas Gohr $this->pdo = null; 81aae177f9SAndreas Gohr return array_keys(get_object_vars($this)); 82aae177f9SAndreas Gohr } 83aae177f9SAndreas Gohr 84aae177f9SAndreas Gohr /** 85aae177f9SAndreas Gohr * On deserialization, reinit database connection 86aae177f9SAndreas Gohr */ 87c9d29defSAndreas Gohr public function __wakeup() 88c9d29defSAndreas Gohr { 89aae177f9SAndreas Gohr $this->__construct($this->dbname, $this->schemadir, $this->helper); 90aae177f9SAndreas Gohr } 918da7d805SAndreas Gohr 928da7d805SAndreas Gohr // region public API 938da7d805SAndreas Gohr 948da7d805SAndreas Gohr /** 958da7d805SAndreas Gohr * Direct access to the PDO object 968da7d805SAndreas Gohr * @return \PDO 978da7d805SAndreas Gohr */ 98*33e488b3SAndreas Gohr public function getPdo() 99*33e488b3SAndreas Gohr { 100e22957e9SSzymon Olewniczak return $this->pdo; 101801b921eSSzymon Olewniczak } 102801b921eSSzymon Olewniczak 103801b921eSSzymon Olewniczak /** 1048da7d805SAndreas Gohr * Execute a statement and return it 1058da7d805SAndreas Gohr * 1068da7d805SAndreas Gohr * @param string $sql 1078da7d805SAndreas Gohr * @param array $parameters 1088da7d805SAndreas Gohr * @return \PDOStatement Be sure to close the cursor yourself 1098da7d805SAndreas Gohr * @throws \PDOException 1108da7d805SAndreas Gohr */ 1118da7d805SAndreas Gohr public function query($sql, $parameters = []) 1128da7d805SAndreas Gohr { 11303f14a77SAndreas Gohr // Statement preparation sometime throws ValueErrors instead of PDOExceptions, we streamline here 11403f14a77SAndreas Gohr try { 1158da7d805SAndreas Gohr $stmt = $this->pdo->prepare($sql); 11603f14a77SAndreas Gohr } catch (\Throwable $e) { 11703f14a77SAndreas Gohr throw new \PDOException($e->getMessage(), (int)$e->getCode(), $e); 11803f14a77SAndreas Gohr } 119b35b734aSSzymon Olewniczak $eventData = [ 120b8ae4891SSzymon Olewniczak 'sqlitedb' => $this, 121b35b734aSSzymon Olewniczak 'sql' => &$sql, 122b35b734aSSzymon Olewniczak 'parameters' => &$parameters, 123b35b734aSSzymon Olewniczak 'stmt' => $stmt 124b35b734aSSzymon Olewniczak ]; 125b35b734aSSzymon Olewniczak $event = new Event('PLUGIN_SQLITE_QUERY_EXECUTE', $eventData); 126b35b734aSSzymon Olewniczak if ($event->advise_before()) { 1278da7d805SAndreas Gohr $stmt->execute($parameters); 128b35b734aSSzymon Olewniczak } 129b35b734aSSzymon Olewniczak $event->advise_after(); 1308da7d805SAndreas Gohr return $stmt; 1318da7d805SAndreas Gohr } 1328da7d805SAndreas Gohr 1338da7d805SAndreas Gohr /** 1348da7d805SAndreas Gohr * Execute a statement and return metadata 1358da7d805SAndreas Gohr * 1368da7d805SAndreas Gohr * Returns the last insert ID on INSERTs or the number of affected rows 1378da7d805SAndreas Gohr * 1388da7d805SAndreas Gohr * @param string $sql 1398da7d805SAndreas Gohr * @param array $parameters 1408da7d805SAndreas Gohr * @return int 1418da7d805SAndreas Gohr * @throws \PDOException 1428da7d805SAndreas Gohr */ 1438da7d805SAndreas Gohr public function exec($sql, $parameters = []) 1448da7d805SAndreas Gohr { 145b35b734aSSzymon Olewniczak $stmt = $this->query($sql, $parameters); 1468da7d805SAndreas Gohr 1478da7d805SAndreas Gohr $count = $stmt->rowCount(); 1488da7d805SAndreas Gohr $stmt->closeCursor(); 1498da7d805SAndreas Gohr if ($count && preg_match('/^INSERT /i', $sql)) { 1508da7d805SAndreas Gohr return $this->queryValue('SELECT last_insert_rowid()'); 1518da7d805SAndreas Gohr } 1528da7d805SAndreas Gohr 1538da7d805SAndreas Gohr return $count; 1548da7d805SAndreas Gohr } 1558da7d805SAndreas Gohr 1568da7d805SAndreas Gohr /** 1578da7d805SAndreas Gohr * Simple query abstraction 1588da7d805SAndreas Gohr * 1598da7d805SAndreas Gohr * Returns all data 1608da7d805SAndreas Gohr * 1618da7d805SAndreas Gohr * @param string $sql 1628da7d805SAndreas Gohr * @param array $params 1638da7d805SAndreas Gohr * @return array 1648da7d805SAndreas Gohr * @throws \PDOException 1658da7d805SAndreas Gohr */ 1668da7d805SAndreas Gohr public function queryAll($sql, $params = []) 1678da7d805SAndreas Gohr { 1688da7d805SAndreas Gohr $stmt = $this->query($sql, $params); 1698da7d805SAndreas Gohr $data = $stmt->fetchAll(\PDO::FETCH_ASSOC); 1708da7d805SAndreas Gohr $stmt->closeCursor(); 1718da7d805SAndreas Gohr return $data; 1728da7d805SAndreas Gohr } 1738da7d805SAndreas Gohr 1748da7d805SAndreas Gohr /** 1758da7d805SAndreas Gohr * Query one single row 1768da7d805SAndreas Gohr * 1778da7d805SAndreas Gohr * @param string $sql 1788da7d805SAndreas Gohr * @param array $params 1798da7d805SAndreas Gohr * @return array|null 1808da7d805SAndreas Gohr * @throws \PDOException 1818da7d805SAndreas Gohr */ 1828da7d805SAndreas Gohr public function queryRecord($sql, $params = []) 1838da7d805SAndreas Gohr { 1848da7d805SAndreas Gohr $stmt = $this->query($sql, $params); 185a7a36cdbSAndreas Gohr $row = $stmt->fetch(\PDO::FETCH_ASSOC); 1868da7d805SAndreas Gohr $stmt->closeCursor(); 187a7a36cdbSAndreas Gohr if (is_array($row) && count($row)) { 188a7a36cdbSAndreas Gohr return $row; 189a7a36cdbSAndreas Gohr } 1908da7d805SAndreas Gohr return null; 1918da7d805SAndreas Gohr } 1928da7d805SAndreas Gohr 1938da7d805SAndreas Gohr /** 1948da7d805SAndreas Gohr * Insert or replace the given data into the table 1958da7d805SAndreas Gohr * 1968da7d805SAndreas Gohr * @param string $table 1978da7d805SAndreas Gohr * @param array $data 1988da7d805SAndreas Gohr * @param bool $replace Conflict resolution, replace or ignore 199a7a36cdbSAndreas Gohr * @return array|null Either the inserted row or null if nothing was inserted 2008da7d805SAndreas Gohr * @throws \PDOException 2018da7d805SAndreas Gohr */ 2028da7d805SAndreas Gohr public function saveRecord($table, $data, $replace = true) 2038da7d805SAndreas Gohr { 2048da7d805SAndreas Gohr $columns = array_map(function ($column) { 2058da7d805SAndreas Gohr return '"' . $column . '"'; 2068da7d805SAndreas Gohr }, array_keys($data)); 2078da7d805SAndreas Gohr $values = array_values($data); 2088da7d805SAndreas Gohr $placeholders = array_pad([], count($columns), '?'); 2098da7d805SAndreas Gohr 2108da7d805SAndreas Gohr if ($replace) { 2118da7d805SAndreas Gohr $command = 'REPLACE'; 2128da7d805SAndreas Gohr } else { 2138da7d805SAndreas Gohr $command = 'INSERT OR IGNORE'; 2148da7d805SAndreas Gohr } 2158da7d805SAndreas Gohr 2168da7d805SAndreas Gohr /** @noinspection SqlResolve */ 217a7a36cdbSAndreas Gohr $sql = $command . ' INTO "' . $table . '" (' . join(',', $columns) . ') VALUES (' . join(',', 218a7a36cdbSAndreas Gohr $placeholders) . ')'; 219b35b734aSSzymon Olewniczak $stm = $this->query($sql, $values); 220a7a36cdbSAndreas Gohr $success = $stm->rowCount(); 2218da7d805SAndreas Gohr $stm->closeCursor(); 222a7a36cdbSAndreas Gohr 223a7a36cdbSAndreas Gohr if ($success) { 224a7a36cdbSAndreas Gohr $sql = 'SELECT * FROM "' . $table . '" WHERE rowid = last_insert_rowid()'; 225a7a36cdbSAndreas Gohr return $this->queryRecord($sql); 226a7a36cdbSAndreas Gohr } 227a7a36cdbSAndreas Gohr return null; 2288da7d805SAndreas Gohr } 2298da7d805SAndreas Gohr 2308da7d805SAndreas Gohr /** 2318da7d805SAndreas Gohr * Execute a query that returns a single value 2328da7d805SAndreas Gohr * 2338da7d805SAndreas Gohr * @param string $sql 2348da7d805SAndreas Gohr * @param array $params 2358da7d805SAndreas Gohr * @return mixed|null 2368da7d805SAndreas Gohr * @throws \PDOException 2378da7d805SAndreas Gohr */ 2388da7d805SAndreas Gohr public function queryValue($sql, $params = []) 2398da7d805SAndreas Gohr { 2408da7d805SAndreas Gohr $result = $this->queryAll($sql, $params); 241c9d29defSAndreas Gohr if (is_array($result) && count($result)) { 242c9d29defSAndreas Gohr return array_values($result[0])[0]; 243c9d29defSAndreas Gohr } 2448da7d805SAndreas Gohr return null; 2458da7d805SAndreas Gohr } 2468da7d805SAndreas Gohr 247*33e488b3SAndreas Gohr /** 248*33e488b3SAndreas Gohr * Execute a query that returns a list of key-value pairs 249*33e488b3SAndreas Gohr * 250*33e488b3SAndreas Gohr * The first column is used as key, the second as value. Any additional colums are ignored. 251*33e488b3SAndreas Gohr * 252*33e488b3SAndreas Gohr * @param string $sql 253*33e488b3SAndreas Gohr * @param array $params 254*33e488b3SAndreas Gohr * @return array 255*33e488b3SAndreas Gohr */ 256*33e488b3SAndreas Gohr public function queryKeyValueList($sql, $params = []) 257*33e488b3SAndreas Gohr { 258*33e488b3SAndreas Gohr $result = $this->queryAll($sql, $params); 259*33e488b3SAndreas Gohr if (!$result) return []; 260*33e488b3SAndreas Gohr if (count(array_keys($result[0])) != 2) { 261*33e488b3SAndreas Gohr throw new \RuntimeException('queryKeyValueList expects a query that returns exactly two columns'); 262*33e488b3SAndreas Gohr } 263*33e488b3SAndreas Gohr [$key, $val] = array_keys($result[0]); 264*33e488b3SAndreas Gohr 265*33e488b3SAndreas Gohr return array_combine( 266*33e488b3SAndreas Gohr array_column($result, $key), 267*33e488b3SAndreas Gohr array_column($result, $val) 268*33e488b3SAndreas Gohr ); 269*33e488b3SAndreas Gohr } 270*33e488b3SAndreas Gohr 2718da7d805SAndreas Gohr // endregion 2728da7d805SAndreas Gohr 2738da7d805SAndreas Gohr // region meta handling 2748da7d805SAndreas Gohr 2758da7d805SAndreas Gohr /** 2768da7d805SAndreas Gohr * Get a config value from the opt table 2778da7d805SAndreas Gohr * 2788da7d805SAndreas Gohr * @param string $opt Config name 2798da7d805SAndreas Gohr * @param mixed $default What to return if the value isn't set 2808da7d805SAndreas Gohr * @return mixed 2818da7d805SAndreas Gohr * @throws \PDOException 2828da7d805SAndreas Gohr */ 2838da7d805SAndreas Gohr public function getOpt($opt, $default = null) 2848da7d805SAndreas Gohr { 2858da7d805SAndreas Gohr $value = $this->queryValue("SELECT val FROM opts WHERE opt = ?", [$opt]); 286c9d29defSAndreas Gohr if ($value === null) { 287c9d29defSAndreas Gohr return $default; 288c9d29defSAndreas Gohr } 2898da7d805SAndreas Gohr return $value; 2908da7d805SAndreas Gohr } 2918da7d805SAndreas Gohr 2928da7d805SAndreas Gohr /** 2938da7d805SAndreas Gohr * Set a config value in the opt table 2948da7d805SAndreas Gohr * 2958da7d805SAndreas Gohr * @param $opt 2968da7d805SAndreas Gohr * @param $value 2978da7d805SAndreas Gohr * @throws \PDOException 2988da7d805SAndreas Gohr */ 2998da7d805SAndreas Gohr public function setOpt($opt, $value) 3008da7d805SAndreas Gohr { 3018da7d805SAndreas Gohr $this->exec('REPLACE INTO opts (opt,val) VALUES (?,?)', [$opt, $value]); 3028da7d805SAndreas Gohr } 3038da7d805SAndreas Gohr 3048da7d805SAndreas Gohr /** 3058da7d805SAndreas Gohr * @return string 3068da7d805SAndreas Gohr */ 3078da7d805SAndreas Gohr public function getDbName() 3088da7d805SAndreas Gohr { 3098da7d805SAndreas Gohr return $this->dbname; 3108da7d805SAndreas Gohr } 3118da7d805SAndreas Gohr 3128da7d805SAndreas Gohr /** 3138da7d805SAndreas Gohr * @return string 3148da7d805SAndreas Gohr */ 3158da7d805SAndreas Gohr public function getDbFile() 3168da7d805SAndreas Gohr { 3178da7d805SAndreas Gohr global $conf; 3188da7d805SAndreas Gohr return $conf['metadir'] . '/' . $this->dbname . self::FILE_EXTENSION; 3198da7d805SAndreas Gohr } 3208da7d805SAndreas Gohr 3218da7d805SAndreas Gohr /** 3228da7d805SAndreas Gohr * Create a dump of the database and its contents 3238da7d805SAndreas Gohr * 3248da7d805SAndreas Gohr * @return string 3258da7d805SAndreas Gohr * @throws \Exception 3268da7d805SAndreas Gohr */ 3278da7d805SAndreas Gohr public function dumpToFile($filename) 3288da7d805SAndreas Gohr { 3298da7d805SAndreas Gohr $fp = fopen($filename, 'w'); 3308da7d805SAndreas Gohr if (!$fp) { 3318da7d805SAndreas Gohr throw new \Exception('Could not open file ' . $filename . ' for writing'); 3328da7d805SAndreas Gohr } 3338da7d805SAndreas Gohr 3347ddaad11SAndreas Gohr $tables = $this->queryAll("SELECT name,sql FROM sqlite_master WHERE type='table'"); 3357ddaad11SAndreas Gohr $indexes = $this->queryAll("SELECT name,sql FROM sqlite_master WHERE type='index'"); 3368da7d805SAndreas Gohr 3378da7d805SAndreas Gohr foreach ($tables as $table) { 3387ddaad11SAndreas Gohr fwrite($fp, "DROP TABLE IF EXISTS '{$table['name']}';\n"); 3397ddaad11SAndreas Gohr } 3408da7d805SAndreas Gohr 3417ddaad11SAndreas Gohr foreach ($tables as $table) { 3427ddaad11SAndreas Gohr fwrite($fp, $table['sql'] . ";\n"); 3437ddaad11SAndreas Gohr } 3447ddaad11SAndreas Gohr 3457ddaad11SAndreas Gohr foreach ($tables as $table) { 3468da7d805SAndreas Gohr $sql = "SELECT * FROM " . $table['name']; 3478da7d805SAndreas Gohr $res = $this->query($sql); 3488da7d805SAndreas Gohr while ($row = $res->fetch(\PDO::FETCH_ASSOC)) { 3497ddaad11SAndreas Gohr $values = join(',', array_map(function ($value) { 3507ddaad11SAndreas Gohr if ($value === null) return 'NULL'; 3517ddaad11SAndreas Gohr return $this->pdo->quote($value); 3527ddaad11SAndreas Gohr }, $row)); 3537ddaad11SAndreas Gohr fwrite($fp, "INSERT INTO '{$table['name']}' VALUES ({$values});\n"); 3548da7d805SAndreas Gohr } 3558da7d805SAndreas Gohr $res->closeCursor(); 3568da7d805SAndreas Gohr } 3578da7d805SAndreas Gohr 3588da7d805SAndreas Gohr foreach ($indexes as $index) { 3598da7d805SAndreas Gohr fwrite($fp, $index['sql'] . ";\n"); 3608da7d805SAndreas Gohr } 3618da7d805SAndreas Gohr fclose($fp); 3628da7d805SAndreas Gohr return $filename; 3638da7d805SAndreas Gohr } 3648da7d805SAndreas Gohr 3658da7d805SAndreas Gohr // endregion 3668da7d805SAndreas Gohr 3678da7d805SAndreas Gohr // region migration handling 3688da7d805SAndreas Gohr 3698da7d805SAndreas Gohr /** 3708da7d805SAndreas Gohr * Apply all pending migrations 3718da7d805SAndreas Gohr * 3728da7d805SAndreas Gohr * Each migration is executed in a transaction which is rolled back on failure 3738da7d805SAndreas Gohr * Migrations can be files in the schema directory or event handlers 3748da7d805SAndreas Gohr * 3758da7d805SAndreas Gohr * @throws \Exception 3768da7d805SAndreas Gohr */ 3778da7d805SAndreas Gohr protected function applyMigrations() 3788da7d805SAndreas Gohr { 3798da7d805SAndreas Gohr $currentVersion = $this->currentDbVersion(); 3808da7d805SAndreas Gohr $latestVersion = $this->latestDbVersion(); 3818da7d805SAndreas Gohr 3828da7d805SAndreas Gohr for ($newVersion = $currentVersion + 1; $newVersion <= $latestVersion; $newVersion++) { 3838da7d805SAndreas Gohr $data = [ 3848da7d805SAndreas Gohr 'dbname' => $this->dbname, 3858da7d805SAndreas Gohr 'from' => $currentVersion, 3868da7d805SAndreas Gohr 'to' => $newVersion, 3878da7d805SAndreas Gohr 'file' => $this->getMigrationFile($newVersion), 3888da7d805SAndreas Gohr 'sqlite' => $this->helper, 3898da7d805SAndreas Gohr 'adapter' => $this, 3908da7d805SAndreas Gohr ]; 3918da7d805SAndreas Gohr $event = new \Doku_Event('PLUGIN_SQLITE_DATABASE_UPGRADE', $data); 3928da7d805SAndreas Gohr 3938da7d805SAndreas Gohr $this->pdo->beginTransaction(); 3948da7d805SAndreas Gohr try { 3958da7d805SAndreas Gohr if ($event->advise_before()) { 3968da7d805SAndreas Gohr // standard migration file 3977ddaad11SAndreas Gohr $sql = Tools::SQLstring2array(file_get_contents($data['file'])); 3987ddaad11SAndreas Gohr foreach ($sql as $query) { 3997ddaad11SAndreas Gohr $this->pdo->exec($query); 4007ddaad11SAndreas Gohr } 401c9d29defSAndreas Gohr } else { 402c9d29defSAndreas Gohr if (!$event->result) { 4038da7d805SAndreas Gohr // advise before returned false, but the result was false 4048da7d805SAndreas Gohr throw new \PDOException('Plugin event did not signal success'); 4058da7d805SAndreas Gohr } 406c9d29defSAndreas Gohr } 4078da7d805SAndreas Gohr $this->setOpt('dbversion', $newVersion); 4088da7d805SAndreas Gohr $this->pdo->commit(); 4098da7d805SAndreas Gohr $event->advise_after(); 4108da7d805SAndreas Gohr } catch (\Exception $e) { 4118da7d805SAndreas Gohr // something went wrong, rollback 4128da7d805SAndreas Gohr $this->pdo->rollBack(); 4138da7d805SAndreas Gohr throw $e; 4148da7d805SAndreas Gohr } 4158da7d805SAndreas Gohr } 4168da7d805SAndreas Gohr 4178da7d805SAndreas Gohr // vacuum the database to free up unused space 4188da7d805SAndreas Gohr $this->pdo->exec('VACUUM'); 4198da7d805SAndreas Gohr } 4208da7d805SAndreas Gohr 4218da7d805SAndreas Gohr /** 4228da7d805SAndreas Gohr * Read the current version from the opt table 4238da7d805SAndreas Gohr * 4248da7d805SAndreas Gohr * The opt table is created here if not found 4258da7d805SAndreas Gohr * 4268da7d805SAndreas Gohr * @return int 4278da7d805SAndreas Gohr * @throws \PDOException 4288da7d805SAndreas Gohr */ 4298da7d805SAndreas Gohr protected function currentDbVersion() 4308da7d805SAndreas Gohr { 4318da7d805SAndreas Gohr try { 4328da7d805SAndreas Gohr $version = $this->getOpt('dbversion', 0); 4338da7d805SAndreas Gohr return (int)$version; 4348da7d805SAndreas Gohr } catch (\PDOException $ignored) { 4358da7d805SAndreas Gohr // add the opt table - if this fails too, let the exception bubble up 4368da7d805SAndreas Gohr $sql = "CREATE TABLE IF NOT EXISTS opts (opt TEXT NOT NULL PRIMARY KEY, val NOT NULL DEFAULT '')"; 4378da7d805SAndreas Gohr $this->exec($sql); 4388da7d805SAndreas Gohr $this->setOpt('dbversion', 0); 4398da7d805SAndreas Gohr return 0; 4408da7d805SAndreas Gohr } 4418da7d805SAndreas Gohr } 4428da7d805SAndreas Gohr 4438da7d805SAndreas Gohr /** 4448da7d805SAndreas Gohr * Get the version this db should have 4458da7d805SAndreas Gohr * 4468da7d805SAndreas Gohr * @return int 4478da7d805SAndreas Gohr * @throws \PDOException 4488da7d805SAndreas Gohr */ 4498da7d805SAndreas Gohr protected function latestDbVersion() 4508da7d805SAndreas Gohr { 4518da7d805SAndreas Gohr if (!file_exists($this->schemadir . '/latest.version')) { 4528da7d805SAndreas Gohr throw new \PDOException('No latest.version in schema dir'); 4538da7d805SAndreas Gohr } 4548da7d805SAndreas Gohr return (int)trim(file_get_contents($this->schemadir . '/latest.version')); 4558da7d805SAndreas Gohr } 4568da7d805SAndreas Gohr 4578da7d805SAndreas Gohr /** 4588da7d805SAndreas Gohr * Get the migrartion file for the given version 4598da7d805SAndreas Gohr * 4608da7d805SAndreas Gohr * @param int $version 4618da7d805SAndreas Gohr * @return string 4628da7d805SAndreas Gohr */ 4638da7d805SAndreas Gohr protected function getMigrationFile($version) 4648da7d805SAndreas Gohr { 4658da7d805SAndreas Gohr return sprintf($this->schemadir . '/update%04d.sql', $version); 4668da7d805SAndreas Gohr } 4678da7d805SAndreas Gohr // endregion 4688da7d805SAndreas Gohr} 469