1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +00:00
Arsse/lib/Db/AbstractStatement.php
J. King c4a41255b0 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
2019-01-10 19:01:32 -05:00

110 lines
4.3 KiB
PHP

<?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\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);
}
public function retype(...$bindings): bool {
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 {
if (!$append) {
$this->types = [];
}
foreach ($bindings as $binding) {
if (is_array($binding)) {
// recursively flatten any arrays, which may be provided for SET or IN() clauses
$this->retypeArray($binding, true);
} else {
$binding = trim(strtolower($binding));
if (strpos($binding, "strict ")===0) {
// "strict" types' values may never be null; null values will later be cast to the type specified
$this->isNullable[] = false;
$binding = substr($binding, 7);
} else {
$this->isNullable[] = true;
}
if (!array_key_exists($binding, self::TYPES)) {
throw new Exception("paramTypeInvalid", $binding); // @codeCoverageIgnore
}
$this->types[] = self::TYPES[$binding];
}
}
if (!$append) {
$this->prepare(static::mungeQuery($this->query, $this->types));
}
return true;
}
protected function cast($v, string $t, bool $nullable) {
switch ($t) {
case "datetime":
$v = Date::transform($v, "sql");
if (is_null($v) && !$nullable) {
$v = 0;
$v = Date::transform($v, "sql");
}
return $v;
case "integer":
return ValueInfo::normalize($v, ValueInfo::T_INT | ($nullable ? ValueInfo::M_NULL : 0), null, "sql");
case "float":
return ValueInfo::normalize($v, ValueInfo::T_FLOAT | ($nullable ? ValueInfo::M_NULL : 0), null, "sql");
case "binary":
case "string":
return ValueInfo::normalize($v, ValueInfo::T_STRING | ($nullable ? ValueInfo::M_NULL : 0), null, "sql");
case "boolean":
$v = ValueInfo::normalize($v, ValueInfo::T_BOOL | ($nullable ? ValueInfo::M_NULL : 0), null, "sql");
return is_null($v) ? $v : (int) $v;
default:
throw new Exception("paramTypeUnknown", $type); // @codeCoverageIgnore
}
}
protected function bindValues(array $values, int $offset = null): int {
$a = (int) $offset;
foreach ($values as $value) {
if (is_array($value)) {
// recursively flatten any arrays, which may be provided for SET or IN() clauses
$a += $this->bindValues($value, $a);
} elseif (array_key_exists($a, $this->types)) {
$value = $this->cast($value, $this->types[$a], $this->isNullable[$a]);
$this->bindValue($value, $this->types[$a], ++$a);
} else {
throw new Exception("paramTypeMissing", $a+1);
}
}
// once the last value is bound, check that all parameters have been supplied values and bind null for any missing ones
// SQLite will happily substitute null for a missing value, but other engines (viz. PostgreSQL) produce an error
if (is_null($offset)) {
while ($a < sizeof($this->types)) {
$this->bindValue(null, $this->types[$a], ++$a);
}
}
return $a - $offset;
}
}