Browse Source

Make munging of queries a generic feature

microsub
J. King 5 years ago
parent
commit
f0d30c2eee
  1. 4
      lib/Database.php
  2. 8
      lib/Db/AbstractStatement.php
  3. 21
      lib/Db/MySQL/PDOStatement.php
  4. 8
      lib/Db/PDODriver.php
  5. 4
      lib/Db/PDOError.php
  6. 17
      lib/Db/PDOStatement.php
  7. 49
      lib/Db/PostgreSQL/PDOStatement.php
  8. 23
      lib/Db/PostgreSQL/Statement.php
  9. 8
      lib/Db/SQLite3/Driver.php
  10. 15
      lib/Db/SQLite3/Statement.php
  11. 2
      tests/cases/Db/MySQLPDO/TestStatement.php
  12. 2
      tests/cases/Db/SQLite3/TestStatement.php
  13. 2
      tests/cases/Db/SQLite3PDO/TestStatement.php
  14. 2
      tests/lib/DatabaseInformation.php

4
lib/Database.php

@ -1054,9 +1054,9 @@ class Database {
if ($data['read'] || $data['starred'] || strlen($data['note'] ?? "")) { if ($data['read'] || $data['starred'] || strlen($data['note'] ?? "")) {
// first prepare a query to insert any missing marks rows for the articles we want to mark // first prepare a query to insert any missing marks rows for the articles we want to mark
// but only insert new mark records if we're setting at least one "positive" mark // but only insert new mark records if we're setting at least one "positive" mark
$q = $this->articleQuery($user, $context, ["id", "subscription"]); $q = $this->articleQuery($user, $context, ["id", "subscription", "note"]);
$q->setWhere("arsse_marks.starred is null"); // null means there is no marks row for the article $q->setWhere("arsse_marks.starred is null"); // null means there is no marks row for the article
$this->db->prepare("INSERT INTO arsse_marks(article,subscription) ".$q->getQuery(), $q->getTypes())->run($q->getValues()); $this->db->prepare("INSERT INTO arsse_marks(article,subscription,note) ".$q->getQuery(), $q->getTypes())->run($q->getValues());
} }
if (isset($data['read']) && (isset($data['starred']) || isset($data['note'])) && ($context->edition() || $context->editions())) { if (isset($data['read']) && (isset($data['starred']) || isset($data['note'])) && ($context->edition() || $context->editions())) {
// if marking by edition both read and something else, do separate marks for starred and note than for read // if marking by edition both read and something else, do separate marks for starred and note than for read

8
lib/Db/AbstractStatement.php

@ -15,6 +15,7 @@ abstract class AbstractStatement implements Statement {
abstract public function runArray(array $values = []): Result; abstract public function runArray(array $values = []): Result;
abstract protected function bindValue($value, string $type, int $position): bool; abstract protected function bindValue($value, string $type, int $position): bool;
abstract protected function prepare(string $query): bool;
public function run(...$values): Result { public function run(...$values): Result {
return $this->runArray($values); return $this->runArray($values);
@ -24,6 +25,10 @@ abstract class AbstractStatement implements Statement {
return $this->retypeArray($bindings); return $this->retypeArray($bindings);
} }
public static function mungeQuery(string $query, array $types, ...$extraData): string {
return $query;
}
public function retypeArray(array $bindings, bool $append = false): bool { public function retypeArray(array $bindings, bool $append = false): bool {
if (!$append) { if (!$append) {
$this->types = []; $this->types = [];
@ -47,6 +52,9 @@ abstract class AbstractStatement implements Statement {
$this->types[] = self::TYPES[$binding]; $this->types[] = self::TYPES[$binding];
} }
} }
if (!$append) {
$this->prepare(static::mungeQuery($this->query, $this->types));
}
return true; return true;
} }

21
lib/Db/MySQL/PDOStatement.php

@ -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\Db\MySQL;
class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
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

@ -30,12 +30,6 @@ trait PDODriver {
} }
public function prepareArray(string $query, array $paramTypes): Statement { public function prepareArray(string $query, array $paramTypes): Statement {
try { return new PDOStatement($this->db, $query, $paramTypes);
$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);
} }
} }

4
lib/Db/PDOError.php

@ -9,8 +9,8 @@ namespace JKingWeb\Arsse\Db;
use JKingWeb\Arsse\Db\SQLite3\Driver as SQLite3; use JKingWeb\Arsse\Db\SQLite3\Driver as SQLite3;
trait PDOError { trait PDOError {
public function exceptionBuild(bool $statementError = null): array { public function exceptionBuild(bool $statementError = false): array {
if ($statementError ?? ($this instanceof Statement)) { if ($statementError) {
$err = $this->st->errorInfo(); $err = $this->st->errorInfo();
} else { } else {
$err = $this->db->errorInfo(); $err = $this->db->errorInfo();

17
lib/Db/PDOStatement.php

@ -20,13 +20,24 @@ class PDOStatement extends AbstractStatement {
protected $st; protected $st;
protected $db; protected $db;
protected $query;
public function __construct(\PDO $db, \PDOStatement $st, array $bindings = []) { public function __construct(\PDO $db, string $query, array $bindings = []) {
$this->db = $db; $this->db = $db;
$this->st = $st; $this->query = $query;
$this->retypeArray($bindings); $this->retypeArray($bindings);
} }
protected function prepare(string $query): bool {
try {
$this->st = $this->db->prepare($query);
return true;
} catch (\PDOException $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
throw new $excClass($excMsg, $excData);
}
}
public function __destruct() { public function __destruct() {
unset($this->st, $this->db); unset($this->st, $this->db);
} }
@ -37,7 +48,7 @@ class PDOStatement extends AbstractStatement {
try { try {
$this->st->execute(); $this->st->execute();
} catch (\PDOException $e) { } catch (\PDOException $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild(); list($excClass, $excMsg, $excData) = $this->exceptionBuild(true);
throw new $excClass($excMsg, $excData); throw new $excClass($excMsg, $excData);
} }
return new PDOResult($this->db, $this->st); return new PDOResult($this->db, $this->st);

49
lib/Db/PostgreSQL/PDOStatement.php

@ -6,51 +6,8 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Arsse\Db\PostgreSQL; namespace JKingWeb\Arsse\Db\PostgreSQL;
class PDOStatement extends Statement { class PDOStatement extends \JKingWeb\Arsse\Db\PDOStatement {
use \JKingWeb\Arsse\Db\PDOError; public static function mungeQuery(string $query, array $types, ...$extraData): string {
return Statement::mungeQuery($query, $types, false);
protected $db;
protected $st;
protected $qOriginal;
protected $qMunged;
protected $bindings;
public function __construct(\PDO $db, string $query, array $bindings = []) {
$this->db = $db;
$this->qOriginal = $query;
$this->retypeArray($bindings);
}
public function __destruct() {
unset($this->db, $this->st);
}
public function retypeArray(array $bindings, bool $append = false): bool {
if ($append) {
return parent::retypeArray($bindings, $append);
} else {
$this->bindings = $bindings;
parent::retypeArray($bindings, $append);
$this->qMunged = self::mungeQuery($this->qOriginal, $this->types, false);
try {
// statement creation with PostgreSQL should never fail (it is not evaluated at creation time)
$s = $this->db->prepare($this->qMunged);
} catch (\PDOException $e) { // @codeCoverageIgnore
list($excClass, $excMsg, $excData) = $this->exceptionBuild(true); // @codeCoverageIgnore
throw new $excClass($excMsg, $excData); // @codeCoverageIgnore
}
$this->st = new \JKingWeb\Arsse\Db\PDOStatement($this->db, $s, $this->bindings);
}
return true;
}
public function runArray(array $values = []): \JKingWeb\Arsse\Db\Result {
return $this->st->runArray($values);
}
/** @codeCoverageIgnore */
protected function bindValue($value, string $type, int $position): bool {
// stub required by abstract parent, but never used
return true;
} }
} }

23
lib/Db/PostgreSQL/Statement.php

@ -24,27 +24,16 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
protected $db; protected $db;
protected $in = []; protected $in = [];
protected $qOriginal; protected $query;
protected $qMunged; protected $qMunged;
protected $bindings; protected $bindings;
public function __construct($db, string $query, array $bindings = []) { public function __construct($db, string $query, array $bindings = []) {
$this->db = $db; $this->db = $db;
$this->qOriginal = $query; $this->query = $query;
$this->retypeArray($bindings); $this->retypeArray($bindings);
} }
public function retypeArray(array $bindings, bool $append = false): bool {
if ($append) {
return parent::retypeArray($bindings, $append);
} else {
$this->bindings = $bindings;
parent::retypeArray($bindings, $append);
$this->qMunged = self::mungeQuery($this->qOriginal, $this->types, true);
}
return true;
}
public function runArray(array $values = []): \JKingWeb\Arsse\Db\Result { public function runArray(array $values = []): \JKingWeb\Arsse\Db\Result {
$this->in = []; $this->in = [];
$this->bindValues($values); $this->bindValues($values);
@ -62,7 +51,8 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
return true; return true;
} }
protected static function mungeQuery(string $q, array $types, bool $mungeParamMarkers = true): string { public static function mungeQuery(string $q, array $types, ...$extraData): string {
$mungeParamMarkers = (bool) ($extraData[0] ?? true);
$q = explode("?", $q); $q = explode("?", $q);
$out = ""; $out = "";
for ($b = 1; $b < sizeof($q); $b++) { for ($b = 1; $b < sizeof($q); $b++) {
@ -74,4 +64,9 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
$out .= array_pop($q); $out .= array_pop($q);
return $out; return $out;
} }
protected function prepare(string $query): bool {
$this->qMunged = $query;
return true;
}
} }

8
lib/Db/SQLite3/Driver.php

@ -166,13 +166,7 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
} }
public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement { public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement {
try { return new Statement($this->db, $query, $paramTypes);
$s = $this->db->prepare($query);
} catch (\Exception $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
throw new $excClass($excMsg, $excData);
}
return new Statement($this->db, $s, $paramTypes);
} }
protected function lock(): bool { protected function lock(): bool {

15
lib/Db/SQLite3/Statement.php

@ -27,13 +27,24 @@ class Statement extends \JKingWeb\Arsse\Db\AbstractStatement {
protected $db; protected $db;
protected $st; protected $st;
protected $query;
public function __construct(\SQLite3 $db, \SQLite3Stmt $st, array $bindings = []) { public function __construct(\SQLite3 $db, string $query, array $bindings = []) {
$this->db = $db; $this->db = $db;
$this->st = $st; $this->query = $query;
$this->retypeArray($bindings); $this->retypeArray($bindings);
} }
protected function prepare(string $query): bool {
try {
$this->st = $this->db->prepare($query);
return true;
} catch (\Exception $e) {
list($excClass, $excMsg, $excData) = $this->exceptionBuild();
throw new $excClass($excMsg, $excData);
}
}
public function __destruct() { public function __destruct() {
try { try {
$this->st->close(); $this->st->close();

2
tests/cases/Db/MySQLPDO/TestStatement.php

@ -14,7 +14,7 @@ class TestStatement extends \JKingWeb\Arsse\TestCase\Db\BaseStatement {
protected static $implementation = "PDO MySQL"; protected static $implementation = "PDO MySQL";
protected function makeStatement(string $q, array $types = []): array { protected function makeStatement(string $q, array $types = []): array {
return [static::$interface, static::$interface->prepare($q), $types]; return [static::$interface, $q, $types];
} }
protected function decorateTypeSyntax(string $value, string $type): string { protected function decorateTypeSyntax(string $value, string $type): string {

2
tests/cases/Db/SQLite3/TestStatement.php

@ -19,7 +19,7 @@ class TestStatement extends \JKingWeb\Arsse\TestCase\Db\BaseStatement {
} }
protected function makeStatement(string $q, array $types = []): array { protected function makeStatement(string $q, array $types = []): array {
return [static::$interface, static::$interface->prepare($q), $types]; return [static::$interface, $q, $types];
} }
protected function decorateTypeSyntax(string $value, string $type): string { protected function decorateTypeSyntax(string $value, string $type): string {

2
tests/cases/Db/SQLite3PDO/TestStatement.php

@ -13,7 +13,7 @@ class TestStatement extends \JKingWeb\Arsse\TestCase\Db\BaseStatement {
protected static $implementation = "PDO SQLite 3"; protected static $implementation = "PDO SQLite 3";
protected function makeStatement(string $q, array $types = []): array { protected function makeStatement(string $q, array $types = []): array {
return [static::$interface, static::$interface->prepare($q), $types]; return [static::$interface, $q, $types];
} }
protected function decorateTypeSyntax(string $value, string $type): string { protected function decorateTypeSyntax(string $value, string $type): string {

2
tests/lib/DatabaseInformation.php

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

Loading…
Cancel
Save