Преглед на файлове

Type conversion for #9

Added BOOLEAN to SQLite types.
Expanded DBStatement (started).
The fecth() method for INTEGER and BOOLEAN fields returns integer/float(?) and bool data.
Visman преди 3 години
родител
ревизия
1077d2c6b6
променени са 4 файла, в които са добавени 170 реда и са изтрити 4 реда
  1. 4 1
      app/Core/DB.php
  2. 70 0
      app/Core/DB/AbstractStatement.php
  3. 5 3
      app/Core/DB/Sqlite.php
  4. 91 0
      app/Core/DB/SqliteStatement.php

+ 4 - 1
app/Core/DB.php

@@ -66,8 +66,11 @@ class DB extends PDO
             throw new PDOException("Driver isn't found for '$type'");
         }
 
+        $statement = DBStatement::class;
+
         if ('sqlite' === $type) {
             $dsn = \str_replace('!PATH!', \realpath(__DIR__ . '/../config/db') . '/', $dsn);
+            $statement = 'ForkBB\\Core\\DB\\' . \ucfirst($type) . 'Statement';
         }
 
         $this->dbType   = $type;
@@ -78,7 +81,7 @@ class DB extends PDO
             self::ATTR_EMULATE_PREPARES   => false,
             self::ATTR_STRINGIFY_FETCHES  => false,
             self::ATTR_ERRMODE            => self::ERRMODE_EXCEPTION,
-            self::ATTR_STATEMENT_CLASS    => [DBStatement::class, [$this]],
+            self::ATTR_STATEMENT_CLASS    => [$statement, [$this]],
         ];
 
         $start  = \microtime(true);

+ 70 - 0
app/Core/DB/AbstractStatement.php

@@ -0,0 +1,70 @@
+<?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\DBStatement;
+use PDO;
+use PDOStatement;
+use PDOException;
+use RuntimeException;
+use ReturnTypeWillChange;
+
+abstract class AbstractStatement extends DBStatement
+{
+    const BOOLEAN = 'b';
+    const FLOAT   = 'f';
+    const INTEGER = 'i';
+    const STRING  = 's';
+
+    /**
+     * Типы столбцов полученные через getColumnMeta()
+     * @var array
+     */
+    protected $columnsType;
+
+    abstract public function getColumnsType(): array;
+    abstract protected function convToBoolean(/* mixed */ $value): bool;
+
+    #[ReturnTypeWillChange]
+    public function fetch(/* int */ $mode = 0 /* PDO::FETCH_DEFAULT */, /* int */ $cursorOrientation = PDO::FETCH_ORI_NEXT, /* int */ $cursorOffset = 0) /* : mixed */
+    {
+        $data = parent::fetch($mode, $cursorOrientation, $cursorOffset);
+
+        if (! \is_array($data)) {
+            return $data;
+        }
+
+        $types = $this->getColumnsType();
+
+        foreach ($data as $key => &$value) {
+            if (isset($types[$key])) {
+                switch ($types[$key]) {
+                    case self::INTEGER:
+                        $value += 0; // If the string is not a number, then Warning/Notice
+                                     // It can return not an integer, but a float.
+                        break;
+                    case self::BOOLEAN:
+                        $value = $this->convToBoolean($value);
+                        break;
+                    case self::FLOAT:
+                    case self::STRING:
+                        break;
+                    default:
+                        throw new RuntimeException("Unknown field type: '{$types[$key]}'");
+                }
+            }
+        }
+
+        unset($value);
+
+        return $data;
+    }
+}

+ 5 - 3
app/Core/DB/Sqlite.php

@@ -37,7 +37,8 @@ class Sqlite
         '%^.*?(?:CHAR|CLOB|TEXT).*$%i'            => 'TEXT',
         '%^.*?BLOB.*$%i'                          => 'BLOB',
         '%^.*?(?:REAL|FLOA|DOUB).*$%i'            => 'REAL',
-        '%^.*?(?:NUMERIC|DECIMAL|BOOL|DATE).*$%i' => 'NUMERIC',
+        '%^.*?(?:NUMERIC|DECIMAL).*$%i'           => 'NUMERIC',
+        '%^.*?BOOL.*$%i'                          => 'BOOLEAN', // ???? не соответствует SQLite
         '%^SERIAL$%i'                             => 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
     ];
 
@@ -46,9 +47,10 @@ class Sqlite
      * @var array
      */
     protected $types = [
+        'boolean' => 'b',
         'integer' => 'i',
-        'real'    => 'i',
-        'numeric' => 'i',
+        'real'    => 'f',
+        'numeric' => 'f',
     ];
 
     public function __construct(DB $db, string $prefix)

+ 91 - 0
app/Core/DB/SqliteStatement.php

@@ -0,0 +1,91 @@
+<?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;
+use PDOStatement;
+use PDOException;
+use ReturnTypeWillChange;
+
+class SqliteStatement 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 ($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;
+    }
+}