Selaa lähdekoodia

Work around various SQLite-related problems

- WAL mode was not getting set properly
- Queries using the PDO driver could fail because PDO sucks
microsub
J. King 5 vuotta sitten
vanhempi
commit
6000d80b7b
  1. 1
      lib/AbstractException.php
  2. 10
      lib/Db/ExceptionRetry.php
  3. 11
      lib/Db/SQLite3/AbstractPDODriver.php
  4. 5
      lib/Db/SQLite3/Driver.php
  5. 3
      lib/Db/SQLite3/ExceptionBuilder.php
  6. 40
      lib/Db/SQLite3/PDODriver.php
  7. 19
      lib/Db/SQLite3/PDOStatement.php
  8. 1
      locale/en.php
  9. 3
      sql/SQLite3/0.sql
  10. 2
      tests/cases/DatabaseDrivers/SQLite3.php

1
lib/AbstractException.php

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

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

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

5
lib/Db/SQLite3/Driver.php

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

3
lib/Db/SQLite3/ExceptionBuilder.php

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

40
lib/Db/SQLite3/PDODriver.php

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

19
lib/Db/SQLite3/PDOStatement.php

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

1
locale/en.php

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

3
sql/SQLite3/0.sql

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

2
tests/cases/DatabaseDrivers/SQLite3.php

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

Ladataan…
Peruuta
Tallenna