Browse Source

Experimental native MySQL driver

No testing has been performed yet, but changes are extensive enough to
warrant a commit. Of particular note:

- SQL states are enumerated in a separate trait to reduce duplication
- PDOStatement is now an abstract class to avoid duplication of
engine-specific error handling
- Error handling has been cleaned up somewhat
microsub
J. King 5 years ago
parent
commit
c4a41255b0
  1. 6
      lib/Db/AbstractDriver.php
  2. 3
      lib/Db/AbstractStatement.php
  3. 57
      lib/Db/MySQL/Driver.php
  4. 30
      lib/Db/MySQL/ExceptionBuilder.php
  5. 1
      lib/Db/MySQL/PDODriver.php
  6. 15
      lib/Db/MySQL/PDOStatement.php
  7. 51
      lib/Db/MySQL/Result.php
  8. 107
      lib/Db/MySQL/Statement.php
  9. 8
      lib/Db/PDODriver.php
  10. 51
      lib/Db/PDOError.php
  11. 6
      lib/Db/PDOStatement.php
  12. 26
      lib/Db/PostgreSQL/Dispatch.php
  13. 9
      lib/Db/PostgreSQL/Driver.php
  14. 7
      lib/Db/PostgreSQL/PDOStatement.php
  15. 1
      lib/Db/PostgreSQL/Statement.php
  16. 12
      lib/Db/ResultEmpty.php
  17. 31
      lib/Db/SQLState.php
  18. 10
      lib/Db/SQLite3/Driver.php
  19. 22
      lib/Db/SQLite3/ExceptionBuilder.php
  20. 4
      lib/Db/SQLite3/PDODriver.php
  21. 12
      lib/Db/SQLite3/PDOStatement.php
  22. 4
      lib/Db/SQLite3/Statement.php
  23. 2
      tests/lib/DatabaseInformation.php

6
lib/Db/AbstractDriver.php

