diff --git a/lib/AbstractException.php b/lib/AbstractException.php index 0f617639..7e155afa 100644 --- a/lib/AbstractException.php +++ b/lib/AbstractException.php @@ -22,11 +22,13 @@ abstract class AbstractException extends \Exception { "Db/Exception.fileUncreatable" => 10206, "Db/Exception.fileCorrupt" => 10207, "Db/Exception.updateTooNew" => 10211, - "Db/Exception.updateFileMissing" => 10212, - "Db/Exception.updateFileUnusable" => 10213, - "Db/Exception.updateFileUnreadable" => 10214, - "Db/Exception.updateManual" => 10215, - "Db/Exception.updateManualOnly" => 10216, + "Db/Exception.updateManual" => 10212, + "Db/Exception.updateManualOnly" => 10213, + "Db/Exception.updateFileMissing" => 10214, + "Db/Exception.updateFileUnusable" => 10215, + "Db/Exception.updateFileUnreadable" => 10216, + "Db/Exception.updateFileError" => 10217, + "Db/Exception.updateFileIncomplete" => 10218, "Db/Exception.paramTypeInvalid" => 10221, "Db/Exception.paramTypeUnknown" => 10222, "Db/Exception.paramTypeMissing" => 10223, @@ -39,12 +41,12 @@ abstract class AbstractException extends \Exception { "Db/ExceptionInput.constraintViolation" => 10236, "Db/ExceptionInput.typeViolation" => 10237, "Db/ExceptionTimeout.general" => 10241, - "Conf/Exception.fileMissing" => 10302, - "Conf/Exception.fileUnusable" => 10303, - "Conf/Exception.fileUnreadable" => 10304, - "Conf/Exception.fileUnwritable" => 10305, - "Conf/Exception.fileUncreatable" => 10306, - "Conf/Exception.fileCorrupt" => 10307, + "Conf/Exception.fileMissing" => 10301, + "Conf/Exception.fileUnusable" => 10302, + "Conf/Exception.fileUnreadable" => 10303, + "Conf/Exception.fileUnwritable" => 10304, + "Conf/Exception.fileUncreatable" => 10305, + "Conf/Exception.fileCorrupt" => 10306, "User/Exception.functionNotImplemented" => 10401, "User/Exception.doesNotExist" => 10402, "User/Exception.alreadyExists" => 10403, @@ -61,7 +63,7 @@ abstract class AbstractException extends \Exception { "Feed/Exception.malformed" => 10511, "Feed/Exception.xmlEntity" => 10512, "Feed/Exception.subscriptionNotFound" => 10521, - "Feed/Exception.unsupportedFeedFormat" => 10522 + "Feed/Exception.unsupportedFeedFormat" => 10522, ]; public function __construct(string $msgID = "", $vars = null, \Throwable $e = null) { diff --git a/lib/Conf.php b/lib/Conf.php index c373519f..faa41b8e 100644 --- a/lib/Conf.php +++ b/lib/Conf.php @@ -6,6 +6,7 @@ class Conf { public $lang = "en"; public $dbDriver = Db\SQLite3\Driver::class; + public $dbSchemaBase = BASE.'sql'; public $dbSQLite3File = BASE."newssync.db"; public $dbSQLite3Key = ""; public $dbSQLite3AutoUpd = true; @@ -28,8 +29,6 @@ class Conf { public $userComposeNames = true; public $userTempPasswordLength = 20; - public $simplepieCache = BASE.".cache"; - public function __construct(string $import_file = "") { if($import_file != "") $this->importFile($import_file); diff --git a/lib/Db/AbstractDriver.php b/lib/Db/AbstractDriver.php index c78adea7..711f958d 100644 --- a/lib/Db/AbstractDriver.php +++ b/lib/Db/AbstractDriver.php @@ -57,6 +57,7 @@ abstract class AbstractDriver implements Driver { } public function unlock(): bool { + if($this->schemaVersion() < 1) return true; $this->exec("DELETE from newssync_settings where key is 'lock'"); return true; } diff --git a/lib/Db/SQLite3/Driver.php b/lib/Db/SQLite3/Driver.php index e415c028..b9a9911b 100644 --- a/lib/Db/SQLite3/Driver.php +++ b/lib/Db/SQLite3/Driver.php @@ -61,21 +61,27 @@ class Driver extends \JKingWeb\NewsSync\Db\AbstractDriver { if(!$this->data->conf->dbSQLite3AutoUpd) throw new Exception("updateManual", ['version' => $ver, 'driver_name' => $this->driverName()]); if($ver >= $to) throw new Exception("updateTooNew", ['difference' => ($ver - $to), 'current' => $ver, 'target' => $to, 'driver_name' => $this->driverName()]); $sep = \DIRECTORY_SEPARATOR; - $path = \JKingWeb\NewsSync\BASE."sql".$sep."SQLite3".$sep; + $path = $this->data->conf->dbSchemaBase.$sep."SQLite3".$sep; $this->lock(); $this->begin(); for($a = $ver; $a < $to; $a++) { $this->begin(); try { $file = $path.$a.".sql"; - if(!file_exists($file)) throw new Exception("updateFileMissing", ['file' => $file, 'driver_name' => $this->driverName()]); - if(!is_readable($file)) throw new Exception("updateFileUnreadable", ['file' => $file, 'driver_name' => $this->driverName()]); + if(!file_exists($file)) throw new Exception("updateFileMissing", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]); + if(!is_readable($file)) throw new Exception("updateFileUnreadable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]); $sql = @file_get_contents($file); - if($sql===false) throw new Exception("updateFileUnusable", ['file' => $file, 'driver_name' => $this->driverName()]); - $this->exec($sql); + if($sql===false) throw new Exception("updateFileUnusable", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]); + try { + $this->exec($sql); + } catch(\Exception $e) { + throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $this->getError()]); + } + if($this->schemaVersion() != $a+1) throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]); } catch(\Throwable $e) { // undo any partial changes from the failed update $this->rollback(); + $this->unlock(); // commit any successful updates if updating by more than one version $this->commit(true); // throw the error received @@ -88,6 +94,10 @@ class Driver extends \JKingWeb\NewsSync\Db\AbstractDriver { return true; } + protected function getError(): string { + return $this->db->lastErrorMsg(); + } + public function exec(string $query): bool { try { return (bool) $this->db->exec($query); diff --git a/locale/en.php b/locale/en.php index 6bc3a02f..330dad4a 100644 --- a/locale/en.php +++ b/locale/en.php @@ -42,6 +42,8 @@ return [ 'Exception.JKingWeb/NewsSync/Db/Exception.updateFileMissing' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} not being available', 'Exception.JKingWeb/NewsSync/Db/Exception.updateFileUnreadable' => 'Automatic updating of the {driver_name} database failed due to insufficient permissions to read instructions for updating from version {current}', 'Exception.JKingWeb/NewsSync/Db/Exception.updateFileUnusable' => 'Automatic updating of the {driver_name} database failed due to an error reading instructions for updating from version {current}', + 'Exception.JKingWeb/NewsSync/Db/Exception.updateFileError' => 'Automatic updating of the {driver_name} database failed updating from version {current} with the following error: "{message}"', + 'Exception.JKingWeb/NewsSync/Db/Exception.updateFileIncomplete' => 'Automatic updating of the {driver_name} database failed due to instructions for updating from version {current} being incomplete', 'Exception.JKingWeb/NewsSync/Db/Exception.updateTooNew' => '{difference, select, 0 {Automatic updating of the {driver_name} database failed because it is already up to date with the requested version, {target}} diff --git a/tests/Db/SQLite3/TestDbDriverSQLite3.php b/tests/Db/SQLite3/TestDbDriverSQLite3.php index c8decda3..091146c2 100644 --- a/tests/Db/SQLite3/TestDbDriverSQLite3.php +++ b/tests/Db/SQLite3/TestDbDriverSQLite3.php @@ -6,7 +6,8 @@ namespace JKingWeb\NewsSync; class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase { use Test\Tools; - protected $c; + protected $data; + protected $drv; function setUp() { $conf = new Conf(); @@ -262,6 +263,7 @@ class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase { } function testManipulateAdvisoryLock() { + $this->assertTrue($this->drv->unlock()); $this->assertFalse($this->drv->isLocked()); $this->assertTrue($this->drv->lock()); $this->assertFalse($this->drv->isLocked()); @@ -278,9 +280,4 @@ class TestDbDriverSQLite3 extends \PHPUnit\Framework\TestCase { $this->assertTrue($this->drv->unlock()); $this->assertFalse($this->drv->isLocked()); } - - function testUpdateTheSchema() { - // FIXME: This should be its own test suite with VFS schemata to simulate various error conditions - $this->assertTrue($this->drv->schemaUpdate(1)); - } } \ No newline at end of file diff --git a/tests/Db/SQLite3/TestDbUpdateSQLite3.php b/tests/Db/SQLite3/TestDbUpdateSQLite3.php new file mode 100644 index 00000000..30b23ff6 --- /dev/null +++ b/tests/Db/SQLite3/TestDbUpdateSQLite3.php @@ -0,0 +1,89 @@ +vfs = vfsStream::setup("schemata", null, ['SQLite3' => []]); + $conf = new Conf(); + $conf->dbDriver = Db\SQLite3\Driver::class; + $conf->dbSchemaBase = $this->vfs->url(); + $this->base = $this->vfs->url()."/SQLite3/"; + $conf->dbSQLite3File = ":memory:"; + $this->data = new Test\RuntimeData($conf); + $this->drv = new Db\SQLite3\Driver($this->data, true); + } + + function tearDown() { + unset($this->drv); + unset($this->data); + unset($this->vfs); + } + + function testLoadMissingFile() { + $this->assertException("updateFileMissing", "Db"); + $this->drv->schemaUpdate(1); + } + + function testLoadUnreadableFile() { + touch($this->base."0.sql"); + chmod($this->base."0.sql", 0000); + $this->assertException("updateFileUnreadable", "Db"); + $this->drv->schemaUpdate(1); + } + + function testLoadCorruptFile() { + file_put_contents($this->base."0.sql", "This is a corrupt file"); + $this->assertException("updateFileError", "Db"); + $this->drv->schemaUpdate(1); + } + + function testLoadIncompleteFile() { + file_put_contents($this->base."0.sql", "create table newssync_settings(key text primary key not null, value text, type text not null);"); + $this->assertException("updateFileIncomplete", "Db"); + $this->drv->schemaUpdate(1); + } + + function testLoadCorrectFile() { + file_put_contents($this->base."0.sql", self::MINIMAL1); + $this->drv->schemaUpdate(1); + $this->assertEquals(1, $this->drv->schemaVersion()); + } + + function testPerformPartialUpdate() { + file_put_contents($this->base."0.sql", self::MINIMAL1); + file_put_contents($this->base."1.sql", ""); + $this->assertException("updateFileIncomplete", "Db"); + try { + $this->drv->schemaUpdate(2); + } catch(Exception $e) { + $this->assertEquals(1, $this->drv->schemaVersion()); + throw $e; + } + } + + function testPerformSequentialUpdate() { + file_put_contents($this->base."0.sql", self::MINIMAL1); + file_put_contents($this->base."1.sql", self::MINIMAL2); + $this->drv->schemaUpdate(2); + $this->assertEquals(2, $this->drv->schemaVersion()); + } + + function testPerformActualUpdate() { + $this->data->conf->dbSchemaBase = (new Conf())->dbSchemaBase; + $this->drv->schemaUpdate(Database::SCHEMA_VERSION); + $this->assertEquals(Database::SCHEMA_VERSION, $this->drv->schemaVersion()); + } +} \ No newline at end of file diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 1153fa44..303a5314 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -32,6 +32,7 @@ Db/SQLite3/TestDbResultSQLite3.php Db/SQLite3/TestDbStatementSQLite3.php Db/SQLite3/TestDbDriverSQLite3.php + Db/SQLite3/TestDbUpdateSQLite3.php \ No newline at end of file