1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +00:00

Fixes for MySQL native interface

Three test failures remain, but these are minor and will be resolved
soon. Handling of binary data is also broken, but given that this works
fine with the PDO driver, there is presumably some correct method.
This commit is contained in:
J. King 2019-01-13 23:17:19 -05:00
parent e501fbdc87
commit 5d61ab0a57
14 changed files with 216 additions and 34 deletions

View file

@ -49,7 +49,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
$this->exec($q); $this->exec($q);
} }
// get the maximum packet size; parameter strings larger than this size need to be chunked // 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(); $this->packetSize = (int) $this->query("select variable_value from performance_schema.session_variables where variable_name = 'max_allowed_packet'")->getValue();
} }
public static function makeSetupQueries(): array { public static function makeSetupQueries(): array {
@ -161,7 +161,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
} }
public static function requirementsMet(): bool { public static function requirementsMet(): bool {
return false; return class_exists("mysqli");
} }
protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket) { protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket) {
@ -170,39 +170,47 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
if ($this->db->connect_errno) { if ($this->db->connect_errno) {
echo $this->db->connect_errno.": ".$this->db->connect_error; echo $this->db->connect_errno.": ".$this->db->connect_error;
} }
$this->db->set_character_set("utf8mb4"); $this->db->set_charset("utf8mb4");
} catch (\Exception $e) { } catch (\Exception $e) {
throw $e; throw $e;
} }
} }
public function exec(string $query): bool { public function exec(string $query): bool {
$this->dispatch($query); $this->dispatch($query, true);
return true; return true;
} }
protected function dispatch(string $query) { protected function dispatch(string $query, bool $multi = false) {
$r = $this->db->query($query); if ($multi) {
$this->db->multi_query($query);
} else {
$this->db->real_query($query);
}
$e = null;
do {
if ($this->db->sqlstate !== "00000") { if ($this->db->sqlstate !== "00000") {
if ($this->db->sqlstate === "HY000") { if ($this->db->sqlstate === "HY000") {
list($excClass, $excMsg, $excData) = $this->buildEngineException($this->db->errno, $this->db->error); list($excClass, $excMsg, $excData) = $this->buildEngineException($this->db->errno, $this->db->error);
} else { } else {
list($excClass, $excMsg, $excData) = $this->buildStandardException($this->db->sqlstate, $this->db->error); list($excClass, $excMsg, $excData) = $this->buildStandardException($this->db->sqlstate, $this->db->error);
} }
throw new $excClass($excMsg, $excData); $e = new $excClass($excMsg, $excData, $e);
} }
$r = $this->db->store_result();
} while ($this->db->more_results() && $this->db->next_result());
if ($e) {
throw $e;
} else {
return $r; return $r;
} }
}
public function query(string $query): \JKingWeb\Arsse\Db\Result { public function query(string $query): \JKingWeb\Arsse\Db\Result {
$r = $this->dispatch($query); $r = $this->dispatch($query);
$rows = (int) $this->db->affected_rows; $rows = (int) $this->db->affected_rows;
$id = (int) $this->db->insert_id; $id = (int) $this->db->insert_id;
if ($r === true) { return new Result($r, [$rows, $id]);
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 { public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {

View file

@ -27,9 +27,9 @@ class Result extends \JKingWeb\Arsse\Db\AbstractResult {
// constructor/destructor // constructor/destructor
public function __construct(\mysqli_result $result, array $changes = [0,0], Statement $statement = null) { public function __construct($result, array $changes = [0,0], Statement $statement = null) {
$this->st = $statement; //keeps the statement from being destroyed, invalidating the result set $this->st = $statement; //keeps the statement from being destroyed, invalidating the result set
$this->set = $result; $this->set = ($result instanceof \mysqli_result) ? $result : null;
$this->rows = $changes[0]; $this->rows = $changes[0];
$this->id = $changes[1]; $this->id = $changes[1];
} }
@ -45,7 +45,7 @@ class Result extends \JKingWeb\Arsse\Db\AbstractResult {
// PHP iterator methods // PHP iterator methods
public function valid() { public function valid() {
$this->cur = $this->set->fetch_assoc(); $this->cur = $this->set ? $this->set->fetch_assoc() : null;
return ($this->cur !== null); return ($this->cur !== null);
} }
} }

View file

@ -28,9 +28,9 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
protected $packetSize; protected $packetSize;
protected $values; protected $values;
protected $types; protected $binds = "";
public function __construct(\mysqli $db, string $query, array $bindings = [], int $packetSize) { public function __construct(\mysqli $db, string $query, array $bindings = [], int $packetSize = 4194304) {
$this->db = $db; $this->db = $db;
$this->query = $query; $this->query = $query;
$this->packetSize = $packetSize; $this->packetSize = $packetSize;
@ -58,11 +58,13 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
$this->st->reset(); $this->st->reset();
// prepare values and them all at once // prepare values and them all at once
$this->bindValues($values); $this->bindValues($values);
$this->st->bind_params($this->types, ...$this->values); if ($this->values) {
$this->st->bind_param($this->binds, ...$this->values);
}
// execute the statement // execute the statement
$this->st->execute(); $this->st->execute();
// clear normalized values // clear normalized values
$this->types = ""; $this->binds = "";
$this->values = []; $this->values = [];
// check for errors // check for errors
if ($this->st->sqlstate !== "00000") { if ($this->st->sqlstate !== "00000") {
@ -84,13 +86,13 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
// this is a bit of a hack: we collect values (and MySQL bind types) here so that we can take // 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 // 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 // all at once; we also packetize large values here if necessary
if (is_string($value) && strlen($value) > $this->packetSize) { if (($type === "binary" && !is_null($value)) || (is_string($value) && strlen($value) > $this->packetSize)) {
$this->values[] = null; $this->values[] = null;
$this->st->send_long_data($position - 1, $value); $this->st->send_long_data($position - 1, $value);
} else { } else {
$this->values[] = $value; $this->values[] = $value;
$this->types .= self::BINDINGS[$type];
} }
$this->binds .= self::BINDINGS[$type];
return true; return true;
} }
public static function mungeQuery(string $query, array $types, ...$extraData): string { public static function mungeQuery(string $query, array $types, ...$extraData): string {

View file

@ -22,8 +22,8 @@ return [
'Driver.Db.SQLite3PDO.Name' => 'SQLite 3 (PDO)', 'Driver.Db.SQLite3PDO.Name' => 'SQLite 3 (PDO)',
'Driver.Db.PostgreSQL.Name' => 'PostgreSQL', 'Driver.Db.PostgreSQL.Name' => 'PostgreSQL',
'Driver.Db.PostgreSQLPDO.Name' => 'PostgreSQL (PDO)', 'Driver.Db.PostgreSQLPDO.Name' => 'PostgreSQL (PDO)',
'Driver.Db.MySQL.Name' => 'MySQL/MariaDB', 'Driver.Db.MySQL.Name' => 'MySQL',
'Driver.Db.MySQLPDO.Name' => 'MySQL/MariaDB (PDO)', 'Driver.Db.MySQLPDO.Name' => 'MySQL (PDO)',
'Driver.Service.Curl.Name' => 'HTTP (curl)', 'Driver.Service.Curl.Name' => 'HTTP (curl)',
'Driver.Service.Internal.Name' => 'Internal', 'Driver.Service.Internal.Name' => 'Internal',
'Driver.User.Internal.Name' => 'Internal', 'Driver.User.Internal.Name' => 'Internal',

View file

@ -15,9 +15,18 @@ trait MySQL {
protected static $dbResultClass = \JKingWeb\Arsse\Db\MySQL\Result::class; protected static $dbResultClass = \JKingWeb\Arsse\Db\MySQL\Result::class;
protected static $dbStatementClass = \JKingWeb\Arsse\Db\MySQL\Statement::class; protected static $dbStatementClass = \JKingWeb\Arsse\Db\MySQL\Statement::class;
protected static $dbDriverClass = \JKingWeb\Arsse\Db\MySQL\Driver::class; protected static $dbDriverClass = \JKingWeb\Arsse\Db\MySQL\Driver::class;
protected static $stringOutput = false; protected static $stringOutput = true;
public static function dbInterface() { public static function dbInterface() {
$d = new \mysqli(Arsse::$conf->dbMySQLHost, Arsse::$conf->dbMySQLUser, Arsse::$conf->dbMySQLPass, Arsse::$conf->dbMySQLDb, Arsse::$conf->dbMySQLPort);
if ($d->connect_errno) {
return;
}
$d->set_charset("utf8mb4");
foreach (\JKingWeb\Arsse\Db\MySQL\PDODriver::makeSetupQueries() as $q) {
$d->query($q);
}
return $d;
} }
public static function dbTableList($db): array { public static function dbTableList($db): array {

View file

@ -30,7 +30,14 @@ trait MySQLPDO {
$dsn[] = "$k=$v"; $dsn[] = "$k=$v";
} }
$dsn = "mysql:".implode(";", $dsn); $dsn = "mysql:".implode(";", $dsn);
return new \PDO($dsn, Arsse::$conf->dbMySQLUser, Arsse::$conf->dbMySQLPass, [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::MYSQL_ATTR_MULTI_STATEMENTS => false, \PDO::MYSQL_ATTR_INIT_COMMAND => "SET sql_mode = '".\JKingWeb\Arsse\Db\MySQL\PDODriver::SQL_MODE."'",]); $d = new \PDO($dsn, Arsse::$conf->dbMySQLUser, Arsse::$conf->dbMySQLPass, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::MYSQL_ATTR_MULTI_STATEMENTS => false,
]);
foreach (\JKingWeb\Arsse\Db\MySQL\PDODriver::makeSetupQueries() as $q) {
$d->exec($q);
}
return $d;
} catch (\Throwable $e) { } catch (\Throwable $e) {
return; return;
} }

View file

@ -67,8 +67,8 @@ abstract class BaseStatement extends \JKingWeb\Arsse\Test\AbstractTest {
/** @dataProvider provideBinaryBindings */ /** @dataProvider provideBinaryBindings */
public function testHandleBinaryData($value, string $type, string $exp) { public function testHandleBinaryData($value, string $type, string $exp) {
if (in_array(static::$implementation, ["PostgreSQL", "PDO PostgreSQL"])) { if (in_array(static::$implementation, ["MySQL", "PostgreSQL", "PDO PostgreSQL"])) {
$this->markTestSkipped("Correct handling of binary data with PostgreSQL is currently unknown"); $this->markTestSkipped("Correct handling of binary data with PostgreSQL and native MySQL is currently unknown");
} }
if ($exp === "null") { if ($exp === "null") {
$query = "SELECT (? is null) as pass"; $query = "SELECT (? is null) as pass";

View file

@ -0,0 +1,21 @@
<?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\MySQL;
/**
* @group slow
* @group coverageOptional
* @covers \JKingWeb\Arsse\Database<extended>
* @covers \JKingWeb\Arsse\Misc\Query<extended>
*/
class TestDatabase extends \JKingWeb\Arsse\TestCase\Database\Base {
use \JKingWeb\Arsse\TestCase\DatabaseDrivers\MySQL;
protected function nextID(string $table): int {
return (int) (static::$drv->query("SELECT auto_increment from information_schema.tables where table_name = '$table'")->getValue() ?? 1);
}
}

View file

@ -0,0 +1,50 @@
<?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\MySQL;
/**
* @group slow
* @covers \JKingWeb\Arsse\Db\MySQL\Driver<extended> */
class TestDriver extends \JKingWeb\Arsse\TestCase\Db\BaseDriver {
use \JKingWeb\Arsse\TestCase\DatabaseDrivers\MySQL;
protected $create = "CREATE TABLE arsse_test(id bigint auto_increment primary key)";
protected $lock = ["SET lock_wait_timeout = 1", "LOCK TABLES arsse_meta WRITE"];
protected $setVersion = "UPDATE arsse_meta set value = '#' where `key` = 'schema_version'";
protected static $insertDefaultValues = "INSERT INTO arsse_test(id) values(default)";
protected function exec($q): bool {
if (is_array($q)) {
$q = implode("; ", $q);
}
static::$interface->multi_query($q);
$e = null;
do {
if (!$e && static::$interface->sqlstate !== "00000") {
$e = new \Exception(static::$interface->error);
}
} while (static::$interface->more_results() && static::$interface->next_result());
if ($e) {
throw $e;
}
return true;
}
protected function query(string $q) {
$r = static::$interface->query($q);
if ($r) {
$row = $r->fetch_row();
$r->free();
if ($row) {
return $row[0];
} else {
return null;
}
}
return null;
}
}

View file

@ -0,0 +1,28 @@
<?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\MySQL;
use JKingWeb\Arsse\Test\DatabaseInformation;
/**
* @group slow
* @covers \JKingWeb\Arsse\Db\MySQL\Result<extended>
*/
class TestResult extends \JKingWeb\Arsse\TestCase\Db\BaseResult {
use \JKingWeb\Arsse\TestCase\DatabaseDrivers\MySQL;
protected static $createMeta = "CREATE TABLE arsse_meta(`key` varchar(255) primary key not null, value text)";
protected static $createTest = "CREATE TABLE arsse_test(id bigint auto_increment primary key)";
protected static $insertDefault = "INSERT INTO arsse_test(id) values(default)";
protected function makeResult(string $q): array {
$r = static::$interface->query($q);
$rows = static::$interface->affected_rows;
$id = static::$interface->insert_id;
return [$r, [$rows, $id]];
}
}

View file

@ -0,0 +1,32 @@
<?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\MySQL;
/**
* @group slow
* @covers \JKingWeb\Arsse\Db\MySQL\Statement<extended> */
class TestStatement extends \JKingWeb\Arsse\TestCase\Db\BaseStatement {
use \JKingWeb\Arsse\TestCase\DatabaseDrivers\MySQL;
protected function makeStatement(string $q, array $types = []): array {
return [static::$interface, $q, $types];
}
protected function decorateTypeSyntax(string $value, string $type): string {
switch ($type) {
case "float":
return (substr($value, -2) === ".0") ? "'".substr($value, 0, strlen($value) - 2)."'" : "'$value'";
case "string":
if (preg_match("<^char\((\d+)\)$>", $value, $match)) {
return "'".\IntlChar::chr((int) $match[1])."'";
}
return $value;
default:
return $value;
}
}
}

View file

@ -0,0 +1,17 @@
<?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\MySQL;
/**
* @group slow
* @covers \JKingWeb\Arsse\Db\MySQL\Driver<extended> */
class TestUpdate extends \JKingWeb\Arsse\TestCase\Db\BaseUpdate {
use \JKingWeb\Arsse\TestCase\DatabaseDrivers\MySQL;
protected static $minimal1 = "CREATE TABLE arsse_meta(`key` varchar(255) primary key, value text); INSERT INTO arsse_meta(`key`,value) values('schema_version','1');";
protected static $minimal2 = "UPDATE arsse_meta set value = '2' where `key` = 'schema_version';";
}

View file

@ -8,6 +8,7 @@ namespace JKingWeb\Arsse\TestCase\Db\MySQLPDO;
/** /**
* @group slow * @group slow
* @group optional
* @group coverageOptional * @group coverageOptional
* @covers \JKingWeb\Arsse\Database<extended> * @covers \JKingWeb\Arsse\Database<extended>
* @covers \JKingWeb\Arsse\Misc\Query<extended> * @covers \JKingWeb\Arsse\Misc\Query<extended>

View file

@ -69,6 +69,12 @@
<file>cases/Db/PostgreSQLPDO/TestDriver.php</file> <file>cases/Db/PostgreSQLPDO/TestDriver.php</file>
<file>cases/Db/PostgreSQLPDO/TestUpdate.php</file> <file>cases/Db/PostgreSQLPDO/TestUpdate.php</file>
<file>cases/Db/MySQL/TestResult.php</file>
<file>cases/Db/MySQL/TestStatement.php</file>
<file>cases/Db/MySQL/TestCreation.php</file>
<file>cases/Db/MySQL/TestDriver.php</file>
<file>cases/Db/MySQL/TestUpdate.php</file>
<file>cases/Db/MySQLPDO/TestResult.php</file> <file>cases/Db/MySQLPDO/TestResult.php</file>
<file>cases/Db/MySQLPDO/TestStatement.php</file> <file>cases/Db/MySQLPDO/TestStatement.php</file>
<file>cases/Db/MySQLPDO/TestCreation.php</file> <file>cases/Db/MySQLPDO/TestCreation.php</file>
@ -80,6 +86,7 @@
<file>cases/Db/SQLite3PDO/TestDatabase.php</file> <file>cases/Db/SQLite3PDO/TestDatabase.php</file>
<file>cases/Db/PostgreSQL/TestDatabase.php</file> <file>cases/Db/PostgreSQL/TestDatabase.php</file>
<file>cases/Db/PostgreSQLPDO/TestDatabase.php</file> <file>cases/Db/PostgreSQLPDO/TestDatabase.php</file>
<file>cases/Db/MySQL/TestDatabase.php</file>
<file>cases/Db/MySQLPDO/TestDatabase.php</file> <file>cases/Db/MySQLPDO/TestDatabase.php</file>
</testsuite> </testsuite>
<testsuite name="REST"> <testsuite name="REST">