@ -9,13 +9,15 @@ namespace JKingWeb\Arsse\Db;
use JKingWeb\Arsse\Arsse;
abstract class AbstractDriver implements Driver {
use SQLState;
protected $locked = false;
protected $transDepth = 0;
protected $transStatus = [];
abstract protected function lock(): bool;
abstract protected function unlock(bool $rollback = false): bool;
abstract protected function getError(): string;
abstract protected static function buildEngineException($code, string $msg): array;
public function schemaUpdate(int $to, string $basePath = null): bool {
$ver = $this->schemaVersion();
@ -46,7 +48,7 @@ abstract class AbstractDriver implements Driver {
try {
$this->exec($sql);
} catch (\Throwable $e) {
throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $this->getError()]);
throw new Exception("updateFileError", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a, 'message' => $e->getMessage()]);
}
if ($this->schemaVersion() != $a+1) {
throw new Exception("updateFileIncomplete", ['file' => $file, 'driver_name' => $this->driverName(), 'current' => $a]);

3
lib/Db/AbstractStatement.php

@ -10,12 +10,15 @@ use JKingWeb\Arsse\Misc\Date;
use JKingWeb\Arsse\Misc\ValueInfo;
abstract class AbstractStatement implements Statement {
use SQLState;
protected $types = [];
protected $isNullable = [];
abstract public function runArray(array $values = []): Result;
abstract protected function bindValue($value, string $type, int $position): bool;
abstract protected function prepare(string $query): bool;
abstract protected static function buildEngineException($code, string $msg): array;
public function run(...$values): Result {
return $this->runArray($values);

57
lib/Db/MySQL/Driver.php

@ -13,11 +13,15 @@ use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ExceptionTimeout;
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
use ExceptionBuilder;
const SQL_MODE = "ANSI_QUOTES,HIGH_NOT_PRECEDENCE,NO_BACKSLASH_ESCAPES,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,STRICT_ALL_TABLES";
const TRANSACTIONAL_LOCKS = false;
/** @var \mysql */
protected $db;
protected $transStart = 0;
protected $packetSize = 4194304;
public function __construct() {
// check to make sure required extension is loaded
@ -26,7 +30,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
}
$host = Arsse::$conf->dbMySQLHost;
if ($host[0] == "/") {
// host is a socket
// host is a Unix socket
$socket = $host;
$host = "";
} elseif(substr($host, 0, 9) == "\\\\.\\pipe\\") {
@ -38,9 +42,22 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
$pass = Arsse::$conf->dbMySQLPass ?? "";
$port = Arsse::$conf->dbMySQLPost ?? 3306;
$db = Arsse::$conf->dbMySQLDb ?? "arsse";
// make the connection
$this->makeConnection($user, $pass, $db, $host, $port, $socket ?? "");
$this->exec("SET lock_wait_timeout = 1");
$this->exec("SET time_zone = '+00:00'");
// set session variables
foreach (static::makeSetupQueries() as $q) {
$this->exec($q);
}
// get the maximum packet size; parameter strings larger than this size need to be chunked
$this->packetSize = $this->query("select variable_value from performance_schema.session_variables where variable_name = 'max_allowed_packet'")->getValue();
}
public static function makeSetupQueries(): array {
return [
"SET sql_mode = '".self::SQL_MODE."'",
"SET time_zone = '+00:00'",
"SET lock_wait_timeout = 1",
];
}
/** @codeCoverageIgnore */
@ -148,17 +165,47 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
}
protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket) {
try {
$this->db = new \mysqli($host, $user, $password, $db, $port, $socket);
if ($this->db->connect_errno) {
echo $this->db->connect_errno.": ".$this->db->connect_error;
}
$this->db->set_character_set("utf8mb4");
} catch (\Exception $e) {
throw $e;
}
}
protected function getError(): string {
public function exec(string $query): bool {
$this->dispatch($query);
return true;
}
public function exec(string $query): bool {
protected function dispatch(string $query) {
$r = $this->db->query($query);
if ($this->db->sqlstate != "00000") {
if ($this->db->sqlstate == "HY000") {
list($excClass, $excMsg, $excData) = $this->buildEngineException($this->db->errno, $this->db->error);
} else {
list($excClass, $excMsg, $excData) = $this->buildStandardException($this->db->sqlstate, $this->db->error);
}
throw new $excClass($excMsg, $excData);
}
return $r;
}
public function query(string $query): \JKingWeb\Arsse\Db\Result {
$r = $this->dispatch($query);
$rows = (int) $this->db->affected_rows;
$id = (int) $this->db->insert_id;
if ($r === true) {
return new \JKingWeb\Arsse\Db\ResultEmpty($rows, $id);
} else {
return new ResultE($r, [$rows, $id]);
}
}
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
return new Statement($this->db, $query, $paramTypes, $this->packetSize);
}
}

30
lib/Db/MySQL/ExceptionBuilder.php

@ -0,0 +1,30 @@
<?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\MySQL;
use JKingWeb\Arsse\Db\Exception;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ExceptionTimeout;
trait ExceptionBuilder {
protected function buildException(): array {
return self::buildEngineException($this->db->errno, $this->db->error);
}
public static function buildEngineException($code, string $msg): array {
switch ($code) {
case 1205:
return [ExceptionTimeout::class, 'general', $msg];
case 1364:
return [ExceptionInput::class, "constraintViolation", $msg];
case 1366:
return [ExceptionInput::class, 'engineTypeViolation', $msg];
default:
return [Exception::class, 'engineErrorGeneral', $msg];
}
}
}

1
lib/Db/MySQL/PDODriver.php

@ -33,7 +33,6 @@ class PDODriver extends Driver {
$dsn = "mysql:".implode(";", $dsn);
$this->db = new \PDO($dsn, $user, $password, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::MYSQL_ATTR_INIT_COMMAND => "SET sql_mode = '".self::SQL_MODE."'",
]);
}

15
lib/Db/MySQL/PDOStatement.php

@ -6,16 +6,11 @@
declare(strict_types=1);
namespace JKingWeb\Arsse\Db\MySQL;
class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
use ExceptionBuilder;
use \JKingWeb\Arsse\Db\PDOError;
public static function mungeQuery(string $query, array $types, ...$extraData): string {
$query = explode("?", $query);
$out = "";
for ($b = 1; $b < sizeof($query); $b++) {
$a = $b - 1;
$mark = (($types[$a] ?? "") == "datetime") ? "cast(? as datetime(0))" : "?";
$out .= $query[$a].$mark;
}
$out .= array_pop($query);
return $out;
return Statement::mungeQuery($query, $types);
}
}

