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.savepointInvalid" => 10226,
|
||||||
"Db/Exception.savepointStale" => 10227,
|
"Db/Exception.savepointStale" => 10227,
|
||||||
"Db/Exception.resultReused" => 10228,
|
"Db/Exception.resultReused" => 10228,
|
||||||
|
"Db/ExceptionRetry.schemaChange" => 10229,
|
||||||
"Db/ExceptionInput.missing" => 10231,
|
"Db/ExceptionInput.missing" => 10231,
|
||||||
"Db/ExceptionInput.whitespace" => 10232,
|
"Db/ExceptionInput.whitespace" => 10232,
|
||||||
"Db/ExceptionInput.tooLong" => 10233,
|
"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 TRANSACTIONAL_LOCKS = true;
|
||||||
|
|
||||||
const SQLITE_BUSY = 5;
|
const SQLITE_BUSY = 5;
|
||||||
|
const SQLITE_SCHEMA = 17;
|
||||||
const SQLITE_CONSTRAINT = 19;
|
const SQLITE_CONSTRAINT = 19;
|
||||||
const SQLITE_MISMATCH = 20;
|
const SQLITE_MISMATCH = 20;
|
||||||
|
|
||||||
|
@ -122,6 +123,10 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function schemaUpdate(int $to, string $basePath = null): bool {
|
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
|
// turn off foreign keys
|
||||||
$this->exec("PRAGMA foreign_keys = no");
|
$this->exec("PRAGMA foreign_keys = no");
|
||||||
// run the generic updater
|
// run the generic updater
|
||||||
|
|
|
@ -7,6 +7,7 @@ declare(strict_types=1);
|
||||||
namespace JKingWeb\Arsse\Db\SQLite3;
|
namespace JKingWeb\Arsse\Db\SQLite3;
|
||||||
|
|
||||||
use JKingWeb\Arsse\Db\Exception;
|
use JKingWeb\Arsse\Db\Exception;
|
||||||
|
use JKingWeb\Arsse\Db\ExceptionRetry;
|
||||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||||
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
||||||
|
|
||||||
|
@ -19,6 +20,8 @@ trait ExceptionBuilder {
|
||||||
switch ($code) {
|
switch ($code) {
|
||||||
case Driver::SQLITE_BUSY:
|
case Driver::SQLITE_BUSY:
|
||||||
return [ExceptionTimeout::class, 'general', $msg];
|
return [ExceptionTimeout::class, 'general', $msg];
|
||||||
|
case Driver::SQLITE_SCHEMA:
|
||||||
|
return [ExceptionRetry::class, 'schemaChange', $msg];
|
||||||
case Driver::SQLITE_CONSTRAINT:
|
case Driver::SQLITE_CONSTRAINT:
|
||||||
return [ExceptionInput::class, 'engineConstraintViolation', $msg];
|
return [ExceptionInput::class, 'engineConstraintViolation', $msg];
|
||||||
case Driver::SQLITE_MISMATCH:
|
case Driver::SQLITE_MISMATCH:
|
||||||
|
|
|
@ -11,9 +11,7 @@ use JKingWeb\Arsse\Db\Exception;
|
||||||
use JKingWeb\Arsse\Db\ExceptionInput;
|
use JKingWeb\Arsse\Db\ExceptionInput;
|
||||||
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
use JKingWeb\Arsse\Db\ExceptionTimeout;
|
||||||
|
|
||||||
class PDODriver extends Driver {
|
class PDODriver extends AbstractPDODriver {
|
||||||
use \JKingWeb\Arsse\Db\PDODriver;
|
|
||||||
|
|
||||||
protected $db;
|
protected $db;
|
||||||
|
|
||||||
public static function requirementsMet(): bool {
|
public static function requirementsMet(): bool {
|
||||||
|
@ -49,4 +47,40 @@ class PDODriver extends Driver {
|
||||||
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
|
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
|
||||||
return new PDOStatement($this->db, $query, $paramTypes);
|
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 {
|
class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
|
||||||
use ExceptionBuilder;
|
use ExceptionBuilder;
|
||||||
use \JKingWeb\Arsse\Db\PDOError;
|
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}',
|
'Exception.JKingWeb/Arsse/Db/Exception.savepointStale' => 'Tried to {action} stale savepoint {index}',
|
||||||
// indicates programming error
|
// indicates programming error
|
||||||
'Exception.JKingWeb/Arsse/Db/Exception.resultReused' => 'Result set already iterated',
|
'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.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.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}',
|
'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.
|
-- Copyright 2017 J. King, Dustin Wilson et al.
|
||||||
-- See LICENSE and AUTHORS files for details
|
-- See LICENSE and AUTHORS files for details
|
||||||
|
|
||||||
-- Make the database WAL-journalled; this is persitent
|
|
||||||
PRAGMA journal_mode = wal;
|
|
||||||
|
|
||||||
create table arsse_meta(
|
create table arsse_meta(
|
||||||
-- application metadata
|
-- application metadata
|
||||||
key text primary key not null, -- metadata key
|
key text primary key not null, -- metadata key
|
||||||
|
|
|
@ -28,7 +28,7 @@ trait SQLite3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function dbTableList($db): array {
|
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) {
|
if ($db instanceof Driver) {
|
||||||
$tables = $db->query($listTables)->getAll();
|
$tables = $db->query($listTables)->getAll();
|
||||||
$tables = sizeof($tables) ? array_column($tables, "name") : [];
|
$tables = sizeof($tables) ? array_column($tables, "name") : [];
|
||||||
|
|
Loading…
Reference in a new issue