浏览代码

Merge pull request #11 from forkbb/db

Change DB and DBStatement
Visman 3 年之前
父节点
当前提交
7b922096f6

+ 172 - 95
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;
         }
 
-        $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]],
-        ];
-
-        $start  = \microtime(true);
+        if (isset($options['initFunction'])) {
+            $result[1] = $options['initFunction'];
 
-        parent::__construct($dsn, $username, $password, $options);
-
-        $this->saveQuery('PDO::__construct()', \microtime(true) - $start, false);
-
-        if ($initSQLCommands) {
-            $this->exec($initSQLCommands);
+            unset($options['initFunction']);
         }
 
-        $this->beginTransaction();
-    }
-
-    /**
-     * Передает вызовы методов в драйвер текущей базы
-     */
-    public function __call(string $name, array $args) /* : mixed */
-    {
-        if (empty($this->dbDrv)) {
-            $drv = 'ForkBB\\Core\\DB\\' . \ucfirst($this->dbType);
-            $this->dbDrv = new $drv($this, $this->dbPrefix);
-        }
+        $options += [
+            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+            PDO::ATTR_EMULATE_PREPARES   => false,
+            PDO::ATTR_STRINGIFY_FETCHES  => false,
+            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
+        ];
 
-        return $this->dbDrv->$name(...$args);
+        return $result;
     }
 
-    /**
-     * Метод определяет массив ли опций подан на вход
-     */
-    protected function isOptions(array $options): bool
+    protected function dbStatement(PDOStatement $stmt): DBStatement
     {
-        $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);
+    }
 }

+ 0 - 88
app/Core/DB/AbstractSqliteStatement.php

@@ -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;
-    }
-}

+ 11 - 12
app/Core/DB/AbstractStatement.php

@@ -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;
-
     }
 }

+ 28 - 11
app/Core/DBStatement.php → app/Core/DB/DBStatement.php

@@ -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);
+    }
 }

+ 66 - 13
app/Core/DB/SqliteStatement.php

@@ -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
-    {
-        return $this->dbFetch($mode, $orientation, $offset);
-    }
+    /**
+     * 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 fetchAll(int $mode = 0 /* PDO::FETCH_DEFAULT */, ...$args): array
+    public function getColumnsType(): array
     {
-        return $this->dbFetchAll($mode, ...$args);
+        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 setFetchMode(int $mode, ...$args): bool
+    protected function convToBoolean(/* mixed */ $value): bool
     {
-        return $this->dbSetFetchMode($mode, ...$args);
+        return (bool) $value;
     }
 }

+ 0 - 59
app/Core/DB/SqliteStatement7.php

@@ -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;
-    }
-}

+ 6 - 1
app/Models/Pages/Admin/Install.php

@@ -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;

+ 1 - 1
app/Models/Topic/Delete.php

@@ -179,7 +179,7 @@ class Delete extends Action
         }
 
         if ($uidsUpdate) {
-            $this->c->users->UpdateCountTopics(...$uidsUpdate);
+            $this->c->users->updateCountTopics(...$uidsUpdate);
         }
     }
 }

+ 14 - 21
app/Models/Topic/Merge.php

@@ -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';
-
-        $this->c->DB->exec($query, $vars);
-
-        $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);
+        $query = 'UPDATE ::posts
+            SET message=CONCAT(?s:prefix, message), topic_id=?i:new
+            WHERE topic_id=?i:id';
+
+        foreach ($otherTopics as $topic) {
+            $vars = [
+                ':new'    => $firstTopic->id,
+                ':id'     => $topic->id,
+                ':prefix' => "[from]{$topic->subject}[/from]\n",
+            ];
+
+            $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());

+ 3 - 3
app/Models/User/ChangeGroup.php

@@ -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);
 

+ 5 - 5
app/Models/User/UpdateCountPosts.php

@@ -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;

+ 5 - 5
app/Models/User/UpdateCountTopics.php

@@ -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;