51
lib/Db/MySQL/Result.php

@ -0,0 +1,51 @@
<?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\MySQL;
use JKingWeb\Arsse\Db\Exception;
class Result extends \JKingWeb\Arsse\Db\AbstractResult {
protected $st;
protected $set;
protected $cur = null;
protected $rows = 0;
protected $id = 0;
// actual public methods
public function changes(): int {
return $this->rows;
}
public function lastId(): int {
return $this->id;
}
// constructor/destructor
public function __construct(\mysqli_result $result, array $changes = [0,0], Statement $statement = null) {
$this->st = $statement; //keeps the statement from being destroyed, invalidating the result set
$this->set = $result;
$this->rows = $changes[0];
$this->id = $changes[1];
}
public function __destruct() {
try {
$this->set->free();
} catch (\Throwable $e) { // @codeCoverageIgnore
}
unset($this->set);
}
// PHP iterator methods
public function valid() {
$this->cur = $this->set->fetch_assoc();
return ($this->cur !== null);
}
}

107
lib/Db/MySQL/Statement.php

@ -0,0 +1,107 @@
<?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\MySQL;
use JKingWeb\Arsse\Db\Exception;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ExceptionTimeout;
class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
use ExceptionBuilder;
const BINDINGS = [
"integer" => "i",
"float" => "d",
"datetime" => "s",
"binary" => "b",
"string" => "s",
"boolean" => "i",
];
protected $db;
protected $st;
protected $query;
protected $packetSize;
protected $values;
protected $types;
public function __construct(\mysqli $db, string $query, array $bindings = [], int $packetSize) {
$this->db = $db;
$this->query = $query;
$this->packetSize = $packetSize;
$this->retypeArray($bindings);
}
protected function prepare(string $query): bool {
$this->st = $this->db->prepare($query);
if (!$this->st) {
list($excClass, $excMsg, $excData) = $this->buildEngineException($this->db->errno, $this->db->error);
throw new $excClass($excMsg, $excData);
}
return true;
}
public function __destruct() {
try {
$this->st->close();
} catch (\Throwable $e) { // @codeCoverageIgnore
}
unset($this->st);
}
public function runArray(array $values = []): \JKingWeb\Arsse\Db\Result {
$this->st->reset();
// prepare values and them all at once
$this->bindValues($values);
$this->st->bind_params($this->types, ...$this->values);
// execute the statement
$this->st->execute();
// clear normalized values
$this->types = "";
$this->values = [];
// check for errors
if ($this->st->sqlstate != "00000") {
if ($this->st->sqlstate == "HY000") {
list($excClass, $excMsg, $excData) = $this->buildEngineException($this->st->errno, $this->st->error);
} else {
list($excClass, $excMsg, $excData) = $this->buildStandardException($this->st->sqlstate, $this->st->error);
}
throw new $excClass($excMsg, $excData);
}
// create a result-set instance
$r = $this->st->get_result();
$changes = $this->st->affected_rows;
$lastId = $this->st->insert_id;
return new Result($r, [$changes, $lastId], $this);
}
protected function bindValue($value, string $type, int $position): bool {
// this is a bit of a hack: we collect values (and MySQL bind types) here so that we can take
// advantage of the work done by bindValues() even though MySQL requires everything to be bound
// all at once; we also packetize large values here if necessary
if (is_string($value) && strlen($value) > $this->packetSize) {
$this->values[] = null;
$this->st->send_long_data($position - 1, $value);
} else {
$this->values[] = $value;
$this->types .= self::BINDINGS[$type];
}
return true;
}
public static function mungeQuery(string $query, array $types, ...$extraData): string {
$query = explode("?", $query);
$out = "";
for ($b = 1; $b < sizeof($query); $b++) {
$a = $b - 1;
$mark = (($types[$a] ?? "") == "datetime") ? "cast(? as datetime(0))" : "?";
$out .= $query[$a].$mark;
}
$out .= array_pop($query);
return $out;
}
}

