2017-03-02 23:42:19 +00:00
|
|
|
<?php
|
2017-11-17 01:23:18 +00:00
|
|
|
/** @license MIT
|
|
|
|
* Copyright 2017 J. King, Dustin Wilson et al.
|
|
|
|
* See LICENSE and AUTHORS files for details */
|
|
|
|
|
2017-03-02 23:42:19 +00:00
|
|
|
declare(strict_types=1);
|
2017-03-28 04:12:12 +00:00
|
|
|
namespace JKingWeb\Arsse\Db;
|
2017-08-29 14:50:31 +00:00
|
|
|
|
2017-07-17 11:47:57 +00:00
|
|
|
use JKingWeb\Arsse\Misc\Date;
|
2017-12-30 23:50:56 +00:00
|
|
|
use JKingWeb\Arsse\Misc\ValueInfo;
|
2017-03-02 23:42:19 +00:00
|
|
|
|
|
|
|
abstract class AbstractStatement implements Statement {
|
2019-01-11 00:01:32 +00:00
|
|
|
use SQLState;
|
|
|
|
|
2019-03-01 17:17:33 +00:00
|
|
|
const TYPE_NORM_MAP = [
|
|
|
|
self::T_INTEGER => ValueInfo::M_NULL | ValueInfo::T_INT,
|
|
|
|
self::T_STRING => ValueInfo::M_NULL | ValueInfo::T_STRING,
|
|
|
|
self::T_BOOLEAN => ValueInfo::M_NULL | ValueInfo::T_BOOL,
|
|
|
|
self::T_DATETIME => ValueInfo::M_NULL | ValueInfo::T_DATE,
|
|
|
|
self::T_FLOAT => ValueInfo::M_NULL | ValueInfo::T_FLOAT,
|
|
|
|
self::T_BINARY => ValueInfo::M_NULL | ValueInfo::T_STRING,
|
|
|
|
self::T_NOT_NULL + self::T_INTEGER => ValueInfo::T_INT,
|
|
|
|
self::T_NOT_NULL + self::T_STRING => ValueInfo::T_STRING,
|
|
|
|
self::T_NOT_NULL + self::T_BOOLEAN => ValueInfo::T_BOOL,
|
|
|
|
self::T_NOT_NULL + self::T_DATETIME => ValueInfo::T_DATE,
|
|
|
|
self::T_NOT_NULL + self::T_FLOAT => ValueInfo::T_FLOAT,
|
|
|
|
self::T_NOT_NULL + self::T_BINARY => ValueInfo::T_STRING,
|
|
|
|
];
|
|
|
|
|
2017-05-04 23:12:33 +00:00
|
|
|
protected $types = [];
|
2017-03-02 23:42:19 +00:00
|
|
|
|
2017-08-29 14:50:31 +00:00
|
|
|
abstract public function runArray(array $values = []): Result;
|
2019-03-01 17:17:33 +00:00
|
|
|
abstract protected function bindValue($value, int $type, int $position): bool;
|
2018-12-21 17:35:10 +00:00
|
|
|
abstract protected function prepare(string $query): bool;
|
2019-01-11 00:01:32 +00:00
|
|
|
abstract protected static function buildEngineException($code, string $msg): array;
|
2017-03-02 23:42:19 +00:00
|
|
|
|
|
|
|
public function run(...$values): Result {
|
|
|
|
return $this->runArray($values);
|
|
|
|
}
|
|
|
|
|
2017-12-30 22:04:21 +00:00
|
|
|
public function retype(...$bindings): bool {
|
|
|
|
return $this->retypeArray($bindings);
|
2017-03-02 23:42:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-21 17:35:10 +00:00
|
|
|
public static function mungeQuery(string $query, array $types, ...$extraData): string {
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
2017-12-30 22:04:21 +00:00
|
|
|
public function retypeArray(array $bindings, bool $append = false): bool {
|
2017-08-29 14:50:31 +00:00
|
|
|
if (!$append) {
|
2017-07-21 02:40:09 +00:00
|
|
|
$this->types = [];
|
|
|
|
}
|
2017-08-29 14:50:31 +00:00
|
|
|
foreach ($bindings as $binding) {
|
|
|
|
if (is_array($binding)) {
|
2017-04-06 15:02:47 +00:00
|
|
|
// recursively flatten any arrays, which may be provided for SET or IN() clauses
|
2017-12-30 22:04:21 +00:00
|
|
|
$this->retypeArray($binding, true);
|
2017-04-06 15:02:47 +00:00
|
|
|
} else {
|
2019-03-01 17:17:33 +00:00
|
|
|
$bindId = self::TYPES[trim(strtolower($binding))] ?? 0;
|
|
|
|
if (!$bindId) {
|
2017-07-22 19:29:12 +00:00
|
|
|
throw new Exception("paramTypeInvalid", $binding); // @codeCoverageIgnore
|
2017-07-21 02:40:09 +00:00
|
|
|
}
|
2019-03-01 17:17:33 +00:00
|
|
|
$this->types[] = $bindId;
|
2017-04-06 15:02:47 +00:00
|
|
|
}
|
2017-03-02 23:42:19 +00:00
|
|
|
}
|
2018-12-21 17:35:10 +00:00
|
|
|
if (!$append) {
|
|
|
|
$this->prepare(static::mungeQuery($this->query, $this->types));
|
|
|
|
}
|
2017-03-02 23:42:19 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-03-01 17:17:33 +00:00
|
|
|
protected function cast($v, int $t) {
|
2017-08-29 14:50:31 +00:00
|
|
|
switch ($t) {
|
2019-03-01 17:17:33 +00:00
|
|
|
case self::T_DATETIME:
|
|
|
|
return Date::transform($v, "sql");
|
|
|
|
case self::T_DATETIME + self::T_NOT_NULL:
|
2017-12-30 23:50:56 +00:00
|
|
|
$v = Date::transform($v, "sql");
|
2019-03-01 17:17:33 +00:00
|
|
|
return $v ? $v : "0001-01-01 00:00:00";
|
2017-04-07 01:41:21 +00:00
|
|
|
default:
|
2019-03-01 17:17:33 +00:00
|
|
|
$v = ValueInfo::normalize($v, self::TYPE_NORM_MAP[$t], null, "sql");
|
|
|
|
return is_bool($v) ? (int) $v : $v;
|
2017-04-07 01:41:21 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-30 23:50:56 +00:00
|
|
|
|
2018-11-20 20:45:20 +00:00
|
|
|
protected function bindValues(array $values, int $offset = null): int {
|
|
|
|
$a = (int) $offset;
|
2017-12-30 23:50:56 +00:00
|
|
|
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)) {
|
2019-03-01 17:17:33 +00:00
|
|
|
$value = $this->cast($value, $this->types[$a]);
|
|
|
|
$this->bindValue($value, $this->types[$a] % self::T_NOT_NULL, ++$a);
|
2017-12-30 23:50:56 +00:00
|
|
|
} else {
|
|
|
|
throw new Exception("paramTypeMissing", $a+1);
|
|
|
|
}
|
|
|
|
}
|
2018-11-20 20:45:20 +00:00
|
|
|
// 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)) {
|
2019-03-01 17:17:33 +00:00
|
|
|
$this->bindValue(null, $this->types[$a] % self::T_NOT_NULL, ++$a);
|
2018-11-20 20:45:20 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-30 23:50:56 +00:00
|
|
|
return $a - $offset;
|
|
|
|
}
|
2017-08-29 14:50:31 +00:00
|
|
|
}
|