1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +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:
J. King 2019-03-04 11:05:46 -05:00
parent 5efef2c2d0
commit 6000d80b7b
10 changed files with 88 additions and 7 deletions

View file

@ -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
View 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 {
}

View 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;
}

View file

@ -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

View file

@ -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:

View file

@ -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;
}
}
}
} }

View file

@ -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;
}
}
}
} }

View file

@ -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}',

View file

@ -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

View file

@ -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") : [];