From eeb1818bb5c748a4e07821f073f4375cfb1a8ba5 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Fri, 3 Mar 2017 13:20:26 -0500 Subject: [PATCH] Still more database changes - Restructured tests - Localized driver name for SQLite driver (fixes #37) - Ensured that binding type definitions are required --- lib/AbstractException.php | 1 + lib/Database.php | 5 ++- lib/Db/DriverSQLite3.php | 2 +- lib/Db/StatementSQLite3.php | 6 +-- lib/Lang.php | 2 +- locale/en.php | 6 ++- tests/Db/SQLite3/TestDbStatementSQLite3.php | 44 ++++++++++++++++++--- tests/lib/Db/BindingTests.php | 17 -------- 8 files changed, 53 insertions(+), 30 deletions(-) diff --git a/lib/AbstractException.php b/lib/AbstractException.php index 07f7437..2811f72 100644 --- a/lib/AbstractException.php +++ b/lib/AbstractException.php @@ -23,6 +23,7 @@ abstract class AbstractException extends \Exception { "Db/Exception.fileCorrupt" => 10207, "Db/Exception.paramTypeInvalid" => 10401, "Db/Exception.paramTypeUnknown" => 10402, + "Db/Exception.paramTypeMissing" => 10403, "Db/Update/Exception.tooNew" => 10211, "Db/Update/Exception.fileMissing" => 10212, "Db/Update/Exception.fileUnusable" => 10213, diff --git a/lib/Database.php b/lib/Database.php index 4a5b27d..27b17a5 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -291,7 +291,10 @@ class Database { throw new Feed\Exception($url, $e); } - $this->db->prepare("INSERT INTO newssync_feeds(url,title,favicon,source,updated,modified,etag,username,password) values(?,?,?,?,?,?,?,?,?)", "str", "str", "str", "str", "datetime", "datetime", "str", "str", "str")->run( + $this->db->prepare( + "INSERT INTO newssync_feeds(url,title,favicon,source,updated,modified,etag,username,password) values(?,?,?,?,?,?,?,?,?)", + "str", "str", "str", "str", "datetime", "datetime", "str", "str", "str" + )->run( $url, $feed->title, // Grab the favicon for the Goodfeed; returns an empty string if it cannot find one. diff --git a/lib/Db/DriverSQLite3.php b/lib/Db/DriverSQLite3.php index 8406926..b9b2f48 100644 --- a/lib/Db/DriverSQLite3.php +++ b/lib/Db/DriverSQLite3.php @@ -38,7 +38,7 @@ class DriverSQLite3 extends AbstractDriver { static public function driverName(): string { - return "SQLite 3"; + return Lang::msg("Driver.Db.$name.Name"); } public function schemaVersion(): int { diff --git a/lib/Db/StatementSQLite3.php b/lib/Db/StatementSQLite3.php index 608a988..0052c21 100644 --- a/lib/Db/StatementSQLite3.php +++ b/lib/Db/StatementSQLite3.php @@ -26,7 +26,7 @@ class StatementSQLite3 extends AbstractStatement { } public function __destruct() { - $this->st->close(); + try {$this->st->close();} catch(\Throwable $e) {} unset($this->st); } @@ -49,14 +49,14 @@ class StatementSQLite3 extends AbstractStatement { if(!array_key_exists($this->types[$a], self::BINDINGS)) throw new Exception("paramTypeUnknown", $this->types[$a]); $type = self::BINDINGS[$this->types[$a]]; } else { - $type = \SQLITE3_TEXT; + throw new Exception("paramTypeMissing", $a+1); } // cast value if necessary $values[$a] = $this->cast($values[$a], $this->types[$a]); // re-adjust for null casts if($values[$a]===null) $type = \SQLITE3_NULL; // perform binding - $this->st->bindParam($a+1, $values[$a], $type); + $this->st->bindValue($a+1, $values[$a], $type); } return new ResultSQLite3($this->st->execute(), $this->db->changes(), $this); } diff --git a/lib/Lang.php b/lib/Lang.php index 735a1df..1a6749c 100644 --- a/lib/Lang.php +++ b/lib/Lang.php @@ -6,7 +6,7 @@ use \Webmozart\Glob\Glob; class Lang { const DEFAULT = "en"; // fallback locale const REQUIRED = [ // collection of absolutely required strings to handle pathological errors - 'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in Exception.php', + 'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in AbstractException.php', 'Exception.JKingWeb/NewsSync/Exception.unknown' => 'An unknown error has occurred', 'Exception.JKingWeb/NewsSync/Lang/Exception.defaultFileMissing' => 'Default language file "{0}" missing', 'Exception.JKingWeb/NewsSync/Lang/Exception.fileMissing' => 'Language file "{0}" is not available', diff --git a/locale/en.php b/locale/en.php index e8564d7..26dd596 100644 --- a/locale/en.php +++ b/locale/en.php @@ -1,9 +1,10 @@ 'Internal', + 'Driver.User.Internal.Name' => 'Internal', + 'Driver.Db.SQLite3.Name' => 'SQLite 3', // this should only be encountered in testing (because tests should cover all exceptions!) - 'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in Exception.php', + 'Exception.JKingWeb/NewsSync/Exception.uncoded' => 'The specified exception symbol {0} has no code specified in AbstractException.php', // this should not usually be encountered 'Exception.JKingWeb/NewsSync/Exception.unknown' => 'An unknown error has occurred', @@ -29,6 +30,7 @@ return [ 'Exception.JKingWeb/NewsSync/Db/Exception.fileCorrupt' => 'Database file "{0}" is corrupt or not a valid database', 'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeInvalid' => 'Prepared statement parameter type "{0}" is invalid', 'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeUnknown' => 'Prepared statement parameter type "{0}" is valid, but not implemented', + 'Exception.JKingWeb/NewsSync/Db/Exception.paramTypeMissing' => 'Prepared statement parameter type for parameter #{0} was not specified', 'Exception.JKingWeb/NewsSync/Db/Update/Exception.manual' => '{from_version, select, diff --git a/tests/Db/SQLite3/TestDbStatementSQLite3.php b/tests/Db/SQLite3/TestDbStatementSQLite3.php index 8b6147e..8e48dd9 100644 --- a/tests/Db/SQLite3/TestDbStatementSQLite3.php +++ b/tests/Db/SQLite3/TestDbStatementSQLite3.php @@ -1,32 +1,66 @@ enableExceptions(true); - $s = $c->prepare("SELECT ? as value"); $this->c = $c; - $this->s = $s; } function tearDown() { try {$this->s->close();} catch(\Exception $e) {} $this->c->close(); - unset($this->s); unset($this->c); } + protected function checkBinding($input, array $expectations) { + $nativeStatement = $this->c->prepare("SELECT ? as value"); + $s = new self::$imp($this->c, $nativeStatement); + $types = array_unique(Statement::TYPES); + foreach($types as $type) { + $s->rebindArray([$type]); + $val = $s->runArray([$input])->get()['value']; + $this->assertSame($expectations[$type], $val, "Type $type failed comparison."); + } + } + function testConstructStatement() { - $this->assertInstanceOf(Db\StatementSQLite3::class, new Db\StatementSQLite3($this->c, $this->s)); + $nativeStatement = $this->c->prepare("SELECT ? as value"); + $this->assertInstanceOf(Db\StatementSQLite3::class, new Db\StatementSQLite3($this->c, $nativeStatement)); } + + function testBindMissingValue() { + $nativeStatement = $this->c->prepare("SELECT ? as value"); + $s = new self::$imp($this->c, $nativeStatement); + $val = $s->runArray()->get()['value']; + $this->assertSame(null, $val); + } + + 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])->get(); + $this->assertSame($exp, $val); + } + + function testBindWithoutType() { + $this->assertException("paramTypeMissing", "Db"); + $nativeStatement = $this->c->prepare("SELECT ? as value"); + $s = new self::$imp($this->c, $nativeStatement, []); + $val = $s->runArray([1])->get(); + } } \ No newline at end of file diff --git a/tests/lib/Db/BindingTests.php b/tests/lib/Db/BindingTests.php index c09cee4..9fbeaa4 100644 --- a/tests/lib/Db/BindingTests.php +++ b/tests/lib/Db/BindingTests.php @@ -4,13 +4,6 @@ namespace JKingWeb\NewsSync\Test\Db; use JKingWeb\NewsSync\Db\Statement; trait BindingTests { - - function testBindMissingValue() { - $s = new self::$imp($this->c, $this->s); - $val = $s->runArray()->get()['value']; - $this->assertSame(null, $val); - } - function testBindNull() { $input = null; $exp = [ @@ -226,14 +219,4 @@ trait BindingTests { function testBindImmutableDateObject() { $this->testBindMutableDateObject('\DateTimeImmutable'); } - - protected function checkBinding($input, array $expectations) { - $s = new self::$imp($this->c, $this->s); - $types = array_unique(Statement::TYPES); - foreach($types as $type) { - $s->rebindArray([$type]); - $val = $s->runArray([$input])->get()['value']; - $this->assertSame($expectations[$type], $val, "Type $type failed comparison."); - } - } } \ No newline at end of file