8
lib/Db/PDODriver.php

@ -14,7 +14,7 @@ trait PDODriver {
$this->db->exec($query);
return true;
} catch (\PDOException $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
list($excClass, $excMsg, $excData) = $this->buildPDOException();
throw new $excClass($excMsg, $excData);
}
}
@ -23,13 +23,9 @@ trait PDODriver {
try {
$r = $this->db->query($query);
} catch (\PDOException $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
list($excClass, $excMsg, $excData) = $this->buildPDOException();
throw new $excClass($excMsg, $excData);
}
return new PDOResult($this->db, $r);
}
public function prepareArray(string $query, array $paramTypes): Statement {
return new PDOStatement($this->db, $query, $paramTypes);
}
}

51
lib/Db/PDOError.php

@ -6,56 +6,19 @@
declare(strict_types=1);
namespace JKingWeb\Arsse\Db;
use JKingWeb\Arsse\Db\SQLite3\Driver as SQLite3;
trait PDOError {
public function exceptionBuild(bool $statementError = false): array {
use SQLState;
protected function buildPDOException(bool $statementError = false): array {
if ($statementError) {
$err = $this->st->errorInfo();
} else {
$err = $this->db->errorInfo();
}
switch ($err[0]) {
case "22007":
case "22P02":
case "42804":
return [ExceptionInput::class, 'engineTypeViolation', $err[2]];
case "23000":
case "23502":
case "23505":
return [ExceptionInput::class, "constraintViolation", $err[2]];
case "55P03":
case "57014":
return [ExceptionTimeout::class, 'general', $err[2]];
case "HY000":
// engine-specific errors
switch ($this->db->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
case "sqlite":
switch ($err[1]) {
case SQLite3::SQLITE_BUSY:
return [ExceptionTimeout::class, 'general', $err[2]];
case SQLite3::SQLITE_MISMATCH:
return [ExceptionInput::class, 'engineTypeViolation', $err[2]];
}
break; // @codeCoverageIgnore
case "mysql":
switch ($err[1]) {
case 1205:
return [ExceptionTimeout::class, 'general', $err[2]];
case 1364:
return [ExceptionInput::class, "constraintViolation", $err[2]];
case 1366:
return [ExceptionInput::class, 'engineTypeViolation', $err[2]];
}
break; // @codeCoverageIgnore
}
return [Exception::class, "engineErrorGeneral", $err[0]."/".$err[1].": ".$err[2]]; // @codeCoverageIgnore
default:
return [Exception::class, "engineErrorGeneral", $err[0].": ".$err[2]]; // @codeCoverageIgnore
if ($err[0]=="HY000") {
return static::buildEngineException($err[1], $err[2]);
} else {
return static::buildStandardException($err[0], $err[2]);
}
}
public function getError(): string {
return (string) $this->db->errorInfo()[2];
}
}

6
lib/Db/PDOStatement.php

@ -6,7 +6,7 @@
declare(strict_types=1);
namespace JKingWeb\Arsse\Db;
class PDOStatement extends AbstractStatement {
abstract class PDOStatement extends AbstractStatement {
use PDOError;
const BINDINGS = [
@ -34,7 +34,7 @@ class PDOStatement extends AbstractStatement {
$this->st = $this->db->prepare($query);
return true;
} catch (\PDOException $e) { // @codeCoverageIgnore
list($excClass, $excMsg, $excData) = $this->exceptionBuild(); // @codeCoverageIgnore
list($excClass, $excMsg, $excData) = $this->buildPDOException(); // @codeCoverageIgnore
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
}
}
@ -49,7 +49,7 @@ class PDOStatement extends AbstractStatement {
try {
$this->st->execute();
} catch (\PDOException $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild(true);
list($excClass, $excMsg, $excData) = $this->buildPDOException(true);
throw new $excClass($excMsg, $excData);
}
return new PDOResult($this->db, $this->st);

26
lib/Db/PostgreSQL/Dispatch.php

@ -6,37 +6,19 @@
declare(strict_types=1);
namespace JKingWeb\Arsse\Db\PostgreSQL;
use JKingWeb\Arsse\Arsse;
use JKingWeb\Arsse\Conf;
use JKingWeb\Arsse\Db\Exception;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ExceptionTimeout;
trait Dispatch {
protected function dispatchQuery(string $query, array $params = []) {
pg_send_query_params($this->db, $query, $params);
$result = pg_get_result($this->db);
if (($code = pg_result_error_field($result, \PGSQL_DIAG_SQLSTATE)) && isset($code) && $code) {
return $this->buildException($code, pg_result_error($result));
return $this->buildStandardException($code, pg_result_error($result));
} else {
return $result;
}
}
protected function buildException(string $code, string $msg): array {
switch ($code) {
case "22P02":
case "42804":
return [ExceptionInput::class, 'engineTypeViolation', $msg];
case "23000":
case "23502":
case "23505":
return [ExceptionInput::class, "engineConstraintViolation", $msg];
case "55P03":
case "57014":
return [ExceptionTimeout::class, 'general', $msg];
default:
return [Exception::class, "engineErrorGeneral", $code.": ".$msg]; // @codeCoverageIgnore
}
public static function buildEngineException($code, string $msg): array {
// PostgreSQL uses SQLSTATE exclusively, so this is not used
return [];
}
}

9
lib/Db/PostgreSQL/Driver.php

@ -20,7 +20,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
protected $db;
protected $transStart = 0;
public function __construct(string $user = null, string $pass = null, string $db = null, string $host = null, int $port = null, string $schema = null, string $service = null) {
public function __construct() {
// check to make sure required extension is loaded
if (!static::requirementsMet()) {
throw new Exception("extMissing", static::driverName()); // @codeCoverageIgnore
@ -193,16 +193,11 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
}
}
protected function getError(): string {
// stub
return "";
}
public function exec(string $query): bool {
pg_send_query($this->db, $query);
while ($result = pg_get_result($this->db)) {
if (($code = pg_result_error_field($result, \PGSQL_DIAG_SQLSTATE)) && isset($code) && $code) {
list($excClass, $excMsg, $excData) = $this->buildException($code, pg_result_error($result));
list($excClass, $excMsg, $excData) = $this->buildStandardException($code, pg_result_error($result));
throw new $excClass($excMsg, $excData);
}
}

7
lib/Db/PostgreSQL/PDOStatement.php

@ -6,8 +6,13 @@
declare(strict_types=1);
namespace JKingWeb\Arsse\Db\PostgreSQL;
class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
public static function mungeQuery(string $query, array $types, ...$extraData): string {
return Statement::mungeQuery($query, $types, false);
}
public static function buildEngineException($code, string $msg): array {
// PostgreSQL uses SQLSTATE exclusively, so this is not used
return [];
}
}

1
lib/Db/PostgreSQL/Statement.php

@ -38,6 +38,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
$this->in = [];
$this->bindValues($values);
$r = $this->dispatchQuery($this->qMunged, $this->in);
$this->in = [];
if (is_resource($r)) {
return new Result($this->db, $r);
} else {

12
lib/Db/ResultEmpty.php

@ -9,12 +9,20 @@ namespace JKingWeb\Arsse\Db;
use JKingWeb\Arsse\Db\Exception;
class ResultEmpty extends AbstractResult {
protected $changes = 0;
protected $id = 0;
public function __construct(int $changes = 0, int $id = 0) {
$this->changes = $changes;
$this->id = $id;
}
public function changes(): int {
return 0;
return $this->changes;
}
public function lastId(): int {
return 0;
return $this->id;
}
// PHP iterator methods

31
lib/Db/SQLState.php

@ -0,0 +1,31 @@
<?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;
use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ExceptionTimeout;
trait SQLState {
protected static function buildStandardException(string $code, string $msg): array {
switch ($code) {
case "22007":
case "22P02":
case "42804":
return [ExceptionInput::class, 'engineTypeViolation', $msg];
case "23000":
case "23502":
case "23505":
return [ExceptionInput::class, "constraintViolation", $msg];
case "55P03":
case "57014":
return [ExceptionTimeout::class, 'general', $msg];
default:
return [Exception::class, "engineErrorGeneral", "SQLSTATE $code: $msg"]; // @codeCoverageIgnore
}
}
}

10
lib/Db/SQLite3/Driver.php

@ -22,7 +22,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
protected $db;
public function __construct(string $dbFile = null, string $dbKey = null) {
public function __construct() {
// check to make sure required extension is loaded
if (!static::requirementsMet()) {
throw new Exception("extMissing", static::driverName()); // @codeCoverageIgnore
@ -140,15 +140,11 @@ class Driver extends \JKingWeb\Arsse\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);
} catch (\Exception $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
list($excClass, $excMsg, $excData) = $this->buildException();
throw new $excClass($excMsg, $excData);
}
}
@ -157,7 +153,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
try {
$r = $this->db->query($query);
} catch (\Exception $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
list($excClass, $excMsg, $excData) = $this->buildException();
throw new $excClass($excMsg, $excData);
}
$changes = $this->db->changes();

22
lib/Db/SQLite3/ExceptionBuilder.php

@ -11,16 +11,20 @@ use JKingWeb\Arsse\Db\ExceptionInput;
use JKingWeb\Arsse\Db\ExceptionTimeout;
trait ExceptionBuilder {
public function exceptionBuild(): array {
switch ($this->db->lastErrorCode()) {
case self::SQLITE_BUSY:
return [ExceptionTimeout::class, 'general', $this->db->lastErrorMsg()];
case self::SQLITE_CONSTRAINT:
return [ExceptionInput::class, 'engineConstraintViolation', $this->db->lastErrorMsg()];
case self::SQLITE_MISMATCH:
return [ExceptionInput::class, 'engineTypeViolation', $this->db->lastErrorMsg()];
protected function buildException(): array {
return self::buildEngineException($this->db->lastErrorCode(), $this->db->lastErrorMsg());
}
public static function buildEngineException($code, string $msg): array {
switch ($code) {
case Driver::SQLITE_BUSY:
return [ExceptionTimeout::class, 'general', $msg];
case Driver::SQLITE_CONSTRAINT:
return [ExceptionInput::class, 'engineConstraintViolation', $msg];
case Driver::SQLITE_MISMATCH:
return [ExceptionInput::class, 'engineTypeViolation', $msg];
default:
return [Exception::class, 'engineErrorGeneral', $this->db->lastErrorMsg()];
return [Exception::class, 'engineErrorGeneral', $msg];
}
}
}

4
lib/Db/SQLite3/PDODriver.php

@ -45,4 +45,8 @@ class PDODriver extends Driver {
public static function driverName(): string {
return Arsse::$lang->msg("Driver.Db.SQLite3PDO.Name");
}
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
return new PDOStatement($this->db, $query, $paramTypes);
}
}

12
lib/Db/SQLite3/PDOStatement.php

@ -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\Db\SQLite3;
class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
use ExceptionBuilder;
use \JKingWeb\Arsse\Db\PDOError;
}

4
lib/Db/SQLite3/Statement.php

@ -41,7 +41,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
$this->st = $this->db->prepare($query);
return true;
} catch (\Exception $e) { // @codeCoverageIgnore
list($excClass, $excMsg, $excData) = $this->exceptionBuild(); // @codeCoverageIgnore
list($excClass, $excMsg, $excData) = $this->buildException(); // @codeCoverageIgnore
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
}
}
@ -60,7 +60,7 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
try {
$r = $this->st->execute();
} catch (\Exception $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
list($excClass, $excMsg, $excData) = $this->buildException();
throw new $excClass($excMsg, $excData);
}
$changes = $this->db->changes();

2
tests/lib/DatabaseInformation.php

@ -228,7 +228,7 @@ class DatabaseInformation {
'PDO SQLite 3' => [
'pdo' => true,
'backend' => "SQLite 3",
'statementClass' => \JKingWeb\Arsse\Db\PDOStatement::class,
'statementClass' => \JKingWeb\Arsse\Db\SQLite3\PDOStatement::class,
'resultClass' => \JKingWeb\Arsse\Db\PDOResult::class,
'driverClass' => \JKingWeb\Arsse\Db\SQLite3\PDODriver::class,
'stringOutput' => true,

Loading…
Cancel
Save