mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Work around various SQLite-related problems
- WAL mode was not getting set properly - Queries using the PDO driver could fail because PDO sucks
This commit is contained in:
parent
fb1bdbfb37
commit
ed22090e49
10 changed files with 88 additions and 7 deletions
|
@ -45,6 +45,7 @@ abstract class AbstractException extends \Exception {
|
|||
"Db/Exception.savepointInvalid" => 10226,
|
||||
"Db/Exception.savepointStale" => 10227,
|
||||
"Db/Exception.resultReused" => 10228,
|
||||
"Db/ExceptionRetry.schemaChange" => 10229,
|
||||
"Db/ExceptionInput.missing" => 10231,
|
||||
"Db/ExceptionInput.whitespace" => 10232,
|
||||
"Db/ExceptionInput.tooLong" => 10233,
|
||||
|
|
10
lib/Db/ExceptionRetry.php
Normal file
10
lib/Db/ExceptionRetry.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
/** @license MIT
|
||||
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||
* See LICENSE and AUTHORS files for details */
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Db;
|
||||
|
||||
class ExceptionRetry extends \JKingWeb\Arsse\AbstractException {
|
||||
}
|
11
lib/Db/SQLite3/AbstractPDODriver.php
Normal file
11
lib/Db/SQLite3/AbstractPDODriver.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
/** @license MIT
|
||||
* Copyright 2017 J. King, Dustin Wilson et al.
|
||||
* See LICENSE and AUTHORS files for details */
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace JKingWeb\Arsse\Db\SQLite3;
|
||||
|
||||
abstract class AbstractPDODriver extends Driver {
|
||||
use \JKingWeb\Arsse\Db\PDODriver;
|
||||
}
|
|
@ -17,6 +17,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
const TRANSACTIONAL_LOCKS = true;
|
||||
|
||||
const SQLITE_BUSY = 5;
|
||||
const SQLITE_SCHEMA = 17;
|
||||
const SQLITE_CONSTRAINT = 19;
|
||||
const SQLITE_MISMATCH = 20;
|
||||
|
||||
|
@ -122,6 +123,10 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
|||
}
|
||||
|
||||
public function schemaUpdate(int $to, string $basePath = null): bool {
|
||||
if ($to == 1) {
|
||||
// if we're initializing the database for the first time, switch to WAL mode
|
||||
$this->exec("PRAGMA journal_mode = wal");
|
||||
}
|
||||
// turn off foreign keys
|
||||
$this->exec("PRAGMA foreign_keys = no");
|
||||
// run the generic updater
|
||||
|
|
|
@ -7,6 +7,7 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\Arsse\Db\SQLite3;
|
||||
|
||||
use JKingWeb\Arsse\Db\Exception;
|
||||
use JKingWeb\Arsse\Db\ExceptionRetry;
|
||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
||||
|
||||
|
@ -19,6 +20,8 @@ trait ExceptionBuilder {
|
|||
switch ($code) {
|
||||
case Driver::SQLITE_BUSY:
|
||||
return [ExceptionTimeout::class, 'general', $msg];
|
||||
case Driver::SQLITE_SCHEMA:
|
||||
return [ExceptionRetry::class, 'schemaChange', $msg];
|
||||
case Driver::SQLITE_CONSTRAINT:
|
||||
return [ExceptionInput::class, 'engineConstraintViolation', $msg];
|
||||
case Driver::SQLITE_MISMATCH:
|
||||
|
|
|
@ -11,9 +11,7 @@ use JKingWeb\Arsse\Db\Exception;
|
|||
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
||||
|
||||
class PDODriver extends Driver {
|
||||
use \JKingWeb\Arsse\Db\PDODriver;
|
||||
|
||||
class PDODriver extends AbstractPDODriver {
|
||||
protected $db;
|
||||
|
||||
public static function requirementsMet(): bool {
|
||||
|
@ -49,4 +47,40 @@ class PDODriver extends Driver {
|
|||
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
|
||||
return new PDOStatement($this->db, $query, $paramTypes);
|
||||
}
|
||||
|
||||
/** @codeCoverageIgnore */
|
||||
public function exec(string $query): bool {
|
||||
// because PDO uses sqlite3_prepare() internally instead of sqlite3_prepare_v2(),
|
||||
// we have to retry ourselves in cases of schema changes
|
||||
// the SQLite3 class is not similarly affected
|
||||
$attempts = 0;
|
||||
retry:
|
||||
try {
|
||||
return parent::exec($query);
|
||||
} catch (\JKingWeb\Arsse\Db\ExceptionRetry $e) {
|
||||
if (++$attempts > 50) {
|
||||
throw $e;
|
||||
} else {
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @codeCoverageIgnore */
|
||||
public function query(string $query): \JKingWeb\Arsse\Db\Result {
|
||||
// because PDO uses sqlite3_prepare() internally instead of sqlite3_prepare_v2(),
|
||||
// we have to retry ourselves in cases of schema changes
|
||||
// the SQLite3 class is not similarly affected
|
||||
$attempts = 0;
|
||||
retry:
|
||||
try {
|
||||
return parent::query($query);
|
||||
} catch (\JKingWeb\Arsse\Db\ExceptionRetry $e) {
|
||||
if (++$attempts > 50) {
|
||||
throw $e;
|
||||
} else {
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,23 @@ namespace JKingWeb\Arsse\Db\SQLite3;
|
|||
class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
|
||||
use ExceptionBuilder;
|
||||
use \JKingWeb\Arsse\Db\PDOError;
|
||||
|
||||
/** @codeCoverageIgnore */
|
||||
public function runArray(array $values = []): \JKingWeb\Arsse\Db\Result {
|
||||
// because PDO uses sqlite3_prepare() internally instead of sqlite3_prepare_v2(),
|
||||
// we have to retry ourselves in cases of schema changes
|
||||
// the SQLite3 class is not similarly affected
|
||||
$attempts = 0;
|
||||
retry:
|
||||
try {
|
||||
return parent::runArray($values);
|
||||
} catch (\JKingWeb\Arsse\Db\ExceptionRetry $e) {
|
||||
if (++$attempts > 50) {
|
||||
throw $e;
|
||||
} else {
|
||||
$this->st = $this->db->prepare($this->st->queryString);
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ return [
|
|||
'Exception.JKingWeb/Arsse/Db/Exception.savepointStale' => 'Tried to {action} stale savepoint {index}',
|
||||
// indicates programming error
|
||||
'Exception.JKingWeb/Arsse/Db/Exception.resultReused' => 'Result set already iterated',
|
||||
'Exception.JKingWeb/Arsse/Db/ExceptionRetry.schemaChange' => '{0}',
|
||||
'Exception.JKingWeb/Arsse/Db/ExceptionInput.missing' => 'Required field "{field}" missing while performing action "{action}"',
|
||||
'Exception.JKingWeb/Arsse/Db/ExceptionInput.whitespace' => 'Field "{field}" of action "{action}" may not contain only whitespace',
|
||||
'Exception.JKingWeb/Arsse/Db/ExceptionInput.tooLong' => 'Field "{field}" of action "{action}" has a maximum length of {max}',
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
-- Copyright 2017 J. King, Dustin Wilson et al.
|
||||
-- See LICENSE and AUTHORS files for details
|
||||
|
||||
-- Make the database WAL-journalled; this is persitent
|
||||
PRAGMA journal_mode = wal;
|
||||
|
||||
create table arsse_meta(
|
||||
-- application metadata
|
||||
key text primary key not null, -- metadata key
|
||||
|
|
|
@ -28,7 +28,7 @@ trait SQLite3 {
|
|||
}
|
||||
|
||||
public static function dbTableList($db): array {
|
||||
$listTables = "SELECT name from sqlite_master where type = 'table' and name like 'arsse_%'";
|
||||
$listTables = "SELECT name from sqlite_master where type = 'table' and name like 'arsse^_%' escape '^'";
|
||||
if ($db instanceof Driver) {
|
||||
$tables = $db->query($listTables)->getAll();
|
||||
$tables = sizeof($tables) ? array_column($tables, "name") : [];
|
||||
|
|
Loading…
Reference in a new issue