J. King
6 years ago
54 changed files with 2049 additions and 514 deletions
@ -0,0 +1,47 @@ |
|||||
|
<?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; |
||||
|
|
||||
|
trait PDODriver { |
||||
|
use PDOError; |
||||
|
|
||||
|
public function exec(string $query): bool { |
||||
|
try { |
||||
|
$this->db->exec($query); |
||||
|
return true; |
||||
|
} catch (\PDOException $e) { |
||||
|
list($excClass, $excMsg, $excData) = $this->exceptionBuild(); |
||||
|
throw new $excClass($excMsg, $excData); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function query(string $query): Result { |
||||
|
try { |
||||
|
$r = $this->db->query($query); |
||||
|
} catch (\PDOException $e) { |
||||
|
list($excClass, $excMsg, $excData) = $this->exceptionBuild(); |
||||
|
throw new $excClass($excMsg, $excData); |
||||
|
} |
||||
|
$changes = $r->rowCount(); |
||||
|
try { |
||||
|
$lastId = 0; |
||||
|
$lastId = $this->db->lastInsertId(); |
||||
|
} catch (\PDOException $e) { // @codeCoverageIgnore |
||||
|
} |
||||
|
return new PDOResult($r, [$changes, $lastId]); |
||||
|
} |
||||
|
|
||||
|
public function prepareArray(string $query, array $paramTypes): Statement { |
||||
|
try { |
||||
|
$s = $this->db->prepare($query); |
||||
|
} catch (\PDOException $e) { |
||||
|
list($excClass, $excMsg, $excData) = $this->exceptionBuild(); |
||||
|
throw new $excClass($excMsg, $excData); |
||||
|
} |
||||
|
return new PDOStatement($this->db, $s, $paramTypes); |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
<?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; |
||||
|
|
||||
|
trait PDOError { |
||||
|
public function exceptionBuild() { |
||||
|
if ($this instanceof Statement) { |
||||
|
$err = $this->st->errorInfo(); |
||||
|
} else { |
||||
|
$err = $this->db->errorInfo(); |
||||
|
} |
||||
|
switch ($err[0]) { |
||||
|
case "23000": |
||||
|
return [ExceptionInput::class, "constraintViolation", $err[2]]; |
||||
|
case "HY000": |
||||
|
// engine-specific errors |
||||
|
switch ($this->db->getAttribute(\PDO::ATTR_DRIVER_NAME)) { |
||||
|
case "sqlite": |
||||
|
switch ($err[1]) { |
||||
|
case \JKingWeb\Arsse\Db\SQLite3\Driver::SQLITE_BUSY: |
||||
|
return [ExceptionTimeout::class, 'general', $err[2]]; |
||||
|
case \JKingWeb\Arsse\Db\SQLite3\Driver::SQLITE_MISMATCH: |
||||
|
return [ExceptionInput::class, 'engineTypeViolation', $err[2]]; |
||||
|
default: |
||||
|
return [Exception::class, "engineErrorGeneral", $err[1]." - ".$err[2]]; |
||||
|
} |
||||
|
// no break |
||||
|
default: |
||||
|
return [Exception::class, "engineErrorGeneral", $err[2]]; // @codeCoverageIgnore |
||||
|
} |
||||
|
// no break |
||||
|
default: |
||||
|
return [Exception::class, "engineErrorGeneral", $err[0].": ".$err[2]]; // @codeCoverageIgnore |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function getError(): string { |
||||
|
return (string) $this->db->errorInfo()[2]; |
||||
|
} |
||||
|
} |
@ -0,0 +1,49 @@ |
|||||
|
<?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; |
||||
|
|
||||
|
use JKingWeb\Arsse\Db\Exception; |
||||
|
|
||||
|
class PDOResult extends AbstractResult { |
||||
|
protected $set; |
||||
|
protected $cur = null; |
||||
|
protected $rows = 0; |
||||
|
protected $id = 0; |
||||
|
|
||||
|
// actual public methods |
||||
|
|
||||
|
public function changes() { |
||||
|
return $this->rows; |
||||
|
} |
||||
|
|
||||
|
public function lastId() { |
||||
|
return $this->id; |
||||
|
} |
||||
|
|
||||
|
// constructor/destructor |
||||
|
|
||||
|
public function __construct(\PDOStatement $result, array $changes = [0,0]) { |
||||
|
$this->set = $result; |
||||
|
$this->rows = (int) $changes[0]; |
||||
|
$this->id = (int) $changes[1]; |
||||
|
} |
||||
|
|
||||
|
public function __destruct() { |
||||
|
try { |
||||
|
$this->set->closeCursor(); |
||||
|
} catch (\PDOException $e) { // @codeCoverageIgnore |
||||
|
} |
||||
|
unset($this->set); |
||||
|
} |
||||
|
|
||||
|
// PHP iterator methods |
||||
|
|
||||
|
public function valid() { |
||||
|
$this->cur = $this->set->fetch(\PDO::FETCH_ASSOC); |
||||
|
return ($this->cur !== false); |
||||
|
} |
||||
|
} |
@ -0,0 +1,55 @@ |
|||||
|
<?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 PDOStatement extends AbstractStatement { |
||||
|
use PDOError; |
||||
|
|
||||
|
const BINDINGS = [ |
||||
|
"integer" => \PDO::PARAM_INT, |
||||
|
"float" => \PDO::PARAM_STR, |
||||
|
"datetime" => \PDO::PARAM_STR, |
||||
|
"binary" => \PDO::PARAM_LOB, |
||||
|
"string" => \PDO::PARAM_STR, |
||||
|
"boolean" => \PDO::PARAM_BOOL, |
||||
|
]; |
||||
|
|
||||
|
protected $st; |
||||
|
protected $db; |
||||
|
|
||||
|
public function __construct(\PDO $db, \PDOStatement $st, array $bindings = []) { |
||||
|
$this->db = $db; |
||||
|
$this->st = $st; |
||||
|
$this->retypeArray($bindings); |
||||
|
} |
||||
|
|
||||
|
public function __destruct() { |
||||
|
unset($this->st); |
||||
|
} |
||||
|
|
||||
|
public function runArray(array $values = []): \JKingWeb\Arsse\Db\Result { |
||||
|
$this->st->closeCursor(); |
||||
|
$this->bindValues($values); |
||||
|
try { |
||||
|
$this->st->execute(); |
||||
|
} catch (\PDOException $e) { |
||||
|
list($excClass, $excMsg, $excData) = $this->exceptionBuild(); |
||||
|
throw new $excClass($excMsg, $excData); |
||||
|
} |
||||
|
$changes = $this->st->rowCount(); |
||||
|
try { |
||||
|
$lastId = 0; |
||||
|
$lastId = $this->db->lastInsertId(); |
||||
|
} catch (\PDOException $e) { // @codeCoverageIgnore |
||||
|
} |
||||
|
return new PDOResult($this->st, [$changes, $lastId]); |
||||
|
} |
||||
|
|
||||
|
protected function bindValue($value, string $type, int $position): bool { |
||||
|
return $this->st->bindValue($position, $value, is_null($value) ? \PDO::PARAM_NULL : self::BINDINGS[$type]); |
||||
|
} |
||||
|
} |
@ -0,0 +1,46 @@ |
|||||
|
<?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; |
||||
|
|
||||
|
use JKingWeb\Arsse\Arsse; |
||||
|
use JKingWeb\Arsse\Db\Exception; |
||||
|
use JKingWeb\Arsse\Db\ExceptionInput; |
||||
|
use JKingWeb\Arsse\Db\ExceptionTimeout; |
||||
|
|
||||
|
class PDODriver extends Driver { |
||||
|
use \JKingWeb\Arsse\Db\PDODriver; |
||||
|
|
||||
|
protected $db; |
||||
|
|
||||
|
public static function requirementsMet(): bool { |
||||
|
return class_exists("PDO") && in_array("sqlite", \PDO::getAvailableDrivers()); |
||||
|
} |
||||
|
|
||||
|
protected function makeConnection(string $file, string $key) { |
||||
|
$this->db = new \PDO("sqlite:".$file, "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]); |
||||
|
} |
||||
|
|
||||
|
public function __destruct() { |
||||
|
unset($this->db); |
||||
|
} |
||||
|
|
||||
|
/** @codeCoverageIgnore */ |
||||
|
public static function create(): \JKingWeb\Arsse\Db\Driver { |
||||
|
if (self::requirementsMet()) { |
||||
|
return new self; |
||||
|
} elseif (Driver::requirementsMet()) { |
||||
|
return new Driver; |
||||
|
} else { |
||||
|
throw new Exception("extMissing", self::driverName()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static function driverName(): string { |
||||
|
return Arsse::$lang->msg("Driver.Db.SQLite3PDO.Name"); |
||||
|
} |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestArticle extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesArticle; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestCleanup extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesCleanup; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestFeed extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesFeed; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestFolder extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesFolder; |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
<?php |
||||
|
declare(strict_types=1); |
||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestLabel extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesLabel; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestMeta extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesMeta; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestMiscellany extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesMiscellany; |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
<?php |
||||
|
declare(strict_types=1); |
||||
|
namespace JKingWeb\Arsse\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestSession extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesSession; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestSubscription extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesSubscription; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO\Database; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Database<extended> */ |
||||
|
class TestUser extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
use \JKingWeb\Arsse\Test\Database\Setup; |
||||
|
use \JKingWeb\Arsse\Test\Database\DriverSQLite3PDO; |
||||
|
use \JKingWeb\Arsse\Test\Database\SeriesUser; |
||||
|
} |
@ -0,0 +1,196 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO; |
||||
|
|
||||
|
use JKingWeb\Arsse\Arsse; |
||||
|
use JKingWeb\Arsse\Conf; |
||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver as Driver; |
||||
|
use org\bovigo\vfs\vfsStream; |
||||
|
use Phake; |
||||
|
|
||||
|
/** |
||||
|
* @covers \JKingWeb\Arsse\Db\SQLite3\PDODriver<extended> |
||||
|
* @covers \JKingWeb\Arsse\Db\PDODriver |
||||
|
* @covers \JKingWeb\Arsse\Db\PDOError */ |
||||
|
class TestCreation extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
protected $data; |
||||
|
protected $drv; |
||||
|
protected $ch; |
||||
|
|
||||
|
public function setUp() { |
||||
|
if (!Driver::requirementsMet()) { |
||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded"); |
||||
|
} |
||||
|
$this->clearData(); |
||||
|
// test files |
||||
|
$this->files = [ |
||||
|
// cannot create files |
||||
|
'Cmain' => [], |
||||
|
'Cshm' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
], |
||||
|
'Cwal' => [ |
||||
|
'arsse.db' => "", |
||||
|
], |
||||
|
// cannot write to files |
||||
|
'Wmain' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
'Wwal' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
'Wshm' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
// cannot read from files |
||||
|
'Rmain' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
'Rwal' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
'Rshm' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
// can neither read from or write to files |
||||
|
'Amain' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
'Awal' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
'Ashm' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
// non-filesystem errors |
||||
|
'corrupt' => [ |
||||
|
'arsse.db' => "", |
||||
|
'arsse.db-wal' => "", |
||||
|
'arsse.db-shm' => "", |
||||
|
], |
||||
|
]; |
||||
|
$vfs = vfsStream::setup("dbtest", 0777, $this->files); |
||||
|
$this->path = $path = $vfs->url()."/"; |
||||
|
// set up access blocks |
||||
|
chmod($path."Cmain", 0555); |
||||
|
chmod($path."Cwal", 0555); |
||||
|
chmod($path."Cshm", 0555); |
||||
|
chmod($path."Rmain/arsse.db", 0333); |
||||
|
chmod($path."Rwal/arsse.db-wal", 0333); |
||||
|
chmod($path."Rshm/arsse.db-shm", 0333); |
||||
|
chmod($path."Wmain/arsse.db", 0555); |
||||
|
chmod($path."Wwal/arsse.db-wal", 0555); |
||||
|
chmod($path."Wshm/arsse.db-shm", 0555); |
||||
|
chmod($path."Amain/arsse.db", 0111); |
||||
|
chmod($path."Awal/arsse.db-wal", 0111); |
||||
|
chmod($path."Ashm/arsse.db-shm", 0111); |
||||
|
// set up configuration |
||||
|
Arsse::$conf = new Conf(); |
||||
|
Arsse::$conf->dbSQLite3File = ":memory:"; |
||||
|
} |
||||
|
|
||||
|
public function tearDown() { |
||||
|
$this->clearData(); |
||||
|
} |
||||
|
|
||||
|
public function testFailToCreateDatabase() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Cmain/arsse.db"; |
||||
|
$this->assertException("fileUncreatable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToCreateJournal() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Cwal/arsse.db"; |
||||
|
$this->assertException("fileUncreatable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToCreateSharedMmeory() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Cshm/arsse.db"; |
||||
|
$this->assertException("fileUncreatable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToReadDatabase() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Rmain/arsse.db"; |
||||
|
$this->assertException("fileUnreadable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToReadJournal() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Rwal/arsse.db"; |
||||
|
$this->assertException("fileUnreadable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToReadSharedMmeory() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Rshm/arsse.db"; |
||||
|
$this->assertException("fileUnreadable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToWriteToDatabase() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Wmain/arsse.db"; |
||||
|
$this->assertException("fileUnwritable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToWriteToJournal() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Wwal/arsse.db"; |
||||
|
$this->assertException("fileUnwritable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToWriteToSharedMmeory() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Wshm/arsse.db"; |
||||
|
$this->assertException("fileUnwritable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToAccessDatabase() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Amain/arsse.db"; |
||||
|
$this->assertException("fileUnusable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToAccessJournal() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Awal/arsse.db"; |
||||
|
$this->assertException("fileUnusable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testFailToAccessSharedMmeory() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."Ashm/arsse.db"; |
||||
|
$this->assertException("fileUnusable", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
|
||||
|
public function testAssumeDatabaseCorruption() { |
||||
|
Arsse::$conf->dbSQLite3File = $this->path."corrupt/arsse.db"; |
||||
|
$this->assertException("fileCorrupt", "Db"); |
||||
|
new Driver; |
||||
|
} |
||||
|
} |
@ -0,0 +1,344 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO; |
||||
|
|
||||
|
use JKingWeb\Arsse\Arsse; |
||||
|
use JKingWeb\Arsse\Conf; |
||||
|
use JKingWeb\Arsse\Database; |
||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver; |
||||
|
use JKingWeb\Arsse\Db\Result; |
||||
|
use JKingWeb\Arsse\Db\Statement; |
||||
|
|
||||
|
/** |
||||
|
* @covers \JKingWeb\Arsse\Db\SQLite3\PDODriver<extended> |
||||
|
* @covers \JKingWeb\Arsse\Db\PDODriver |
||||
|
* @covers \JKingWeb\Arsse\Db\PDOError */ |
||||
|
class TestDriver extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
protected $data; |
||||
|
protected $drv; |
||||
|
protected $ch; |
||||
|
|
||||
|
public function setUp() { |
||||
|
if (!PDODriver::requirementsMet()) { |
||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded"); |
||||
|
} |
||||
|
$this->clearData(); |
||||
|
$conf = new Conf(); |
||||
|
Arsse::$conf = $conf; |
||||
|
$conf->dbDriver = PDODriver::class; |
||||
|
$conf->dbSQLite3Timeout = 0; |
||||
|
$conf->dbSQLite3File = tempnam(sys_get_temp_dir(), 'ook'); |
||||
|
$this->drv = new PDODriver(); |
||||
|
$this->ch = new \PDO("sqlite:".Arsse::$conf->dbSQLite3File, "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]); |
||||
|
} |
||||
|
|
||||
|
public function tearDown() { |
||||
|
unset($this->drv); |
||||
|
unset($this->ch); |
||||
|
if (isset(Arsse::$conf)) { |
||||
|
unlink(Arsse::$conf->dbSQLite3File); |
||||
|
} |
||||
|
$this->clearData(); |
||||
|
} |
||||
|
|
||||
|
public function testFetchDriverName() { |
||||
|
$class = Arsse::$conf->dbDriver; |
||||
|
$this->assertTrue(strlen($class::driverName()) > 0); |
||||
|
} |
||||
|
|
||||
|
public function testCheckCharacterSetAcceptability() { |
||||
|
$this->assertTrue($this->drv->charsetAcceptable()); |
||||
|
} |
||||
|
|
||||
|
public function testExecAValidStatement() { |
||||
|
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key)")); |
||||
|
} |
||||
|
|
||||
|
public function testExecAnInvalidStatement() { |
||||
|
$this->assertException("engineErrorGeneral", "Db"); |
||||
|
$this->drv->exec("And the meek shall inherit the earth..."); |
||||
|
} |
||||
|
|
||||
|
public function testExecMultipleStatements() { |
||||
|
$this->assertTrue($this->drv->exec("CREATE TABLE test(id integer primary key); INSERT INTO test(id) values(2112)")); |
||||
|
$this->assertEquals(2112, $this->ch->query("SELECT id from test")->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testExecTimeout() { |
||||
|
$this->ch->exec("BEGIN EXCLUSIVE TRANSACTION"); |
||||
|
$this->assertException("general", "Db", "ExceptionTimeout"); |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
} |
||||
|
|
||||
|
public function testExecConstraintViolation() { |
||||
|
$this->drv->exec("CREATE TABLE test(id integer not null)"); |
||||
|
$this->assertException("constraintViolation", "Db", "ExceptionInput"); |
||||
|
$this->drv->exec("INSERT INTO test(id) values(null)"); |
||||
|
} |
||||
|
|
||||
|
public function testExecTypeViolation() { |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$this->assertException("typeViolation", "Db", "ExceptionInput"); |
||||
|
$this->drv->exec("INSERT INTO test(id) values('ook')"); |
||||
|
} |
||||
|
|
||||
|
public function testMakeAValidQuery() { |
||||
|
$this->assertInstanceOf(Result::class, $this->drv->query("SELECT 1")); |
||||
|
} |
||||
|
|
||||
|
public function testMakeAnInvalidQuery() { |
||||
|
$this->assertException("engineErrorGeneral", "Db"); |
||||
|
$this->drv->query("Apollo was astonished; Dionysus thought me mad"); |
||||
|
} |
||||
|
|
||||
|
public function testQueryTimeout() { |
||||
|
$this->ch->exec("BEGIN EXCLUSIVE TRANSACTION"); |
||||
|
$this->assertException("general", "Db", "ExceptionTimeout"); |
||||
|
$this->drv->query("CREATE TABLE test(id integer primary key)"); |
||||
|
} |
||||
|
|
||||
|
public function testQueryConstraintViolation() { |
||||
|
$this->drv->exec("CREATE TABLE test(id integer not null)"); |
||||
|
$this->assertException("constraintViolation", "Db", "ExceptionInput"); |
||||
|
$this->drv->query("INSERT INTO test(id) values(null)"); |
||||
|
} |
||||
|
|
||||
|
public function testQueryTypeViolation() { |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$this->assertException("typeViolation", "Db", "ExceptionInput"); |
||||
|
$this->drv->query("INSERT INTO test(id) values('ook')"); |
||||
|
} |
||||
|
|
||||
|
public function testPrepareAValidQuery() { |
||||
|
$s = $this->drv->prepare("SELECT ?, ?", "int", "int"); |
||||
|
$this->assertInstanceOf(Statement::class, $s); |
||||
|
} |
||||
|
|
||||
|
public function testPrepareAnInvalidQuery() { |
||||
|
$this->assertException("engineErrorGeneral", "Db"); |
||||
|
$s = $this->drv->prepare("This is an invalid query", "int", "int"); |
||||
|
} |
||||
|
|
||||
|
public function testCreateASavepoint() { |
||||
|
$this->assertEquals(1, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(2, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(3, $this->drv->savepointCreate()); |
||||
|
} |
||||
|
|
||||
|
public function testReleaseASavepoint() { |
||||
|
$this->assertEquals(1, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(true, $this->drv->savepointRelease()); |
||||
|
$this->assertException("savepointInvalid", "Db"); |
||||
|
$this->drv->savepointRelease(); |
||||
|
} |
||||
|
|
||||
|
public function testUndoASavepoint() { |
||||
|
$this->assertEquals(1, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(true, $this->drv->savepointUndo()); |
||||
|
$this->assertException("savepointInvalid", "Db"); |
||||
|
$this->drv->savepointUndo(); |
||||
|
} |
||||
|
|
||||
|
public function testManipulateSavepoints() { |
||||
|
$this->assertEquals(1, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(2, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(3, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(4, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(5, $this->drv->savepointCreate()); |
||||
|
$this->assertTrue($this->drv->savepointUndo(3)); |
||||
|
$this->assertFalse($this->drv->savepointRelease(4)); |
||||
|
$this->assertEquals(6, $this->drv->savepointCreate()); |
||||
|
$this->assertFalse($this->drv->savepointRelease(5)); |
||||
|
$this->assertTrue($this->drv->savepointRelease(6)); |
||||
|
$this->assertEquals(3, $this->drv->savepointCreate()); |
||||
|
$this->assertTrue($this->drv->savepointRelease(2)); |
||||
|
$this->assertException("savepointStale", "Db"); |
||||
|
$this->drv->savepointRelease(2); |
||||
|
} |
||||
|
|
||||
|
public function testManipulateSavepointsSomeMore() { |
||||
|
$this->assertEquals(1, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(2, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(3, $this->drv->savepointCreate()); |
||||
|
$this->assertEquals(4, $this->drv->savepointCreate()); |
||||
|
$this->assertTrue($this->drv->savepointRelease(2)); |
||||
|
$this->assertFalse($this->drv->savepointUndo(3)); |
||||
|
$this->assertException("savepointStale", "Db"); |
||||
|
$this->drv->savepointUndo(2); |
||||
|
} |
||||
|
|
||||
|
public function testBeginATransaction() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testCommitATransaction() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr->commit(); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(1, $this->ch->query($select)->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testRollbackATransaction() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr->rollback(); |
||||
|
$this->assertEquals(0, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testBeginChainedTransactions() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr1 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testCommitChainedTransactions() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr1 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2->commit(); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr1->commit(); |
||||
|
$this->assertEquals(2, $this->ch->query($select)->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testCommitChainedTransactionsOutOfOrder() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr1 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr1->commit(); |
||||
|
$this->assertEquals(2, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2->commit(); |
||||
|
} |
||||
|
|
||||
|
public function testRollbackChainedTransactions() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr1 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2->rollback(); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr1->rollback(); |
||||
|
$this->assertEquals(0, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testRollbackChainedTransactionsOutOfOrder() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr1 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr1->rollback(); |
||||
|
$this->assertEquals(0, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2->rollback(); |
||||
|
$this->assertEquals(0, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testPartiallyRollbackChainedTransactions() { |
||||
|
$select = "SELECT count(*) FROM test"; |
||||
|
$insert = "INSERT INTO test(id) values(null)"; |
||||
|
$this->drv->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$tr1 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2 = $this->drv->begin(); |
||||
|
$this->drv->query($insert); |
||||
|
$this->assertEquals(2, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr2->rollback(); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(0, $this->ch->query($select)->fetchColumn()); |
||||
|
$tr1->commit(); |
||||
|
$this->assertEquals(1, $this->drv->query($select)->getValue()); |
||||
|
$this->assertEquals(1, $this->ch->query($select)->fetchColumn()); |
||||
|
} |
||||
|
|
||||
|
public function testFetchSchemaVersion() { |
||||
|
$this->assertSame(0, $this->drv->schemaVersion()); |
||||
|
$this->drv->exec("PRAGMA user_version=1"); |
||||
|
$this->assertSame(1, $this->drv->schemaVersion()); |
||||
|
$this->drv->exec("PRAGMA user_version=2"); |
||||
|
$this->assertSame(2, $this->drv->schemaVersion()); |
||||
|
} |
||||
|
|
||||
|
public function testLockTheDatabase() { |
||||
|
$this->drv->savepointCreate(true); |
||||
|
$this->ch->exec("PRAGMA busy_timeout = 0"); |
||||
|
$this->assertException(); |
||||
|
$this->ch->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
} |
||||
|
|
||||
|
public function testUnlockTheDatabase() { |
||||
|
$this->drv->savepointCreate(true); |
||||
|
$this->drv->savepointRelease(); |
||||
|
$this->drv->savepointCreate(true); |
||||
|
$this->drv->savepointUndo(); |
||||
|
$this->assertSame(0, $this->ch->exec("CREATE TABLE test(id integer primary key)")); |
||||
|
} |
||||
|
} |
@ -0,0 +1,108 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO; |
||||
|
|
||||
|
use JKingWeb\Arsse\Db\Result; |
||||
|
use JKingWeb\Arsse\Db\PDOResult; |
||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\Db\PDOResult<extended> */ |
||||
|
class TestResult extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
protected $c; |
||||
|
|
||||
|
public function setUp() { |
||||
|
if (!PDODriver::requirementsMet()) { |
||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded"); |
||||
|
} |
||||
|
$this->clearData(); |
||||
|
$c = new \PDO("sqlite::memory:", "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]); |
||||
|
$this->c = $c; |
||||
|
} |
||||
|
|
||||
|
public function tearDown() { |
||||
|
unset($this->c); |
||||
|
$this->clearData(); |
||||
|
} |
||||
|
|
||||
|
public function testConstructResult() { |
||||
|
$set = $this->c->query("SELECT 1"); |
||||
|
$this->assertInstanceOf(Result::class, new PDOResult($set)); |
||||
|
} |
||||
|
|
||||
|
public function testGetChangeCountAndLastInsertId() { |
||||
|
$this->c->query("CREATE TABLE test(col)"); |
||||
|
$set = $this->c->query("INSERT INTO test(col) values(1)"); |
||||
|
$rows = $set->rowCount(); |
||||
|
$id = $this->c->lastInsertID(); |
||||
|
$r = new PDOResult($set, [$rows,$id]); |
||||
|
$this->assertSame((int) $rows, $r->changes()); |
||||
|
$this->assertSame((int) $id, $r->lastId()); |
||||
|
} |
||||
|
|
||||
|
public function testIterateOverResults() { |
||||
|
$set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col"); |
||||
|
$rows = []; |
||||
|
foreach (new PDOResult($set) as $index => $row) { |
||||
|
$rows[$index] = $row['col']; |
||||
|
} |
||||
|
$this->assertSame([0 => "1", 1 => "2", 2 => "3"], $rows); |
||||
|
} |
||||
|
|
||||
|
public function testIterateOverResultsTwice() { |
||||
|
$set = $this->c->query("SELECT 1 as col union select 2 as col union select 3 as col"); |
||||
|
$rows = []; |
||||
|
$test = new PDOResult($set); |
||||
|
foreach ($test as $row) { |
||||
|
$rows[] = $row['col']; |
||||
|
} |
||||
|
$this->assertSame(["1","2","3"], $rows); |
||||
|
$this->assertException("resultReused", "Db"); |
||||
|
foreach ($test as $row) { |
||||
|
$rows[] = $row['col']; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function testGetSingleValues() { |
||||
|
$set = $this->c->query("SELECT 1867 as year union select 1970 as year union select 2112 as year"); |
||||
|
$test = new PDOResult($set); |
||||
|
$this->assertEquals(1867, $test->getValue()); |
||||
|
$this->assertEquals(1970, $test->getValue()); |
||||
|
$this->assertEquals(2112, $test->getValue()); |
||||
|
$this->assertSame(null, $test->getValue()); |
||||
|
} |
||||
|
|
||||
|
public function testGetFirstValuesOnly() { |
||||
|
$set = $this->c->query("SELECT 1867 as year, 19 as century union select 1970 as year, 20 as century union select 2112 as year, 22 as century"); |
||||
|
$test = new PDOResult($set); |
||||
|
$this->assertEquals(1867, $test->getValue()); |
||||
|
$this->assertEquals(1970, $test->getValue()); |
||||
|
$this->assertEquals(2112, $test->getValue()); |
||||
|
$this->assertSame(null, $test->getValue()); |
||||
|
} |
||||
|
|
||||
|
public function testGetRows() { |
||||
|
$set = $this->c->query("SELECT '2112' as album, '2112' as track union select 'Clockwork Angels' as album, 'The Wreckers' as track"); |
||||
|
$rows = [ |
||||
|
['album' => '2112', 'track' => '2112'], |
||||
|
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'], |
||||
|
]; |
||||
|
$test = new PDOResult($set); |
||||
|
$this->assertEquals($rows[0], $test->getRow()); |
||||
|
$this->assertEquals($rows[1], $test->getRow()); |
||||
|
$this->assertSame(null, $test->getRow()); |
||||
|
} |
||||
|
|
||||
|
public function testGetAllRows() { |
||||
|
$set = $this->c->query("SELECT '2112' as album, '2112' as track union select 'Clockwork Angels' as album, 'The Wreckers' as track"); |
||||
|
$rows = [ |
||||
|
['album' => '2112', 'track' => '2112'], |
||||
|
['album' => 'Clockwork Angels', 'track' => 'The Wreckers'], |
||||
|
]; |
||||
|
$test = new PDOResult($set); |
||||
|
$this->assertEquals($rows, $test->getAll()); |
||||
|
} |
||||
|
} |
@ -0,0 +1,313 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO; |
||||
|
|
||||
|
use JKingWeb\Arsse\Db\Statement; |
||||
|
use JKingWeb\Arsse\Db\PDOStatement; |
||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver; |
||||
|
|
||||
|
/** |
||||
|
* @covers \JKingWeb\Arsse\Db\PDOStatement<extended> |
||||
|
* @covers \JKingWeb\Arsse\Db\PDOError */ |
||||
|
class TestStatement extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
protected $c; |
||||
|
protected static $imp = \JKingWeb\Arsse\Db\PDOStatement::class; |
||||
|
|
||||
|
public function setUp() { |
||||
|
if (!PDODriver::requirementsMet()) { |
||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded"); |
||||
|
} |
||||
|
$this->clearData(); |
||||
|
$c = new \PDO("sqlite::memory:", "", "", [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]); |
||||
|
$this->c = $c; |
||||
|
} |
||||
|
|
||||
|
public function tearDown() { |
||||
|
unset($this->c); |
||||
|
$this->clearData(); |
||||
|
} |
||||
|
|
||||
|
protected function checkBinding($input, array $expectations, bool $strict = false) { |
||||
|
$nativeStatement = $this->c->prepare("SELECT ? as value"); |
||||
|
$s = new self::$imp($this->c, $nativeStatement); |
||||
|
$types = array_unique(Statement::TYPES); |
||||
|
foreach ($types as $type) { |
||||
|
$s->retypeArray([$strict ? "strict $type" : $type]); |
||||
|
$val = $s->runArray([$input])->getRow()['value']; |
||||
|
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison."); |
||||
|
$s->retype(...[$strict ? "strict $type" : $type]); |
||||
|
$val = $s->run(...[$input])->getRow()['value']; |
||||
|
$this->assertSame($expectations[$type], $val, "Binding from type $type failed comparison."); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** @dataProvider provideBindings */ |
||||
|
public function testBindATypedValue($value, $type, $exp) { |
||||
|
$typeStr = "'".str_replace("'", "''", $type)."'"; |
||||
|
$nativeStatement = $this->c->prepare( |
||||
|
"SELECT ( |
||||
|
(CASE WHEN substr($typeStr, 0, 7) <> 'strict ' then null else 1 end) is null |
||||
|
and ? is null |
||||
|
) or ( |
||||
|
$exp = ? |
||||
|
) as pass" |
||||
|
); |
||||
|
$s = new self::$imp($this->c, $nativeStatement); |
||||
|
$s->retype(...[$type, $type]); |
||||
|
$act = (bool) $s->run(...[$value, $value])->getRow()['pass']; |
||||
|
$this->assertTrue($act); |
||||
|
} |
||||
|
|
||||
|
public function provideBindings() { |
||||
|
$dateMutable = new \DateTime("Noon Today", new \DateTimezone("America/Toronto")); |
||||
|
$dateImmutable = new \DateTimeImmutable("Noon Today", new \DateTimezone("America/Toronto")); |
||||
|
$dateUTC = new \DateTime("@".$dateMutable->getTimestamp(), new \DateTimezone("UTC")); |
||||
|
return [ |
||||
|
/* input, type, expected binding as SQL fragment */ |
||||
|
[null, "integer", "null"], |
||||
|
[null, "float", "null"], |
||||
|
[null, "string", "null"], |
||||
|
[null, "binary", "null"], |
||||
|
[null, "datetime", "null"], |
||||
|
[null, "boolean", "null"], |
||||
|
[null, "strict integer", "0"], |
||||
|
[null, "strict float", "'0'"], |
||||
|
[null, "strict string", "''"], |
||||
|
[null, "strict binary", "x''"], |
||||
|
[null, "strict datetime", "'1970-01-01 00:00:00'"], |
||||
|
[null, "strict boolean", "0"], |
||||
|
// true |
||||
|
[true, "integer", "1"], |
||||
|
[true, "float", "'1'"], |
||||
|
[true, "string", "'1'"], |
||||
|
[true, "binary", "x'31'"], |
||||
|
[true, "datetime", "null"], |
||||
|
[true, "boolean", "1"], |
||||
|
[true, "strict integer", "1"], |
||||
|
[true, "strict float", "'1'"], |
||||
|
[true, "strict string", "'1'"], |
||||
|
[true, "strict binary", "x'31'"], |
||||
|
[true, "strict datetime", "'1970-01-01 00:00:00'"], |
||||
|
[true, "strict boolean", "1"], |
||||
|
// false |
||||
|
[false, "integer", "0"], |
||||
|
[false, "float", "'0'"], |
||||
|
[false, "string", "''"], |
||||
|
[false, "binary", "x''"], |
||||
|
[false, "datetime", "null"], |
||||
|
[false, "boolean", "0"], |
||||
|
[false, "strict integer", "0"], |
||||
|
[false, "strict float", "'0'"], |
||||
|
[false, "strict string", "''"], |
||||
|
[false, "strict binary", "x''"], |
||||
|
[false, "strict datetime", "'1970-01-01 00:00:00'"], |
||||
|
[false, "strict boolean", "0"], |
||||
|
// integer |
||||
|
[2112, "integer", "2112"], |
||||
|
[2112, "float", "'2112'"], |
||||
|
[2112, "string", "'2112'"], |
||||
|
[2112, "binary", "x'32313132'"], |
||||
|
[2112, "datetime", "'1970-01-01 00:35:12'"], |
||||
|
[2112, "boolean", "1"], |
||||
|
[2112, "strict integer", "2112"], |
||||
|
[2112, "strict float", "'2112'"], |
||||
|
[2112, "strict string", "'2112'"], |
||||
|
[2112, "strict binary", "x'32313132'"], |
||||
|
[2112, "strict datetime", "'1970-01-01 00:35:12'"], |
||||
|
[2112, "strict boolean", "1"], |
||||
|
// integer zero |
||||
|
[0, "integer", "0"], |
||||
|
[0, "float", "'0'"], |
||||
|
[0, "string", "'0'"], |
||||
|
[0, "binary", "x'30'"], |
||||
|
[0, "datetime", "'1970-01-01 00:00:00'"], |
||||
|
[0, "boolean", "0"], |
||||
|
[0, "strict integer", "0"], |
||||
|
[0, "strict float", "'0'"], |
||||
|
[0, "strict string", "'0'"], |
||||
|
[0, "strict binary", "x'30'"], |
||||
|
[0, "strict datetime", "'1970-01-01 00:00:00'"], |
||||
|
[0, "strict boolean", "0"], |
||||
|
// float |
||||
|
[2112.5, "integer", "2112"], |
||||
|
[2112.5, "float", "'2112.5'"], |
||||
|
[2112.5, "string", "'2112.5'"], |
||||
|
[2112.5, "binary", "x'323131322e35'"], |
||||
|
[2112.5, "datetime", "'1970-01-01 00:35:12'"], |
||||
|
[2112.5, "boolean", "1"], |
||||
|
[2112.5, "strict integer", "2112"], |
||||
|
[2112.5, "strict float", "'2112.5'"], |
||||
|
[2112.5, "strict string", "'2112.5'"], |
||||
|
[2112.5, "strict binary", "x'323131322e35'"], |
||||
|
[2112.5, "strict datetime", "'1970-01-01 00:35:12'"], |
||||
|
[2112.5, "strict boolean", "1"], |
||||
|
// float zero |
||||
|
[0.0, "integer", "0"], |
||||
|
[0.0, "float", "'0'"], |
||||
|
[0.0, "string", "'0'"], |
||||
|
[0.0, "binary", "x'30'"], |
||||
|
[0.0, "datetime", "'1970-01-01 00:00:00'"], |
||||
|
[0.0, "boolean", "0"], |
||||
|
[0.0, "strict integer", "0"], |
||||
|
[0.0, "strict float", "'0'"], |
||||
|
[0.0, "strict string", "'0'"], |
||||
|
[0.0, "strict binary", "x'30'"], |
||||
|
[0.0, "strict datetime", "'1970-01-01 00:00:00'"], |
||||
|
[0.0, "strict boolean", "0"], |
||||
|
// ASCII string |
||||
|
["Random string", "integer", "0"], |
||||
|
["Random string", "float", "'0'"], |
||||
|
["Random string", "string", "'Random string'"], |
||||
|
["Random string", "binary", "x'52616e646f6d20737472696e67'"], |
||||
|
["Random string", "datetime", "null"], |
||||
|
["Random string", "boolean", "1"], |
||||
|
["Random string", "strict integer", "0"], |
||||
|
["Random string", "strict float", "'0'"], |
||||
|
["Random string", "strict string", "'Random string'"], |
||||
|
["Random string", "strict binary", "x'52616e646f6d20737472696e67'"], |
||||
|
["Random string", "strict datetime", "'1970-01-01 00:00:00'"], |
||||
|
["Random string", "strict boolean", "1"], |
||||
|
// UTF-8 string |
||||
|
["é", "integer", "0"], |
||||
|
["é", "float", "'0'"], |
||||
|
["é", "string", "char(233)"], |
||||
|
["é", "binary", "x'c3a9'"], |
||||
|
["é", "datetime", "null"], |
||||
|
["é", "boolean", "1"], |
||||
|
["é", "strict integer", "0"], |
||||
|
["é", "strict float", "'0'"], |
||||
|
["é", "strict string", "char(233)"], |
||||
|
["é", "strict binary", "x'c3a9'"], |
||||
|
["é", "strict datetime", "'1970-01-01 00:00:00'"], |
||||
|
["é", "strict boolean", "1"], |
||||
|
// binary string |
||||
|
[chr(233).chr(233), "integer", "0"], |
||||
|
[chr(233).chr(233), "float", "'0'"], |
||||
|
[chr(233).chr(233), "string", "'".chr(233).chr(233)."'"], |
||||
|
[chr(233).chr(233), "binary", "x'e9e9'"], |
||||
|
[chr(233).chr(233), "datetime", "null"], |
||||
|
[chr(233).chr(233), "boolean", "1"], |
||||
|
[chr(233).chr(233), "strict integer", "0"], |
||||
|
[chr(233).chr(233), "strict float", "'0'"], |
||||
|
[chr(233).chr(233), "strict string", "'".chr(233).chr(233)."'"], |
||||
|
[chr(233).chr(233), "strict binary", "x'e9e9'"], |
||||
|
[chr(233).chr(233), "strict datetime", "'1970-01-01 00:00:00'"], |
||||
|
[chr(233).chr(233), "strict boolean", "1"], |
||||
|
// ISO 8601 date string |
||||
|
["2017-01-09T13:11:17", "integer", "0"], |
||||
|
["2017-01-09T13:11:17", "float", "'0'"], |
||||
|
["2017-01-09T13:11:17", "string", "'2017-01-09T13:11:17'"], |
||||
|
["2017-01-09T13:11:17", "binary", "x'323031372d30312d30395431333a31313a3137'"], |
||||
|
["2017-01-09T13:11:17", "datetime", "'2017-01-09 13:11:17'"], |
||||
|
["2017-01-09T13:11:17", "boolean", "1"], |
||||
|
["2017-01-09T13:11:17", "strict integer", "0"], |
||||
|
["2017-01-09T13:11:17", "strict float", "'0'"], |
||||
|
["2017-01-09T13:11:17", "strict string", "'2017-01-09T13:11:17'"], |
||||
|
["2017-01-09T13:11:17", "strict binary", "x'323031372d30312d30395431333a31313a3137'"], |
||||
|
["2017-01-09T13:11:17", "strict datetime", "'2017-01-09 13:11:17'"], |
||||
|
["2017-01-09T13:11:17", "strict boolean", "1"], |
||||
|
// arbitrary date string |
||||
|
["Today", "integer", "0"], |
||||
|
["Today", "float", "'0'"], |
||||
|
["Today", "string", "'Today'"], |
||||
|
["Today", "binary", "x'546f646179'"], |
||||
|
["Today", "datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"], |
||||
|
["Today", "boolean", "1"], |
||||
|
["Today", "strict integer", "0"], |
||||
|
["Today", "strict float", "'0'"], |
||||
|
["Today", "strict string", "'Today'"], |
||||
|
["Today", "strict binary", "x'546f646179'"], |
||||
|
["Today", "strict datetime", "'".date_create("Today", new \DateTimezone("UTC"))->format("Y-m-d H:i:s")."'"], |
||||
|
["Today", "strict boolean", "1"], |
||||
|
// mutable date object |
||||
|
[$dateMutable, "integer", $dateUTC->getTimestamp()], |
||||
|
[$dateMutable, "float", "'".$dateUTC->getTimestamp()."'"], |
||||
|
[$dateMutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"], |
||||
|
[$dateMutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"], |
||||
|
[$dateMutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"], |
||||
|
[$dateMutable, "boolean", "1"], |
||||
|
[$dateMutable, "strict integer", $dateUTC->getTimestamp()], |
||||
|
[$dateMutable, "strict float", "'".$dateUTC->getTimestamp()."'"], |
||||
|
[$dateMutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"], |
||||
|
[$dateMutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"], |
||||
|
[$dateMutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"], |
||||
|
[$dateMutable, "strict boolean", "1"], |
||||
|
// immutable date object |
||||
|
[$dateImmutable, "integer", $dateUTC->getTimestamp()], |
||||
|
[$dateImmutable, "float", "'".$dateUTC->getTimestamp()."'"], |
||||
|
[$dateImmutable, "string", "'".$dateUTC->format("Y-m-d H:i:s")."'"], |
||||
|
[$dateImmutable, "binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"], |
||||
|
[$dateImmutable, "datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"], |
||||
|
[$dateImmutable, "boolean", "1"], |
||||
|
[$dateImmutable, "strict integer", $dateUTC->getTimestamp()], |
||||
|
[$dateImmutable, "strict float", "'".$dateUTC->getTimestamp()."'"], |
||||
|
[$dateImmutable, "strict string", "'".$dateUTC->format("Y-m-d H:i:s")."'"], |
||||
|
[$dateImmutable, "strict binary", "x'".bin2hex($dateUTC->format("Y-m-d H:i:s"))."'"], |
||||
|
[$dateImmutable, "strict datetime", "'".$dateUTC->format("Y-m-d H:i:s")."'"], |
||||
|
[$dateImmutable, "strict boolean", "1"], |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
public function testConstructStatement() { |
||||
|
$nativeStatement = $this->c->prepare("SELECT ? as value"); |
||||
|
$this->assertInstanceOf(Statement::class, new PDOStatement($this->c, $nativeStatement)); |
||||
|
} |
||||
|
|
||||
|
public function testBindMissingValue() { |
||||
|
$nativeStatement = $this->c->prepare("SELECT ? as value"); |
||||
|
$s = new self::$imp($this->c, $nativeStatement); |
||||
|
$val = $s->runArray()->getRow()['value']; |
||||
|
$this->assertSame(null, $val); |
||||
|
} |
||||
|
|
||||
|
public function testBindMultipleValues() { |
||||
|
$exp = [ |
||||
|
'one' => "1", |
||||
|
'two' => "2", |
||||
|
]; |
||||
|
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two"); |
||||
|
$s = new self::$imp($this->c, $nativeStatement, ["int", "int"]); |
||||
|
$val = $s->runArray([1,2])->getRow(); |
||||
|
$this->assertSame($exp, $val); |
||||
|
} |
||||
|
|
||||
|
public function testBindRecursively() { |
||||
|
$exp = [ |
||||
|
'one' => "1", |
||||
|
'two' => "2", |
||||
|
'three' => "3", |
||||
|
'four' => "4", |
||||
|
]; |
||||
|
$nativeStatement = $this->c->prepare("SELECT ? as one, ? as two, ? as three, ? as four"); |
||||
|
$s = new self::$imp($this->c, $nativeStatement, ["int", ["int", "int"], "int"]); |
||||
|
$val = $s->runArray([1, [2, 3], 4])->getRow(); |
||||
|
$this->assertSame($exp, $val); |
||||
|
} |
||||
|
|
||||
|
public function testBindWithoutType() { |
||||
|
$nativeStatement = $this->c->prepare("SELECT ? as value"); |
||||
|
$this->assertException("paramTypeMissing", "Db"); |
||||
|
$s = new self::$imp($this->c, $nativeStatement, []); |
||||
|
$s->runArray([1]); |
||||
|
} |
||||
|
|
||||
|
public function testViolateConstraint() { |
||||
|
$this->c->exec("CREATE TABLE test(id integer not null)"); |
||||
|
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)"); |
||||
|
$s = new self::$imp($this->c, $nativeStatement, ["int"]); |
||||
|
$this->assertException("constraintViolation", "Db", "ExceptionInput"); |
||||
|
$s->runArray([null]); |
||||
|
} |
||||
|
|
||||
|
public function testMismatchTypes() { |
||||
|
$this->c->exec("CREATE TABLE test(id integer primary key)"); |
||||
|
$nativeStatement = $this->c->prepare("INSERT INTO test(id) values(?)"); |
||||
|
$s = new self::$imp($this->c, $nativeStatement, ["str"]); |
||||
|
$this->assertException("typeViolation", "Db", "ExceptionInput"); |
||||
|
$s->runArray(['ook']); |
||||
|
} |
||||
|
} |
@ -0,0 +1,125 @@ |
|||||
|
<?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\TestCase\Db\SQLite3PDO; |
||||
|
|
||||
|
use JKingWeb\Arsse\Arsse; |
||||
|
use JKingWeb\Arsse\Conf; |
||||
|
use JKingWeb\Arsse\Database; |
||||
|
use JKingWeb\Arsse\Db\Exception; |
||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver; |
||||
|
use org\bovigo\vfs\vfsStream; |
||||
|
|
||||
|
/** |
||||
|
* @covers \JKingWeb\Arsse\Db\SQLite3\PDODriver<extended> |
||||
|
* @covers \JKingWeb\Arsse\Db\PDOError */ |
||||
|
class TestUpdate extends \JKingWeb\Arsse\Test\AbstractTest { |
||||
|
protected $data; |
||||
|
protected $drv; |
||||
|
protected $vfs; |
||||
|
protected $base; |
||||
|
|
||||
|
const MINIMAL1 = "create table arsse_meta(key text primary key not null, value text); pragma user_version=1"; |
||||
|
const MINIMAL2 = "pragma user_version=2"; |
||||
|
|
||||
|
public function setUp(Conf $conf = null) { |
||||
|
if (!PDODriver::requirementsMet()) { |
||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded"); |
||||
|
} |
||||
|
$this->clearData(); |
||||
|
$this->vfs = vfsStream::setup("schemata", null, ['SQLite3' => []]); |
||||
|
if (!$conf) { |
||||
|
$conf = new Conf(); |
||||
|
} |
||||
|
$conf->dbDriver = PDODriver::class; |
||||
|
$conf->dbSQLite3File = ":memory:"; |
||||
|
Arsse::$conf = $conf; |
||||
|
$this->base = $this->vfs->url(); |
||||
|
$this->path = $this->base."/SQLite3/"; |
||||
|
$this->drv = new PDODriver(); |
||||
|
} |
||||
|
|
||||
|
public function tearDown() { |
||||
|
unset($this->drv); |
||||
|
unset($this->data); |
||||
|
unset($this->vfs); |
||||
|
$this->clearData(); |
||||
|
} |
||||
|
|
||||
|
public function testLoadMissingFile() { |
||||
|
$this->assertException("updateFileMissing", "Db"); |
||||
|
$this->drv->schemaUpdate(1, $this->base); |
||||
|
} |
||||
|
|
||||
|
public function testLoadUnreadableFile() { |
||||
|
touch($this->path."0.sql"); |
||||
|
chmod($this->path."0.sql", 0000); |
||||
|
$this->assertException("updateFileUnreadable", "Db"); |
||||
|
$this->drv->schemaUpdate(1, $this->base); |
||||
|
} |
||||
|
|
||||
|
public function testLoadCorruptFile() { |
||||
|
file_put_contents($this->path."0.sql", "This is a corrupt file"); |
||||
|
$this->assertException("updateFileError", "Db"); |
||||
|
$this->drv->schemaUpdate(1, $this->base); |
||||
|
} |
||||
|
|
||||
|
public function testLoadIncompleteFile() { |
||||
|
file_put_contents($this->path."0.sql", "create table arsse_meta(key text primary key not null, value text);"); |
||||
|
$this->assertException("updateFileIncomplete", "Db"); |
||||
|
$this->drv->schemaUpdate(1, $this->base); |
||||
|
} |
||||
|
|
||||
|
public function testLoadEmptyFile() { |
||||
|
file_put_contents($this->path."0.sql", ""); |
||||
|
$this->assertException("updateFileIncomplete", "Db"); |
||||
|
$this->drv->schemaUpdate(1, $this->base); |
||||
|
} |
||||
|
|
||||
|
public function testLoadCorrectFile() { |
||||
|
file_put_contents($this->path."0.sql", self::MINIMAL1); |
||||
|
$this->drv->schemaUpdate(1, $this->base); |
||||
|
$this->assertEquals(1, $this->drv->schemaVersion()); |
||||
|
} |
||||
|
|
||||
|
public function testPerformPartialUpdate() { |
||||
|
file_put_contents($this->path."0.sql", self::MINIMAL1); |
||||
|
file_put_contents($this->path."1.sql", " "); |
||||
|
$this->assertException("updateFileIncomplete", "Db"); |
||||
|
try { |
||||
|
$this->drv->schemaUpdate(2, $this->base); |
||||
|
} catch (Exception $e) { |
||||
|
$this->assertEquals(1, $this->drv->schemaVersion()); |
||||
|
throw $e; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function testPerformSequentialUpdate() { |
||||
|
file_put_contents($this->path."0.sql", self::MINIMAL1); |
||||
|
file_put_contents($this->path."1.sql", self::MINIMAL2); |
||||
|
$this->drv->schemaUpdate(2, $this->base); |
||||
|
$this->assertEquals(2, $this->drv->schemaVersion()); |
||||
|
} |
||||
|
|
||||
|
public function testPerformActualUpdate() { |
||||
|
$this->drv->schemaUpdate(Database::SCHEMA_VERSION); |
||||
|
$this->assertEquals(Database::SCHEMA_VERSION, $this->drv->schemaVersion()); |
||||
|
} |
||||
|
|
||||
|
public function testDeclineManualUpdate() { |
||||
|
// turn auto-updating off |
||||
|
$conf = new Conf(); |
||||
|
$conf->dbAutoUpdate = false; |
||||
|
$this->setUp($conf); |
||||
|
$this->assertException("updateManual", "Db"); |
||||
|
$this->drv->schemaUpdate(Database::SCHEMA_VERSION); |
||||
|
} |
||||
|
|
||||
|
public function testDeclineDowngrade() { |
||||
|
$this->assertException("updateTooNew", "Db"); |
||||
|
$this->drv->schemaUpdate(-1, $this->base); |
||||
|
} |
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
<?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\TestCase\REST\NextCloudNews\PDO; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\REST\NextCloudNews\V1_2<extended> */ |
||||
|
class TestV1_2 extends \JKingWeb\Arsse\TestCase\REST\NextCloudNews\TestV1_2 { |
||||
|
use \JKingWeb\Arsse\Test\PDOTest; |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
<?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\TestCase\REST\TinyTinyRSS\PDO; |
||||
|
|
||||
|
/** @covers \JKingWeb\Arsse\REST\TinyTinyRSS\API<extended> |
||||
|
* @covers \JKingWeb\Arsse\REST\TinyTinyRSS\Exception */ |
||||
|
class TestAPI extends \JKingWeb\Arsse\TestCase\REST\TinyTinyRSS\TestAPI { |
||||
|
use \JKingWeb\Arsse\Test\PDOTest; |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
<?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\Test\Database; |
||||
|
|
||||
|
use JKingWeb\Arsse\Arsse; |
||||
|
use JKingWeb\Arsse\Db\SQLite3\PDODriver; |
||||
|
|
||||
|
trait DriverSQLite3PDO { |
||||
|
public function setUpDriver() { |
||||
|
if (!PDODriver::requirementsMet()) { |
||||
|
$this->markTestSkipped("PDO-SQLite extension not loaded"); |
||||
|
} |
||||
|
Arsse::$conf->dbSQLite3File = ":memory:"; |
||||
|
$this->drv = new PDODriver(); |
||||
|
} |
||||
|
|
||||
|
public function nextID(string $table): int { |
||||
|
return (int) $this->drv->query("SELECT (case when max(id) then max(id) else 0 end)+1 from $table")->getValue(); |
||||
|
} |
||||
|
} |
@ -1,253 +0,0 @@ |
|||||
<?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\Test\Db; |
|
||||
|
|
||||
use JKingWeb\Arsse\Db\Statement; |
|
||||
|
|
||||
trait BindingTests { |
|
||||
public function testBindNull() { |
|
||||
$input = null; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => null, |
|
||||
"float" => null, |
|
||||
"date" => null, |
|
||||
"time" => null, |
|
||||
"datetime" => null, |
|
||||
"binary" => null, |
|
||||
"string" => null, |
|
||||
"boolean" => null, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
// types may also be strict (e.g. "strict integer") and never pass null to the database; this is useful for NOT NULL columns |
|
||||
// only null input should yield different results, so only this test has different expectations |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 0, |
|
||||
"float" => 0.0, |
|
||||
"date" => gmdate("Y-m-d", 0), |
|
||||
"time" => gmdate("H:i:s", 0), |
|
||||
"datetime" => gmdate("Y-m-d H:i:s", 0), |
|
||||
"binary" => "", |
|
||||
"string" => "", |
|
||||
"boolean" => 0, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindTrue() { |
|
||||
$input = true; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 1, |
|
||||
"float" => 1.0, |
|
||||
"date" => null, |
|
||||
"time" => null, |
|
||||
"datetime" => null, |
|
||||
"binary" => "1", |
|
||||
"string" => "1", |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindFalse() { |
|
||||
$input = false; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 0, |
|
||||
"float" => 0.0, |
|
||||
"date" => null, |
|
||||
"time" => null, |
|
||||
"datetime" => null, |
|
||||
"binary" => "", |
|
||||
"string" => "", |
|
||||
"boolean" => 0, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindInteger() { |
|
||||
$input = 2112; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 2112, |
|
||||
"float" => 2112.0, |
|
||||
"date" => gmdate("Y-m-d", 2112), |
|
||||
"time" => gmdate("H:i:s", 2112), |
|
||||
"datetime" => gmdate("Y-m-d H:i:s", 2112), |
|
||||
"binary" => "2112", |
|
||||
"string" => "2112", |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindIntegerZero() { |
|
||||
$input = 0; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 0, |
|
||||
"float" => 0.0, |
|
||||
"date" => gmdate("Y-m-d", 0), |
|
||||
"time" => gmdate("H:i:s", 0), |
|
||||
"datetime" => gmdate("Y-m-d H:i:s", 0), |
|
||||
"binary" => "0", |
|
||||
"string" => "0", |
|
||||
"boolean" => 0, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindFloat() { |
|
||||
$input = 2112.0; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 2112, |
|
||||
"float" => 2112.0, |
|
||||
"date" => gmdate("Y-m-d", 2112), |
|
||||
"time" => gmdate("H:i:s", 2112), |
|
||||
"datetime" => gmdate("Y-m-d H:i:s", 2112), |
|
||||
"binary" => "2112", |
|
||||
"string" => "2112", |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindFloatZero() { |
|
||||
$input = 0.0; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 0, |
|
||||
"float" => 0.0, |
|
||||
"date" => gmdate("Y-m-d", 0), |
|
||||
"time" => gmdate("H:i:s", 0), |
|
||||
"datetime" => gmdate("Y-m-d H:i:s", 0), |
|
||||
"binary" => "0", |
|
||||
"string" => "0", |
|
||||
"boolean" => 0, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindAsciiString() { |
|
||||
$input = "Random string"; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 0, |
|
||||
"float" => 0.0, |
|
||||
"date" => null, |
|
||||
"time" => null, |
|
||||
"datetime" => null, |
|
||||
"binary" => $input, |
|
||||
"string" => $input, |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindUtf8String() { |
|
||||
$input = "é"; |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 0, |
|
||||
"float" => 0.0, |
|
||||
"date" => null, |
|
||||
"time" => null, |
|
||||
"datetime" => null, |
|
||||
"binary" => $input, |
|
||||
"string" => $input, |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindBinaryString() { |
|
||||
// FIXME: This test may be unreliable; SQLite happily stores invalid UTF-8 text as bytes untouched, but other engines probably don't do this |
|
||||
$input = chr(233); |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 0, |
|
||||
"float" => 0.0, |
|
||||
"date" => null, |
|
||||
"time" => null, |
|
||||
"datetime" => null, |
|
||||
"binary" => $input, |
|
||||
"string" => $input, |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindIso8601DateString() { |
|
||||
$input = "2017-01-09T13:11:17"; |
|
||||
$time = strtotime($input." UTC"); |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 2017, |
|
||||
"float" => 2017.0, |
|
||||
"date" => gmdate("Y-m-d", $time), |
|
||||
"time" => gmdate("H:i:s", $time), |
|
||||
"datetime" => gmdate("Y-m-d H:i:s", $time), |
|
||||
"binary" => $input, |
|
||||
"string" => $input, |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindArbitraryDateString() { |
|
||||
$input = "Today"; |
|
||||
$time = date_create($input, new \DateTimezone("UTC"))->getTimestamp(); |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => 0, |
|
||||
"float" => 0.0, |
|
||||
"date" => gmdate("Y-m-d", $time), |
|
||||
"time" => gmdate("H:i:s", $time), |
|
||||
"datetime" => gmdate("Y-m-d H:i:s", $time), |
|
||||
"binary" => $input, |
|
||||
"string" => $input, |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindMutableDateObject($class = '\DateTime') { |
|
||||
$input = new $class("Noon Today"); |
|
||||
$time = $input->getTimestamp(); |
|
||||
$exp = [ |
|
||||
"null" => null, |
|
||||
"integer" => $time, |
|
||||
"float" => (float) $time, |
|
||||
"date" => gmdate("Y-m-d", $time), |
|
||||
"time" => gmdate("H:i:s", $time), |
|
||||
"datetime" => gmdate("Y-m-d H:i:s", $time), |
|
||||
"binary" => gmdate("Y-m-d H:i:s", $time), |
|
||||
"string" => gmdate("Y-m-d H:i:s", $time), |
|
||||
"boolean" => 1, |
|
||||
]; |
|
||||
$this->checkBinding($input, $exp); |
|
||||
$this->checkBinding($input, $exp, true); |
|
||||
} |
|
||||
|
|
||||
public function testBindImmutableDateObject() { |
|
||||
$this->testBindMutableDateObject('\DateTimeImmutable'); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,23 @@ |
|||||
|
<?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\Test; |
||||
|
|
||||
|
trait PDOTest { |
||||
|
protected function v($value) { |
||||
|
if (!is_array($value)) { |
||||
|
return $value; |
||||
|
} |
||||
|
foreach($value as $k => $v) { |
||||
|
if (is_array($v)) { |
||||
|
$value[$k] = $this->v($v); |
||||
|
} elseif (is_int($v) || is_float($v)) { |
||||
|
$value[$k] = (string) $v; |
||||
|
} |
||||
|
} |
||||
|
return $value; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue