commit
7b922096f6
12 changed files with 311 additions and 314 deletions
269
app/Core/DB.php
269
app/Core/DB.php
|
@ -10,14 +10,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace ForkBB\Core;
|
||||
|
||||
use ForkBB\Core\DBStatement;
|
||||
use ForkBB\Core\DB\DBStatement;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PDOException;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class DB extends PDO
|
||||
class DB
|
||||
{
|
||||
/**
|
||||
* @var PDO
|
||||
*/
|
||||
protected $pdo;
|
||||
|
||||
/**
|
||||
* Префикс для таблиц базы
|
||||
* @var string
|
||||
|
@ -30,12 +34,24 @@ class DB extends PDO
|
|||
*/
|
||||
protected $dbType;
|
||||
|
||||
/**
|
||||
* Имя класса для драйвера
|
||||
* @var string
|
||||
*/
|
||||
protected $dbDrvClass;
|
||||
|
||||
/**
|
||||
* Драйвер текущей базы
|
||||
* @var //????
|
||||
*/
|
||||
protected $dbDrv;
|
||||
|
||||
/**
|
||||
* Имя класса для PDOStatement
|
||||
* @var string
|
||||
*/
|
||||
protected $statementClass;
|
||||
|
||||
/**
|
||||
* Количество выполненных запросов
|
||||
* @var int
|
||||
|
@ -54,93 +70,129 @@ class DB extends PDO
|
|||
*/
|
||||
protected $delta = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $pdoMethods = [
|
||||
'beginTransaction' => true,
|
||||
'commit' => true,
|
||||
'errorCode' => true,
|
||||
'errorInfo' => true,
|
||||
'exec' => true,
|
||||
'getAttribute' => true,
|
||||
'getAvailableDrivers' => true,
|
||||
'inTransaction' => true,
|
||||
'lastInsertId' => true,
|
||||
'prepare' => true,
|
||||
'query' => true,
|
||||
'quote' => true,
|
||||
'rollBack' => true,
|
||||
'setAttribute' => true,
|
||||
|
||||
'pgsqlCopyFromArray' => true,
|
||||
'pgsqlCopyFromFile' => true,
|
||||
'pgsqlCopyToArray' => true,
|
||||
'pgsqlCopyToFile' => true,
|
||||
'pgsqlGetNotify' => true,
|
||||
'pgsqlGetPid' => true,
|
||||
'pgsqlLOBCreate' => true,
|
||||
'pgsqlLOBOpen' => true,
|
||||
'pgsqlLOBUnlink' => true,
|
||||
|
||||
'sqliteCreateAggregate' => true,
|
||||
'sqliteCreateCollation' => true,
|
||||
'sqliteCreateFunction' => true,
|
||||
];
|
||||
|
||||
public function __construct(string $dsn, string $username = null, string $password = null, array $options = [], string $prefix = '')
|
||||
{
|
||||
$type = \strstr($dsn, ':', true);
|
||||
$typeU = \ucfirst($type);
|
||||
$dsn = $this->initialConfig($dsn);
|
||||
|
||||
$this->dbPrefix = $prefix;
|
||||
|
||||
list($initSQLCommands, $initFunction) = $this->prepareOptions($options);
|
||||
|
||||
$start = \microtime(true);
|
||||
$this->pdo = new PDO($dsn, $username, $password, $options);
|
||||
|
||||
$this->saveQuery('PDO::__construct()', \microtime(true) - $start, false);
|
||||
|
||||
if (\is_string($initSQLCommands)) {
|
||||
$this->exec($initSQLCommands);
|
||||
}
|
||||
|
||||
if (
|
||||
! $type
|
||||
|| ! \in_array($type, PDO::getAvailableDrivers(), true)
|
||||
|| ! \is_file(__DIR__ . "/DB/{$typeU}.php")
|
||||
null !== $initFunction
|
||||
&& true !== $initFunction($this)
|
||||
) {
|
||||
throw new PDOException("initFunction failure");
|
||||
}
|
||||
|
||||
$this->beginTransaction();
|
||||
}
|
||||
|
||||
protected function initialConfig(string $dsn): string
|
||||
{
|
||||
$type = \strstr($dsn, ':', true);
|
||||
|
||||
if (! \in_array($type, PDO::getAvailableDrivers(), true)) {
|
||||
throw new PDOException("PDO does not have driver for '{$type}'");
|
||||
}
|
||||
|
||||
$typeU = \ucfirst($type);
|
||||
|
||||
if (! \is_file(__DIR__ . "/DB/{$typeU}.php")) {
|
||||
throw new PDOException("Driver isn't found for '$type'");
|
||||
}
|
||||
|
||||
$statement = $typeU . 'Statement' . (\PHP_MAJOR_VERSION < 8 ? '7' : '');
|
||||
$this->dbType = $type;
|
||||
$this->dbDrvClass = "ForkBB\\Core\\DB\\{$typeU}";
|
||||
|
||||
if (\is_file(__DIR__ . "/DB/{$statement}.php")) {
|
||||
$statement = 'ForkBB\\Core\\DB\\' . $statement;
|
||||
if (\is_file(__DIR__ . "/DB/{$typeU}Statement.php")) {
|
||||
$this->statementClass = "ForkBB\\Core\\DB\\{$typeU}Statement";
|
||||
} else {
|
||||
$statement = DBStatement::class;
|
||||
$this->statementClass = DBStatement::class;
|
||||
}
|
||||
|
||||
if ('sqlite' === $type) {
|
||||
$dsn = \str_replace('!PATH!', \realpath(__DIR__ . '/../config/db') . '/', $dsn);
|
||||
}
|
||||
|
||||
$this->dbType = $type;
|
||||
$this->dbPrefix = $prefix;
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
protected function prepareOptions(array &$options): array
|
||||
{
|
||||
$result = [
|
||||
0 => null,
|
||||
1 => null,
|
||||
];
|
||||
|
||||
if (isset($options['initSQLCommands'])) {
|
||||
$initSQLCommands = \implode(';', $options['initSQLCommands']);
|
||||
$result[0] = \implode(';', $options['initSQLCommands']);
|
||||
|
||||
unset($options['initSQLCommands']);
|
||||
} else {
|
||||
$initSQLCommands = null;
|
||||
}
|
||||
|
||||
if (isset($options['initFunction'])) {
|
||||
$result[1] = $options['initFunction'];
|
||||
|
||||
unset($options['initFunction']);
|
||||
}
|
||||
|
||||
$options += [
|
||||
self::ATTR_DEFAULT_FETCH_MODE => self::FETCH_ASSOC,
|
||||
self::ATTR_EMULATE_PREPARES => false,
|
||||
self::ATTR_STRINGIFY_FETCHES => false,
|
||||
self::ATTR_ERRMODE => self::ERRMODE_EXCEPTION,
|
||||
self::ATTR_STATEMENT_CLASS => [$statement, [$this]],
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
];
|
||||
|
||||
$start = \microtime(true);
|
||||
|
||||
parent::__construct($dsn, $username, $password, $options);
|
||||
|
||||
$this->saveQuery('PDO::__construct()', \microtime(true) - $start, false);
|
||||
|
||||
if ($initSQLCommands) {
|
||||
$this->exec($initSQLCommands);
|
||||
}
|
||||
|
||||
$this->beginTransaction();
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Передает вызовы методов в драйвер текущей базы
|
||||
*/
|
||||
public function __call(string $name, array $args) /* : mixed */
|
||||
protected function dbStatement(PDOStatement $stmt): DBStatement
|
||||
{
|
||||
if (empty($this->dbDrv)) {
|
||||
$drv = 'ForkBB\\Core\\DB\\' . \ucfirst($this->dbType);
|
||||
$this->dbDrv = new $drv($this, $this->dbPrefix);
|
||||
}
|
||||
|
||||
return $this->dbDrv->$name(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод определяет массив ли опций подан на вход
|
||||
*/
|
||||
protected function isOptions(array $options): bool
|
||||
{
|
||||
$verify = [self::ATTR_CURSOR => [self::CURSOR_FWDONLY, self::CURSOR_SCROLL]];
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
if (
|
||||
! isset($verify[$key])
|
||||
|| ! \in_array($value, $verify[$key], true)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return new $this->statementClass($this, $stmt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,7 +259,7 @@ class DB extends PDO
|
|||
/**
|
||||
* Метод возвращает значение из массива параметров по ключу или исключение
|
||||
*/
|
||||
public function getValue(/* mixed */ $key, array $params) /* : mixed */
|
||||
public function getValue(/* int|string */ $key, array $params) /* : mixed */
|
||||
{
|
||||
if (
|
||||
\is_string($key)
|
||||
|
@ -259,6 +311,7 @@ class DB extends PDO
|
|||
if ($add) {
|
||||
++$this->qCount;
|
||||
}
|
||||
|
||||
$this->queries[] = [$query, $time + $this->delta];
|
||||
$this->delta = 0;
|
||||
}
|
||||
|
@ -266,23 +319,29 @@ class DB extends PDO
|
|||
/**
|
||||
* Метод расширяет PDO::exec()
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function exec(/* string */ $query, array $params = []) /* : int|false */
|
||||
public function exec(string $query, array $params = []) /* : int|false */
|
||||
{
|
||||
$map = $this->parse($query, $params);
|
||||
|
||||
if (empty($params)) {
|
||||
$start = \microtime(true);
|
||||
$result = parent::exec($query);
|
||||
$result = $this->pdo->exec($query);
|
||||
|
||||
$this->saveQuery($query, \microtime(true) - $start);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$start = \microtime(true);
|
||||
$stmt = parent::prepare($query);
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$this->delta = \microtime(true) - $start;
|
||||
|
||||
if (! $stmt instanceof PDOStatement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $this->dbStatement($stmt);
|
||||
|
||||
$stmt->setMap($map);
|
||||
|
||||
if ($stmt->execute($params)) {
|
||||
|
@ -295,31 +354,20 @@ class DB extends PDO
|
|||
/**
|
||||
* Метод расширяет PDO::prepare()
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function prepare(/* string */ $query, /* array */ $arg1 = null, /* array */ $arg2 = null): PDOStatement
|
||||
public function prepare(string $query, array $params = [], array $options = []) /* : DBStatement|false */
|
||||
{
|
||||
if (
|
||||
empty($arg1) === empty($arg2)
|
||||
|| ! empty($arg2)
|
||||
) {
|
||||
$params = $arg1;
|
||||
$options = $arg2;
|
||||
} elseif ($this->isOptions($arg1)) {
|
||||
$params = [];
|
||||
$options = $arg1;
|
||||
} else {
|
||||
$params = $arg1;
|
||||
$options = [];
|
||||
}
|
||||
|
||||
$map = $this->parse($query, $params);
|
||||
|
||||
$map = $this->parse($query, $params);
|
||||
$start = \microtime(true);
|
||||
$stmt = parent::prepare($query, $options);
|
||||
$stmt = $this->pdo->prepare($query, $options);
|
||||
$this->delta = \microtime(true) - $start;
|
||||
|
||||
$stmt->setMap($map);
|
||||
if (! $stmt instanceof PDOStatement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $this->dbStatement($stmt);
|
||||
|
||||
$stmt->setMap($map);
|
||||
$stmt->bindValueList($params);
|
||||
|
||||
return $stmt;
|
||||
|
@ -328,8 +376,7 @@ class DB extends PDO
|
|||
/**
|
||||
* Метод расширяет PDO::query()
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function query(string $query, /* mixed */ ...$args) /* : PDOStatement|false */
|
||||
public function query(string $query, /* mixed */ ...$args) /* : DBStatement|false */
|
||||
{
|
||||
if (
|
||||
isset($args[0])
|
||||
|
@ -343,17 +390,28 @@ class DB extends PDO
|
|||
$map = $this->parse($query, $params);
|
||||
|
||||
if (empty($params)) {
|
||||
$start = \microtime(true);
|
||||
$result = parent::query($query, ...$args);
|
||||
$start = \microtime(true);
|
||||
$stmt = $this->pdo->query($query, ...$args);
|
||||
|
||||
$this->saveQuery($query, \microtime(true) - $start);
|
||||
|
||||
return $result;
|
||||
if (! $stmt instanceof PDOStatement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->dbStatement($stmt);
|
||||
}
|
||||
|
||||
$start = \microtime(true);
|
||||
$stmt = parent::prepare($query);
|
||||
$stmt = $this->pdo->prepare($query);
|
||||
$this->delta = \microtime(true) - $start;
|
||||
|
||||
if (! $stmt instanceof PDOStatement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $this->dbStatement($stmt);
|
||||
|
||||
$stmt->setMap($map);
|
||||
|
||||
if ($stmt->execute($params)) {
|
||||
|
@ -373,7 +431,8 @@ class DB extends PDO
|
|||
public function beginTransaction(): bool
|
||||
{
|
||||
$start = \microtime(true);
|
||||
$result = parent::beginTransaction();
|
||||
$result = $this->pdo->beginTransaction();
|
||||
|
||||
$this->saveQuery('beginTransaction()', \microtime(true) - $start, false);
|
||||
|
||||
return $result;
|
||||
|
@ -385,7 +444,8 @@ class DB extends PDO
|
|||
public function commit(): bool
|
||||
{
|
||||
$start = \microtime(true);
|
||||
$result = parent::commit();
|
||||
$result = $this->pdo->commit();
|
||||
|
||||
$this->saveQuery('commit()', \microtime(true) - $start, false);
|
||||
|
||||
return $result;
|
||||
|
@ -397,9 +457,26 @@ class DB extends PDO
|
|||
public function rollback(): bool
|
||||
{
|
||||
$start = \microtime(true);
|
||||
$result = parent::rollback();
|
||||
$result = $this->pdo->rollback();
|
||||
|
||||
$this->saveQuery('rollback()', \microtime(true) - $start, false);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Передает вызовы метода в PDO или драйвер текущей базы
|
||||
*/
|
||||
public function __call(string $name, array $args) /* : mixed */
|
||||
{
|
||||
if (isset($this->pdoMethods[$name])) {
|
||||
return $this->pdo->$name(...$args);
|
||||
} elseif (empty($this->dbDrv)) {
|
||||
$this->dbDrv = new $this->dbDrvClass($this, $this->dbPrefix);
|
||||
|
||||
// ????? проверка типа
|
||||
}
|
||||
|
||||
return $this->dbDrv->$name(...$args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DB\AbstractStatement;
|
||||
use PDO;
|
||||
|
||||
abstract class AbstractSqliteStatement extends AbstractStatement
|
||||
{
|
||||
/**
|
||||
* https://github.com/php/php-src/blob/master/ext/pdo_sqlite/sqlite_statement.c
|
||||
*
|
||||
* SQLite:
|
||||
* native_type:
|
||||
* null - для значения NULL, а не типа столбца
|
||||
* integer - это INTEGER, NUMERIC(?), BOOLEAN // BOOLEAN тут как-то не к месту, его бы в отдельный тип
|
||||
* string - это TEXT
|
||||
* double - это REAL, NUMERIC(?) // NUMERIC может быть и double, и integer
|
||||
* sqlite:decl_type:
|
||||
* INTEGER
|
||||
* TEXT
|
||||
* REAL
|
||||
* NUMERIC
|
||||
* BOOLEAN
|
||||
* ... (это те типы, которые прописаны в CREATE TABLE и полученные после перекодировки из {driver}::bTypeRepl)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $nativeTypeRepl = [
|
||||
'integer' => self::INTEGER,
|
||||
'double' => self::FLOAT,
|
||||
];
|
||||
|
||||
public function getColumnsType(): array
|
||||
{
|
||||
if (isset($this->columnsType)) {
|
||||
return $this->columnsType;
|
||||
}
|
||||
|
||||
$this->columnsType = [];
|
||||
|
||||
$count = $this->columnCount();
|
||||
$i = 0;
|
||||
// $dbType = $this->db->getType();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$meta = $this->getColumnMeta($i);
|
||||
$type = null;
|
||||
// $declType = $meta[$dbType . ':decl_type'] ?? null;
|
||||
$declType = $meta['sqlite:decl_type'] ?? null;
|
||||
|
||||
if (null === $declType) {
|
||||
$type = $this->nativeTypeRepl[$meta['native_type']] ?? null;
|
||||
} elseif (\preg_match('%INT%i', $declType)) {
|
||||
$type = self::INTEGER;
|
||||
} elseif (\preg_match('%BOOL%i', $declType)) {
|
||||
$type = self::BOOLEAN;
|
||||
// } elseif (\preg_match('%REAL|FLOA|DOUB|NUMERIC|DECIMAL%i', $declType)) {
|
||||
// $type = self::FLOAT;
|
||||
}
|
||||
|
||||
if ($type) {
|
||||
$this->columnsType[$i] = $type;
|
||||
|
||||
if (isset($meta['name'])) { // ????? проверка на тип содержимого? только строки, не числа?
|
||||
$this->columnsType[$meta['name']] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->columnsType;
|
||||
}
|
||||
|
||||
protected function convToBoolean(/* mixed */ $value): bool
|
||||
{
|
||||
return (bool) $value;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DBStatement;
|
||||
use ForkBB\Core\DB\DBStatement;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PDOException;
|
||||
|
@ -68,14 +68,14 @@ abstract class AbstractStatement extends DBStatement
|
|||
}
|
||||
}
|
||||
|
||||
protected function dbSetFetchMode(int $mode, ...$args): bool
|
||||
public function setFetchMode(int $mode, ...$args): bool
|
||||
{
|
||||
$this->setFetchVars($mode, ...$args);
|
||||
|
||||
return parent::setFetchMode($mode, ...$args);
|
||||
return $this->stmt->setFetchMode($mode, ...$args);
|
||||
}
|
||||
|
||||
protected function dbFetch(int $mode = 0, int $orientation = PDO::FETCH_ORI_NEXT, int $offset = 0) /* : mixed */
|
||||
public function fetch(int $mode = 0, int $orientation = PDO::FETCH_ORI_NEXT, int $offset = 0) /* : mixed */
|
||||
{
|
||||
$this->okFetchColumn = false;
|
||||
|
||||
|
@ -86,7 +86,7 @@ abstract class AbstractStatement extends DBStatement
|
|||
$colNum = 0;
|
||||
}
|
||||
|
||||
$data = parent::fetch(
|
||||
$data = $this->stmt->fetch(
|
||||
PDO::FETCH_COLUMN === $mode ? PDO::FETCH_NUM : $mode,
|
||||
$orientation,
|
||||
$offset
|
||||
|
@ -130,7 +130,7 @@ abstract class AbstractStatement extends DBStatement
|
|||
return $data;
|
||||
}
|
||||
|
||||
protected function dbFetchAll(int $mode, ...$args): array
|
||||
public function fetchAll(int $mode = 0, ...$args): array
|
||||
{
|
||||
if (0 !== $mode) {
|
||||
$this->setFetchVars($mode, ...$args);
|
||||
|
@ -144,7 +144,7 @@ abstract class AbstractStatement extends DBStatement
|
|||
case PDO::FETCH_NUM:
|
||||
case PDO::FETCH_ASSOC:
|
||||
case PDO::FETCH_COLUMN:
|
||||
while (false !== ($data = $this->dbFetch()) || $this->okFetchColumn) {
|
||||
while (false !== ($data = $this->fetch()) || $this->okFetchColumn) {
|
||||
$result[] = $data;
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ abstract class AbstractStatement extends DBStatement
|
|||
throw new PDOException('General error: PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns');
|
||||
}
|
||||
|
||||
while (false !== ($data = $this->dbFetch(PDO::FETCH_NUM))) {
|
||||
while (false !== ($data = $this->fetch(PDO::FETCH_NUM))) {
|
||||
$result[$data[0]] = $data[1];
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ abstract class AbstractStatement extends DBStatement
|
|||
case PDO::FETCH_UNIQUE | PDO::FETCH_ASSOC:
|
||||
$this->fetchMode ^= PDO::FETCH_UNIQUE;
|
||||
|
||||
while (false !== ($data = $this->dbFetch())) {
|
||||
while (false !== ($data = $this->fetch())) {
|
||||
$key = \array_shift($data);
|
||||
$result[$key] = $data;
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ abstract class AbstractStatement extends DBStatement
|
|||
case PDO::FETCH_GROUP | PDO::FETCH_ASSOC:
|
||||
$this->fetchMode ^= PDO::FETCH_GROUP;
|
||||
|
||||
while (false !== ($data = $this->dbFetch())) {
|
||||
while (false !== ($data = $this->fetch())) {
|
||||
$key = \array_shift($data);
|
||||
|
||||
if (PDO::FETCH_BOTH === $this->fetchMode) {
|
||||
|
@ -195,10 +195,9 @@ abstract class AbstractStatement extends DBStatement
|
|||
default:
|
||||
throw new PDOException('AbstractStatement class does not support this type for fetchAll(): ' . $this->fetchMode);
|
||||
|
||||
return parent::fetchAll($mode, ...$args);
|
||||
return $this->stmt->fetchAll($mode, ...$args);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core;
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DB;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PDOException;
|
||||
|
||||
class DBStatement extends PDOStatement
|
||||
class DBStatement
|
||||
{
|
||||
const BOOLEAN = 'b';
|
||||
const FLOAT = 'f';
|
||||
|
@ -22,11 +23,15 @@ class DBStatement extends PDOStatement
|
|||
const STRING = 's';
|
||||
|
||||
/**
|
||||
* Префикс для таблиц базы
|
||||
* @var PDO
|
||||
* @var DB
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @var PDOStatement
|
||||
*/
|
||||
protected $stmt;
|
||||
|
||||
/**
|
||||
* Карта преобразования переменных
|
||||
* @var array
|
||||
|
@ -47,9 +52,10 @@ class DBStatement extends PDOStatement
|
|||
'as' => PDO::PARAM_STR,
|
||||
];
|
||||
|
||||
protected function __construct(PDO $db)
|
||||
public function __construct(DB $db, PDOStatement $stmt)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->db = $db;
|
||||
$this->stmt = $stmt;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,12 +80,13 @@ class DBStatement extends PDOStatement
|
|||
if (! \is_array($bValue)) {
|
||||
throw new PDOException("Expected array: key='{$key}'");
|
||||
}
|
||||
|
||||
foreach ($data as $bParam) {
|
||||
parent::bindValue($bParam, \array_shift($bValue), $bType); //????
|
||||
$this->stmt->bindValue($bParam, \array_shift($bValue), $bType); //????
|
||||
}
|
||||
} else {
|
||||
foreach ($data as $bParam) {
|
||||
parent::bindValue($bParam, $bValue, $bType); //????
|
||||
$this->stmt->bindValue($bParam, $bValue, $bType); //????
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +95,7 @@ class DBStatement extends PDOStatement
|
|||
/**
|
||||
* Метод расширяет PDOStatement::execute()
|
||||
*/
|
||||
public function execute(/* array */ $params = null): bool
|
||||
public function execute(array $params = null): bool
|
||||
{
|
||||
if (
|
||||
\is_array($params)
|
||||
|
@ -96,10 +103,20 @@ class DBStatement extends PDOStatement
|
|||
) {
|
||||
$this->bindValueList($params);
|
||||
}
|
||||
|
||||
$start = \microtime(true);
|
||||
$result = parent::execute();
|
||||
$this->db->saveQuery($this->queryString, \microtime(true) - $start);
|
||||
$result = $this->stmt->execute();
|
||||
|
||||
$this->db->saveQuery($this->stmt->queryString, \microtime(true) - $start);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Передает вызовы метода в PDOStatement
|
||||
*/
|
||||
public function __call(string $name, array $args) /* : mixed */
|
||||
{
|
||||
return $this->stmt->$name(...$args);
|
||||
}
|
||||
}
|
|
@ -10,26 +10,79 @@ declare(strict_types=1);
|
|||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DB\AbstractSqliteStatement;
|
||||
use ForkBB\Core\DB\AbstractStatement;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* For PHP 8
|
||||
*/
|
||||
class SqliteStatement extends AbstractSqliteStatement
|
||||
class SqliteStatement extends AbstractStatement
|
||||
{
|
||||
public function fetch(int $mode = 0 /* PDO::FETCH_DEFAULT */, int $orientation = PDO::FETCH_ORI_NEXT, int $offset = 0): mixed
|
||||
/**
|
||||
* https://github.com/php/php-src/blob/master/ext/pdo_sqlite/sqlite_statement.c
|
||||
*
|
||||
* SQLite:
|
||||
* native_type:
|
||||
* null - для значения NULL, а не типа столбца
|
||||
* integer - это INTEGER, NUMERIC(?), BOOLEAN // BOOLEAN тут как-то не к месту, его бы в отдельный тип
|
||||
* string - это TEXT
|
||||
* double - это REAL, NUMERIC(?) // NUMERIC может быть и double, и integer
|
||||
* sqlite:decl_type:
|
||||
* INTEGER
|
||||
* TEXT
|
||||
* REAL
|
||||
* NUMERIC
|
||||
* BOOLEAN
|
||||
* ... (это те типы, которые прописаны в CREATE TABLE и полученные после перекодировки из {driver}::bTypeRepl)
|
||||
*/
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $nativeTypeRepl = [
|
||||
'integer' => self::INTEGER,
|
||||
'double' => self::FLOAT,
|
||||
];
|
||||
|
||||
public function getColumnsType(): array
|
||||
{
|
||||
return $this->dbFetch($mode, $orientation, $offset);
|
||||
if (isset($this->columnsType)) {
|
||||
return $this->columnsType;
|
||||
}
|
||||
|
||||
$this->columnsType = [];
|
||||
|
||||
$count = $this->columnCount();
|
||||
$i = 0;
|
||||
// $dbType = $this->db->getType();
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$meta = $this->getColumnMeta($i);
|
||||
$type = null;
|
||||
// $declType = $meta[$dbType . ':decl_type'] ?? null;
|
||||
$declType = $meta['sqlite:decl_type'] ?? null;
|
||||
|
||||
if (null === $declType) {
|
||||
$type = $this->nativeTypeRepl[$meta['native_type']] ?? null;
|
||||
} elseif (\preg_match('%INT%i', $declType)) {
|
||||
$type = self::INTEGER;
|
||||
} elseif (\preg_match('%BOOL%i', $declType)) {
|
||||
$type = self::BOOLEAN;
|
||||
// } elseif (\preg_match('%REAL|FLOA|DOUB|NUMERIC|DECIMAL%i', $declType)) {
|
||||
// $type = self::FLOAT;
|
||||
}
|
||||
|
||||
if ($type) {
|
||||
$this->columnsType[$i] = $type;
|
||||
|
||||
if (isset($meta['name'])) { // ????? проверка на тип содержимого? только строки, не числа?
|
||||
$this->columnsType[$meta['name']] = $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->columnsType;
|
||||
}
|
||||
|
||||
public function fetchAll(int $mode = 0 /* PDO::FETCH_DEFAULT */, ...$args): array
|
||||
protected function convToBoolean(/* mixed */ $value): bool
|
||||
{
|
||||
return $this->dbFetchAll($mode, ...$args);
|
||||
}
|
||||
|
||||
public function setFetchMode(int $mode, ...$args): bool
|
||||
{
|
||||
return $this->dbSetFetchMode($mode, ...$args);
|
||||
return (bool) $value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of the ForkBB <https://github.com/forkbb>.
|
||||
*
|
||||
* @copyright (c) Visman <mio.visman@yandex.ru, https://github.com/MioVisman>
|
||||
* @license The MIT License (MIT)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ForkBB\Core\DB;
|
||||
|
||||
use ForkBB\Core\DB\AbstractSqliteStatement;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* For PHP 7
|
||||
*/
|
||||
class SqliteStatement7 extends AbstractSqliteStatement
|
||||
{
|
||||
public function fetch($mode = null, $orientation = null, $offset = null)
|
||||
{
|
||||
$mode = $mode ?? 0;
|
||||
$orientation = $orientation ?? PDO::FETCH_ORI_NEXT;
|
||||
$offset = $offset ?? 0;
|
||||
|
||||
return $this->dbFetch($mode, $orientation, $offset);
|
||||
}
|
||||
|
||||
public function fetchAll($mode = null, $fetchArg = null, $ctorArgs = null)
|
||||
{
|
||||
$mode = $mode ?? 0;
|
||||
$args = $this->returnArgs($fetchArg, $ctorArgs);
|
||||
|
||||
return $this->dbFetchAll($mode, ...$args);
|
||||
}
|
||||
|
||||
public function setFetchMode($mode, $fetchArg = null, $ctorArgs = null): bool
|
||||
{
|
||||
$args = $this->returnArgs($fetchArg, $ctorArgs);
|
||||
|
||||
return $this->dbSetFetchMode($mode, ...$args);
|
||||
}
|
||||
|
||||
protected function returnArgs($fetchArg, $ctorArgs): array
|
||||
{
|
||||
$args = [];
|
||||
|
||||
if (isset($fetchArg)) {
|
||||
$args[] = $fetchArg;
|
||||
|
||||
if (isset($ctorArgs)) {
|
||||
$args[] = $ctorArgs;
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
|
@ -506,12 +506,17 @@ class Install extends Admin
|
|||
break;
|
||||
case 'sqlite':
|
||||
$this->c->DB_DSN = "sqlite:!PATH!{$dbname}";
|
||||
$this->c->DB_OPTS_AS_STR = '\\PDO::ATTR_TIMEOUT => 5, /* \'initSQLCommands\' => [\'PRAGMA journal_mode=WAL\',], */';
|
||||
$this->c->DB_OPTS_AS_STR = "\n"
|
||||
. ' \\PDO::ATTR_TIMEOUT => 5,' . "\n"
|
||||
. ' /* \'initSQLCommands\' => [\'PRAGMA journal_mode=WAL\',], */' . "\n"
|
||||
. ' \'initFunction\' => function ($db) {return $db->sqliteCreateFunction(\'CONCAT\', function (...$args) {return \\implode(\'\', $args);});},' . "\n"
|
||||
. ' ';
|
||||
$this->c->DB_OPTIONS = [
|
||||
PDO::ATTR_TIMEOUT => 5,
|
||||
'initSQLCommands' => [
|
||||
'PRAGMA journal_mode=WAL',
|
||||
],
|
||||
'initFunction' => function ($db) {return $db->sqliteCreateFunction('CONCAT', function (...$args) {return \implode('', $args);});},
|
||||
];
|
||||
|
||||
break;
|
||||
|
|
|
@ -179,7 +179,7 @@ class Delete extends Action
|
|||
}
|
||||
|
||||
if ($uidsUpdate) {
|
||||
$this->c->users->UpdateCountTopics(...$uidsUpdate);
|
||||
$this->c->users->updateCountTopics(...$uidsUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,26 +54,19 @@ class Merge extends Action
|
|||
}
|
||||
|
||||
//???? перенести обработку в посты?
|
||||
$vars = [
|
||||
'start' => "[from]",
|
||||
'end' => "[/from]\n",
|
||||
'topics' => $ids,
|
||||
];
|
||||
$query = 'UPDATE ::posts AS p, ::topics as t
|
||||
SET p.message=CONCAT(?s:start, t.subject, ?s:end, p.message)
|
||||
WHERE p.topic_id IN (?ai:topics) AND t.id=p.topic_id';
|
||||
$query = 'UPDATE ::posts
|
||||
SET message=CONCAT(?s:prefix, message), topic_id=?i:new
|
||||
WHERE topic_id=?i:id';
|
||||
|
||||
$this->c->DB->exec($query, $vars);
|
||||
foreach ($otherTopics as $topic) {
|
||||
$vars = [
|
||||
':new' => $firstTopic->id,
|
||||
':id' => $topic->id,
|
||||
':prefix' => "[from]{$topic->subject}[/from]\n",
|
||||
];
|
||||
|
||||
$vars = [
|
||||
'id' => $firstTopic->id,
|
||||
'topics' => $ids,
|
||||
];
|
||||
$query = 'UPDATE ::posts AS p
|
||||
SET p.topic_id=?i:id
|
||||
WHERE p.topic_id IN (?ai:topics)';
|
||||
|
||||
$this->c->DB->exec($query, $vars);
|
||||
$this->c->DB->exec($query, $vars);
|
||||
}
|
||||
|
||||
// добавить перенос подписок на первую тему?
|
||||
|
||||
|
@ -104,7 +97,7 @@ class Merge extends Action
|
|||
}
|
||||
|
||||
if ($users) {
|
||||
$this->c->users->UpdateCountTopics(...$users);
|
||||
$this->c->users->updateCountTopics(...$users);
|
||||
}
|
||||
} else {
|
||||
$this->c->topics->update($firstTopic->calcStat());
|
||||
|
|
|
@ -71,9 +71,9 @@ class ChangeGroup extends Action
|
|||
':new' => $newGroupId,
|
||||
':ids' => $ids,
|
||||
];
|
||||
$query = 'UPDATE ::users AS u
|
||||
SET u.group_id = ?i:new
|
||||
WHERE u.id IN (?ai:ids)';
|
||||
$query = 'UPDATE ::users
|
||||
SET group_id = ?i:new
|
||||
WHERE id IN (?ai:ids)';
|
||||
|
||||
$this->c->DB->exec($query, $vars);
|
||||
|
||||
|
|
|
@ -38,22 +38,22 @@ class UpdateCountPosts extends Action
|
|||
unset($ids[0]); // ????
|
||||
|
||||
if (empty($ids)) {
|
||||
$where = 'u.id > 0';
|
||||
$where = '::users.id > 0';
|
||||
$vars = [];
|
||||
} else {
|
||||
$where = 'u.id IN (?ai:ids)';
|
||||
$where = '::users.id IN (?ai:ids)';
|
||||
$vars = [
|
||||
':ids' => \array_keys($ids),
|
||||
];
|
||||
}
|
||||
|
||||
$query = 'UPDATE ::users AS u
|
||||
SET u.num_posts = COALESCE((
|
||||
$query = 'UPDATE ::users
|
||||
SET num_posts = COALESCE((
|
||||
SELECT COUNT(p.id)
|
||||
FROM ::posts AS p
|
||||
INNER JOIN ::topics AS t ON t.id=p.topic_id
|
||||
INNER JOIN ::forums AS f ON f.id=t.forum_id
|
||||
WHERE p.poster_id=u.id AND f.no_sum_mess=0
|
||||
WHERE p.poster_id=::users.id AND f.no_sum_mess=0
|
||||
GROUP BY p.poster_id
|
||||
), 0)
|
||||
WHERE ' . $where;
|
||||
|
|
|
@ -38,21 +38,21 @@ class UpdateCountTopics extends Action
|
|||
unset($ids[0]); // ????
|
||||
|
||||
if (empty($ids)) {
|
||||
$where = 'u.id > 0';
|
||||
$where = '::users.id > 0';
|
||||
$vars = [];
|
||||
} else {
|
||||
$where = 'u.id IN (?ai:ids)';
|
||||
$where = '::users.id IN (?ai:ids)';
|
||||
$vars = [
|
||||
':ids' => \array_keys($ids),
|
||||
];
|
||||
}
|
||||
|
||||
$query = 'UPDATE ::users AS u
|
||||
SET u.num_topics = COALESCE((
|
||||
$query = 'UPDATE ::users
|
||||
SET num_topics = COALESCE((
|
||||
SELECT COUNT(t.id)
|
||||
FROM ::topics AS t
|
||||
INNER JOIN ::posts AS p ON t.first_post_id=p.id
|
||||
WHERE p.poster_id=u.id AND t.moved_to=0
|
||||
WHERE p.poster_id=::users.id AND t.moved_to=0
|
||||
GROUP BY p.poster_id
|
||||
), 0)
|
||||
WHERE ' . $where;
|
||||
|
|
Loading…
Add table
Reference in a new issue