1c3437056SNickeau<?php /** @noinspection SpellCheckingInspection */ 2c3437056SNickeau 337748cd8SNickeau/** 437748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 537748cd8SNickeau * 637748cd8SNickeau * This source code is licensed under the GPL license found in the 737748cd8SNickeau * COPYING file in the root directory of this source tree. 837748cd8SNickeau * 937748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 1037748cd8SNickeau * @author ComboStrap <support@combostrap.com> 1137748cd8SNickeau * 1237748cd8SNickeau */ 1337748cd8SNickeau 1437748cd8SNickeaunamespace ComboStrap; 1537748cd8SNickeau 1637748cd8SNickeau 17*d899a2a6Sgerardnicouse dokuwiki\plugin\sqlite\SQLiteDB; 1837748cd8SNickeauuse helper_plugin_sqlite; 1937748cd8SNickeau 2037748cd8SNickeauclass Sqlite 2137748cd8SNickeau{ 2237748cd8SNickeau 23c3437056SNickeau 24c3437056SNickeau /** 25c3437056SNickeau * Principal database 26c3437056SNickeau * (Backup) 27c3437056SNickeau */ 28c3437056SNickeau private const MAIN_DATABASE_NAME = "combo"; 29c3437056SNickeau /** 30c3437056SNickeau * Backend Databse 31c3437056SNickeau * (Log, Pub/Sub,...) 32c3437056SNickeau */ 33c3437056SNickeau private const SECONDARY_DB = "combo-secondary"; 34c3437056SNickeau 35c3437056SNickeau private static $sqliteVersion; 36c3437056SNickeau 3704fd306cSNickeau 3804fd306cSNickeau private helper_plugin_sqlite $sqlitePlugin; 3904fd306cSNickeau 40c3437056SNickeau /** 4104fd306cSNickeau * @var SqliteRequest the actual request. If not closed, it will be close. 4204fd306cSNickeau * Otherwise, it's not possible to delete the database file. See {@link self::deleteDatabasesFile()} 43c3437056SNickeau */ 4404fd306cSNickeau private SqliteRequest $actualRequest; 4504fd306cSNickeau 46c3437056SNickeau 47c3437056SNickeau /** 48c3437056SNickeau * Sqlite constructor. 49c3437056SNickeau * @var helper_plugin_sqlite $sqlitePlugin 50c3437056SNickeau */ 51c3437056SNickeau public function __construct(helper_plugin_sqlite $sqlitePlugin) 52c3437056SNickeau { 53c3437056SNickeau $this->sqlitePlugin = $sqlitePlugin; 54c3437056SNickeau } 55c3437056SNickeau 5637748cd8SNickeau 5737748cd8SNickeau /** 5837748cd8SNickeau * 59c3437056SNickeau * @return Sqlite $sqlite 6004fd306cSNickeau * @throws ExceptionSqliteNotAvailable 6137748cd8SNickeau */ 6204fd306cSNickeau public static function createOrGetSqlite($databaseName = self::MAIN_DATABASE_NAME): Sqlite 6337748cd8SNickeau { 64c3437056SNickeau 6504fd306cSNickeau $sqliteExecutionObjectIdentifier = Sqlite::class . "-$databaseName"; 6604fd306cSNickeau $executionContext = ExecutionContext::getActualOrCreateFromEnv(); 6704fd306cSNickeau 6804fd306cSNickeau try { 6904fd306cSNickeau /** 7004fd306cSNickeau * @var Sqlite $sqlite 7104fd306cSNickeau * 7204fd306cSNickeau * 7304fd306cSNickeau * sqlite is stored globally 7404fd306cSNickeau * because when we create a new instance, it will open the 7504fd306cSNickeau * sqlite file. 7604fd306cSNickeau * 7704fd306cSNickeau * In a {@link cli_plugin_combo} run, you will run in the error: 7804fd306cSNickeau * `` 7904fd306cSNickeau * failed to open stream: Too many open files 8004fd306cSNickeau * `` 8104fd306cSNickeau * As there is by default a limit of 1024 open files 8204fd306cSNickeau * which means that if there is more than 1024 pages 8304fd306cSNickeau * that you replicate using a new sqlite instance each time, 8404fd306cSNickeau * you fail. 8504fd306cSNickeau * 8604fd306cSNickeau */ 8704fd306cSNickeau $sqlite = $executionContext->getRuntimeObject($sqliteExecutionObjectIdentifier); 8804fd306cSNickeau } catch (ExceptionNotFound $e) { 8904fd306cSNickeau $sqlite = null; 9004fd306cSNickeau } 9104fd306cSNickeau 92c3437056SNickeau if ($sqlite !== null) { 93c3437056SNickeau $res = $sqlite->doWeNeedToCreateNewInstance(); 94c3437056SNickeau if ($res === false) { 95c3437056SNickeau return $sqlite; 9637748cd8SNickeau } 9737748cd8SNickeau } 98c3437056SNickeau 9937748cd8SNickeau /** 10037748cd8SNickeau * Init 101c3437056SNickeau * @var helper_plugin_sqlite $sqlitePlugin 10237748cd8SNickeau */ 103c3437056SNickeau $sqlitePlugin = plugin_load('helper', 'sqlite'); 104c3437056SNickeau /** 105c3437056SNickeau * Not enabled / loaded 106c3437056SNickeau */ 107c3437056SNickeau if ($sqlitePlugin === null) { 108c3437056SNickeau 10937748cd8SNickeau $sqliteMandatoryMessage = "The Sqlite Plugin is mandatory. Some functionalities of the ComboStrap Plugin may not work."; 11004fd306cSNickeau throw new ExceptionSqliteNotAvailable($sqliteMandatoryMessage); 11137748cd8SNickeau } 112c3437056SNickeau 113c3437056SNickeau $adapter = $sqlitePlugin->getAdapter(); 11437748cd8SNickeau if ($adapter == null) { 11537748cd8SNickeau self::sendMessageAsNotAvailable(); 11637748cd8SNickeau } 11737748cd8SNickeau 11837748cd8SNickeau $adapter->setUseNativeAlter(true); 11937748cd8SNickeau 12004fd306cSNickeau list($databaseName, $databaseDefinitionDir) = self::getDatabaseNameAndDefinitionDirectory($databaseName); 12104fd306cSNickeau $init = $sqlitePlugin->init($databaseName, $databaseDefinitionDir); 12237748cd8SNickeau if (!$init) { 12337748cd8SNickeau $message = "Unable to initialize Sqlite"; 12404fd306cSNickeau throw new ExceptionSqliteNotAvailable($message); 125c3437056SNickeau } 12637748cd8SNickeau // regexp implementation 12737748cd8SNickeau // https://stackoverflow.com/questions/5071601/how-do-i-use-regex-in-a-sqlite-query/18484596#18484596 128c3437056SNickeau $adapter = $sqlitePlugin->getAdapter(); 129*d899a2a6Sgerardnico $regexFunctioName = 'regexp'; 130*d899a2a6Sgerardnico $regexpClosure = function ($pattern, $data, $delimiter = '~', $modifiers = 'isuS') { 13137748cd8SNickeau if (isset($pattern, $data) === true) { 13237748cd8SNickeau return (preg_match(sprintf('%1$s%2$s%1$s%3$s', $delimiter, $pattern, $modifiers), $data) > 0); 13337748cd8SNickeau } 13437748cd8SNickeau return null; 135*d899a2a6Sgerardnico }; 136*d899a2a6Sgerardnico $regexArgCount = 4; 137*d899a2a6Sgerardnico if (!self::isJuneVersion($adapter)) { 138*d899a2a6Sgerardnico /** @noinspection PhpUndefinedMethodInspection */ 139*d899a2a6Sgerardnico $adapter->create_function($regexFunctioName, $regexpClosure, $regexArgCount); 140*d899a2a6Sgerardnico } else { 141*d899a2a6Sgerardnico $adapter->getPdo()->sqliteCreateFunction($regexFunctioName, $regexpClosure, $regexArgCount); 142*d899a2a6Sgerardnico } 143c3437056SNickeau 144c3437056SNickeau $sqlite = new Sqlite($sqlitePlugin); 14504fd306cSNickeau $executionContext->setRuntimeObject($sqliteExecutionObjectIdentifier, $sqlite); 146c3437056SNickeau return $sqlite; 147c3437056SNickeau 14837748cd8SNickeau } 149c3437056SNickeau 15004fd306cSNickeau /** 15104fd306cSNickeau * @throws ExceptionSqliteNotAvailable 15204fd306cSNickeau */ 153c3437056SNickeau public static function createOrGetBackendSqlite(): ?Sqlite 154c3437056SNickeau { 155c3437056SNickeau return self::createOrGetSqlite(self::SECONDARY_DB); 15637748cd8SNickeau } 157c3437056SNickeau 1582becc3acSgerardnico 159c3437056SNickeau public static function createSelectFromTableAndColumns(string $tableName, array $columns = null): string 160c3437056SNickeau { 161c3437056SNickeau if ($columns === null) { 162c3437056SNickeau $columnStatement = "*"; 163c3437056SNickeau } else { 164c3437056SNickeau $columnsStatement = []; 165c3437056SNickeau foreach ($columns as $columnName) { 166c3437056SNickeau $columnsStatement[] = "$columnName as \"$columnName\""; 167c3437056SNickeau } 168c3437056SNickeau $columnStatement = implode(", ", $columnsStatement); 169c3437056SNickeau } 170031d4b49Sgerardnico /** 1712becc3acSgerardnico * TODO: We had added the `rowid` on all query 1722becc3acSgerardnico * but the underlining code was not supporting it, 1732becc3acSgerardnico * adding it in the next release to be able to locate the row 174031d4b49Sgerardnico */ 1752becc3acSgerardnico return "select $columnStatement from $tableName"; 17637748cd8SNickeau 17737748cd8SNickeau } 17837748cd8SNickeau 17937748cd8SNickeau /** 18004fd306cSNickeau * Used in test to delete the database file 18104fd306cSNickeau * @return void 18204fd306cSNickeau * @throws ExceptionFileSystem - if we can delete the databases 18304fd306cSNickeau */ 18404fd306cSNickeau public static function deleteDatabasesFile() 18504fd306cSNickeau { 18604fd306cSNickeau /** 18704fd306cSNickeau * The plugin does not give you the option to 18804fd306cSNickeau * where to create the database file 18904fd306cSNickeau * See {@link \helper_plugin_sqlite_adapter::initdb()} 19004fd306cSNickeau * $this->dbfile = $conf['metadir'].'/'.$dbname.$this->fileextension; 19104fd306cSNickeau * 19204fd306cSNickeau * If error on delete, see {@link self::close()} 19304fd306cSNickeau */ 19404fd306cSNickeau $metadatDirectory = ExecutionContext::getActualOrCreateFromEnv() 19504fd306cSNickeau ->getConfig() 19604fd306cSNickeau ->getMetaDataDirectory(); 19704fd306cSNickeau $fileChildren = FileSystems::getChildrenLeaf($metadatDirectory); 19804fd306cSNickeau foreach ($fileChildren as $child) { 19904fd306cSNickeau try { 20004fd306cSNickeau $extension = $child->getExtension(); 20104fd306cSNickeau } catch (ExceptionNotFound $e) { 20204fd306cSNickeau // ok no extension 20304fd306cSNickeau continue; 20404fd306cSNickeau } 20504fd306cSNickeau if (in_array($extension, ["sqlite", "sqlite3"])) { 20604fd306cSNickeau FileSystems::delete($child); 20704fd306cSNickeau } 20804fd306cSNickeau 20904fd306cSNickeau } 21004fd306cSNickeau } 21104fd306cSNickeau 21204fd306cSNickeau private static function getDatabaseNameAndDefinitionDirectory($databaseName): array 21304fd306cSNickeau { 21404fd306cSNickeau global $conf; 21504fd306cSNickeau 21604fd306cSNickeau if ($databaseName === self::MAIN_DATABASE_NAME) { 21704fd306cSNickeau $oldDbName = '404manager'; 21804fd306cSNickeau $oldDbFile = $conf['metadir'] . "/{$oldDbName}.sqlite"; 21904fd306cSNickeau $oldDbFileSqlite3 = $conf['metadir'] . "/{$oldDbName}.sqlite3"; 22004fd306cSNickeau if (file_exists($oldDbFile) || file_exists($oldDbFileSqlite3)) { 22104fd306cSNickeau $databaseName = $oldDbName; 22204fd306cSNickeau } 22304fd306cSNickeau } 22404fd306cSNickeau 22504fd306cSNickeau $databaseDir = DOKU_PLUGIN . PluginUtility::PLUGIN_BASE_NAME . "/db/$databaseName"; 22604fd306cSNickeau return [$databaseName, $databaseDir]; 22704fd306cSNickeau 22804fd306cSNickeau } 22904fd306cSNickeau 23004fd306cSNickeau /** 23137748cd8SNickeau * Print debug info to the console in order to resolve 23237748cd8SNickeau * RuntimeException: HY000 8 attempt to write a readonly database 23337748cd8SNickeau * https://phpunit.readthedocs.io/en/latest/writing-tests-for-phpunit.html#error-output 23437748cd8SNickeau */ 235c3437056SNickeau public function printDbInfoAtConsole() 23637748cd8SNickeau { 237c3437056SNickeau $dbFile = $this->sqlitePlugin->getAdapter()->getDbFile(); 23837748cd8SNickeau fwrite(STDERR, "Stderr DbFile: " . $dbFile . "\n"); 23937748cd8SNickeau if (file_exists($dbFile)) { 24037748cd8SNickeau fwrite(STDERR, "File does exists\n"); 24137748cd8SNickeau fwrite(STDERR, "Permission " . substr(sprintf('%o', fileperms($dbFile)), -4) . "\n"); 24237748cd8SNickeau } else { 24337748cd8SNickeau fwrite(STDERR, "File does not exist\n"); 24437748cd8SNickeau } 24537748cd8SNickeau 24637748cd8SNickeau global $conf; 24737748cd8SNickeau $metadir = $conf['metadir']; 24837748cd8SNickeau fwrite(STDERR, "MetaDir: " . $metadir . "\n"); 24937748cd8SNickeau $subdir = strpos($dbFile, $metadir) === 0; 25037748cd8SNickeau if ($subdir) { 25137748cd8SNickeau fwrite(STDERR, "Meta is a subdirectory of the db \n"); 25237748cd8SNickeau } else { 25337748cd8SNickeau fwrite(STDERR, "Meta is a not subdirectory of the db \n"); 25437748cd8SNickeau } 25537748cd8SNickeau 25637748cd8SNickeau } 25737748cd8SNickeau 25837748cd8SNickeau /** 25937748cd8SNickeau * Json support 26037748cd8SNickeau */ 261c3437056SNickeau public function supportJson(): bool 26237748cd8SNickeau { 26337748cd8SNickeau 26437748cd8SNickeau 265c3437056SNickeau $res = $this->sqlitePlugin->query("PRAGMA compile_options"); 26637748cd8SNickeau $isJsonEnabled = false; 267c3437056SNickeau foreach ($this->sqlitePlugin->res2arr($res) as $row) { 26837748cd8SNickeau if ($row["compile_option"] === "ENABLE_JSON1") { 26937748cd8SNickeau $isJsonEnabled = true; 27037748cd8SNickeau break; 27137748cd8SNickeau } 27237748cd8SNickeau }; 273c3437056SNickeau $this->sqlitePlugin->res_close($res); 27437748cd8SNickeau return $isJsonEnabled; 27537748cd8SNickeau } 27637748cd8SNickeau 27737748cd8SNickeau 27804fd306cSNickeau /** 27904fd306cSNickeau * @throws ExceptionSqliteNotAvailable 28004fd306cSNickeau */ 281c3437056SNickeau public 282c3437056SNickeau static function sendMessageAsNotAvailable(): void 28337748cd8SNickeau { 28437748cd8SNickeau $sqliteMandatoryMessage = "The Sqlite Php Extension is mandatory. It seems that it's not available on this installation."; 28504fd306cSNickeau throw new ExceptionSqliteNotAvailable($sqliteMandatoryMessage); 28637748cd8SNickeau } 287c3437056SNickeau 288c3437056SNickeau /** 28904fd306cSNickeau * 29004fd306cSNickeau * Old check when there was no {@link ExecutionContext} 29104fd306cSNickeau * to reset the Sqlite variable 29204fd306cSNickeau * TODO: delete ? 293c3437056SNickeau * 294c3437056SNickeau * 295c3437056SNickeau */ 296c3437056SNickeau private function doWeNeedToCreateNewInstance(): bool 297c3437056SNickeau { 298c3437056SNickeau 299c3437056SNickeau global $conf; 300c3437056SNickeau $metaDir = $conf['metadir']; 301c3437056SNickeau 302c3437056SNickeau /** 303c3437056SNickeau * Adapter may be null 304c3437056SNickeau * when the SQLite & PDO SQLite 305c3437056SNickeau * are not installed 306c3437056SNickeau * ie: SQLite & PDO SQLite support missing 307c3437056SNickeau */ 308c3437056SNickeau $adapter = $this->sqlitePlugin->getAdapter(); 309c3437056SNickeau if ($adapter === null) { 310c3437056SNickeau return true; 311c3437056SNickeau } 312c3437056SNickeau 313c3437056SNickeau /** 314c3437056SNickeau * When the database is {@link \helper_plugin_sqlite_adapter::closedb()} 315c3437056SNickeau */ 316*d899a2a6Sgerardnico if (!self::isJuneVersion($adapter)) { 317*d899a2a6Sgerardnico /** @noinspection PhpUndefinedMethodInspection */ 318*d899a2a6Sgerardnico $db = $adapter->getDb(); 319*d899a2a6Sgerardnico } else { 320*d899a2a6Sgerardnico $db = $adapter->getPdo(); 321*d899a2a6Sgerardnico } 322*d899a2a6Sgerardnico if ($db === null) { 323c3437056SNickeau /** 324c3437056SNickeau * We may also open it again 325c3437056SNickeau * {@link \helper_plugin_sqlite_adapter::opendb()} 326c3437056SNickeau * for now, reinit 327c3437056SNickeau */ 328c3437056SNickeau return true; 329c3437056SNickeau } 330*d899a2a6Sgerardnico 331c3437056SNickeau /** 332c3437056SNickeau * In test, we are running in different context (ie different root 333c3437056SNickeau * directory for DokuWiki and therefore different $conf 334c3437056SNickeau * and therefore different metadir where sqlite is stored) 335c3437056SNickeau * Because a sql file may be deleted, we may get: 336c3437056SNickeau * ``` 337c3437056SNickeau * RuntimeException: HY000 8 attempt to write a readonly database: 338c3437056SNickeau * ``` 339c3437056SNickeau * To avoid this error, we check that we are still in the same metadir 340c3437056SNickeau * where the sqlite database is stored. If not, we create a new instance 341c3437056SNickeau */ 342c3437056SNickeau $dbFile = $adapter->getDbFile(); 343c3437056SNickeau if (!file_exists($dbFile)) { 344c3437056SNickeau $this->close(); 345c3437056SNickeau return true; 346c3437056SNickeau } 347c3437056SNickeau // the file is in the meta directory 348c3437056SNickeau if (strpos($dbFile, $metaDir) === 0) { 349c3437056SNickeau // we are still in a class run 350c3437056SNickeau return false; 351c3437056SNickeau } 352c3437056SNickeau $this->close(); 353c3437056SNickeau return true; 354c3437056SNickeau } 355c3437056SNickeau 35604fd306cSNickeau public function close() 357c3437056SNickeau { 358c3437056SNickeau 35904fd306cSNickeau /** 36004fd306cSNickeau * https://www.php.net/manual/en/pdo.connections.php#114822 36104fd306cSNickeau * You put the variable connection on null 36204fd306cSNickeau * the {@link \helper_plugin_sqlite_adapter::closedb() function} do that 36304fd306cSNickeau * 36404fd306cSNickeau * If we don't do that, the file is still locked 36504fd306cSNickeau * by the sqlite process and the clean up process 36604fd306cSNickeau * of dokuwiki test cannot delete it 36704fd306cSNickeau * 36804fd306cSNickeau * ie to avoid 36904fd306cSNickeau * RuntimeException: Unable to delete the file 37004fd306cSNickeau * (C:/Users/GERARD~1/AppData/Local/Temp/dwtests-1676813655.6773/data/meta/combo-secondary.sqlite3) in D:\dokuwiki\_test\core\TestUtils.php on line 58 37104fd306cSNickeau * {@link TestUtils::rdelete} 37204fd306cSNickeau * 37304fd306cSNickeau * Windows sort of handling/ bug explained here 37404fd306cSNickeau * https://bugs.php.net/bug.php?id=78930&edit=3 37504fd306cSNickeau * 37604fd306cSNickeau * Null to close the db explanation and bug 37704fd306cSNickeau * https://bugs.php.net/bug.php?id=62065 37804fd306cSNickeau * 37904fd306cSNickeau */ 38004fd306cSNickeau 38104fd306cSNickeau $this->closeActualRequestIfNotClosed(); 38204fd306cSNickeau 383c3437056SNickeau $adapter = $this->sqlitePlugin->getAdapter(); 384c3437056SNickeau if ($adapter !== null) { 38504fd306cSNickeau 386*d899a2a6Sgerardnico if (!$this->isJuneVersion($adapter)) { 387*d899a2a6Sgerardnico /** @noinspection PhpUndefinedMethodInspection */ 388c3437056SNickeau $adapter->closedb(); 389*d899a2a6Sgerardnico } else { 390*d899a2a6Sgerardnico $adapter->__sleep(); 391*d899a2a6Sgerardnico } 392c3437056SNickeau 39304fd306cSNickeau unset($adapter); 39404fd306cSNickeau 39504fd306cSNickeau gc_collect_cycles(); 396c3437056SNickeau 397c3437056SNickeau } 398c3437056SNickeau 399c3437056SNickeau } 400c3437056SNickeau 401c3437056SNickeau public function getDbName(): string 402c3437056SNickeau { 403c3437056SNickeau return $this->sqlitePlugin->getAdapter()->getName(); 404c3437056SNickeau } 405c3437056SNickeau 406c3437056SNickeau 407c3437056SNickeau public function getSqlitePlugin(): helper_plugin_sqlite 408c3437056SNickeau { 409c3437056SNickeau return $this->sqlitePlugin; 410c3437056SNickeau } 411c3437056SNickeau 412c3437056SNickeau public function createRequest(): SqliteRequest 413c3437056SNickeau { 41404fd306cSNickeau $this->closeActualRequestIfNotClosed(); 41504fd306cSNickeau $this->actualRequest = new SqliteRequest($this); 41604fd306cSNickeau return $this->actualRequest; 417c3437056SNickeau } 418c3437056SNickeau 419c3437056SNickeau public function getVersion() 420c3437056SNickeau { 421c3437056SNickeau if (self::$sqliteVersion === null) { 422c3437056SNickeau $request = $this->createRequest() 423c3437056SNickeau ->setQuery("select sqlite_version()"); 424c3437056SNickeau try { 425c3437056SNickeau self::$sqliteVersion = $request 426c3437056SNickeau ->execute() 427c3437056SNickeau ->getFirstCellValue(); 42804fd306cSNickeau } catch (ExceptionCompile $e) { 429c3437056SNickeau self::$sqliteVersion = "unknown"; 430c3437056SNickeau } finally { 431c3437056SNickeau $request->close(); 432c3437056SNickeau } 433c3437056SNickeau } 434c3437056SNickeau return self::$sqliteVersion; 435c3437056SNickeau } 436c3437056SNickeau 437c3437056SNickeau /** 438c3437056SNickeau * @param string $option 439c3437056SNickeau * @return bool - true if the option is available 440c3437056SNickeau */ 441c3437056SNickeau public function hasOption(string $option): bool 442c3437056SNickeau { 443c3437056SNickeau try { 444c3437056SNickeau $present = $this->createRequest() 44504fd306cSNickeau ->setQueryParametrized("select count(1) from pragma_compile_options() where compile_options = ?", [$option]) 446c3437056SNickeau ->execute() 447c3437056SNickeau ->getFirstCellValueAsInt(); 448c3437056SNickeau return $present === 1; 44904fd306cSNickeau } catch (ExceptionCompile $e) { 450c3437056SNickeau LogUtility::msg("Error while trying to see if the sqlite option is available"); 451c3437056SNickeau return false; 452c3437056SNickeau } 453c3437056SNickeau 454c3437056SNickeau } 45504fd306cSNickeau 45604fd306cSNickeau /** 45704fd306cSNickeau * Internal function that closes the actual request 45804fd306cSNickeau * This is to be able to close all resources even if the developer 45904fd306cSNickeau * forget. 46004fd306cSNickeau * 46104fd306cSNickeau * This is needed to be able to delete the database file. 46204fd306cSNickeau * See {@link self::close()} for more information 46304fd306cSNickeau * 46404fd306cSNickeau * @return void 46504fd306cSNickeau */ 46604fd306cSNickeau private function closeActualRequestIfNotClosed() 46704fd306cSNickeau { 46804fd306cSNickeau if (isset($this->actualRequest)) { 46904fd306cSNickeau $this->actualRequest->close(); 47004fd306cSNickeau unset($this->actualRequest); 47104fd306cSNickeau } 47204fd306cSNickeau } 473*d899a2a6Sgerardnico 474*d899a2a6Sgerardnico /** 475*d899a2a6Sgerardnico * Version 2023-06-21 brings error 476*d899a2a6Sgerardnico * https://github.com/cosmocode/sqlite/releases/tag/2023-06-21 477*d899a2a6Sgerardnico * https://www.dokuwiki.org/plugin:sqlite#changes_from_earlier_releases 478*d899a2a6Sgerardnico * @param $adapter 479*d899a2a6Sgerardnico * @return bool 480*d899a2a6Sgerardnico */ 481*d899a2a6Sgerardnico public static function isJuneVersion($adapter): bool 482*d899a2a6Sgerardnico { 483*d899a2a6Sgerardnico return get_class($adapter) === SQLiteDB::class; 484*d899a2a6Sgerardnico } 485*d899a2a6Sgerardnico 48637748cd8SNickeau} 487