mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 21:22:40 +00:00
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
This commit is contained in:
parent
3da773eef6
commit
c4a41255b0
23 changed files with 354 additions and 123 deletions
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
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
Normal file
30
lib/Db/MySQL/ExceptionBuilder.php
Normal file
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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."'",
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,15 +7,10 @@ declare(strict_types=1);
|
|||
namespace JKingWeb\Arsse\Db\MySQL;
|
||||
|
||||
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
Normal file
51
lib/Db/MySQL/Result.php
Normal file
|
@ -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
Normal file
107
lib/Db/MySQL/Statement.php
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]];
|
||||
if ($err[0]=="HY000") {
|
||||
return static::buildEngineException($err[1], $err[2]);
|
||||
} else {
|
||||
return static::buildStandardException($err[0], $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
|
||||
}
|
||||
}
|
||||
|
||||
public function getError(): string {
|
||||
return (string) $this->db->errorInfo()[2];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,4 +10,9 @@ 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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
Normal file
31
lib/Db/SQLState.php
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Normal file
12
lib/Db/SQLite3/PDOStatement.php
Normal file
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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…
Reference in a new issue