]> git.mxchange.org Git - friendica.git/blobdiff - src/Database/Database.php
Added description
[friendica.git] / src / Database / Database.php
index 273c2985c63f250688f2e90ba510cd758ec6ee17..88d8d7d0f6ef87baa78cbc7f63dcd2cceeb65c15 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @copyright Copyright (C) 2020, Friendica
+ * @copyright Copyright (C) 2010-2022, the Friendica project
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -21,9 +21,9 @@
 
 namespace Friendica\Database;
 
-use Friendica\Core\Config\Cache;
+use Friendica\Core\Config\ValueObject\Cache;
 use Friendica\Core\System;
-use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Network\HTTPException\ServiceUnavailableException;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
 use mysqli;
@@ -42,10 +42,14 @@ class Database
        const PDO = 'pdo';
        const MYSQLI = 'mysqli';
 
+       const INSERT_DEFAULT = 0;
+       const INSERT_UPDATE = 1;
+       const INSERT_IGNORE = 2;
+
        protected $connected = false;
 
        /**
-        * @var Cache
+        * @var \Friendica\Core\Config\ValueObject\Cache
         */
        protected $configCache;
        /**
@@ -60,7 +64,6 @@ class Database
        /** @var PDO|mysqli */
        protected $connection;
        protected $driver;
-       protected $emulate_prepares = false;
        protected $pdo_emulate_prepares = false;
        private $error          = false;
        private $errorno        = 0;
@@ -111,6 +114,7 @@ class Database
                $pass    = trim($this->configCache->get('database', 'password'));
                $db      = trim($this->configCache->get('database', 'database'));
                $charset = trim($this->configCache->get('database', 'charset'));
+               $socket  = trim($this->configCache->get('database', 'socket')); 
 
                if (!(strlen($server) && strlen($user))) {
                        return false;
@@ -118,7 +122,6 @@ class Database
 
                $persistent = (bool)$this->configCache->get('database', 'persistent');
 
-               $this->emulate_prepares = (bool)$this->configCache->get('database', 'emulate_prepares');
                $this->pdo_emulate_prepares = (bool)$this->configCache->get('database', 'pdo_emulate_prepares');
 
                if (!$this->configCache->get('database', 'disable_pdo') && class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
@@ -133,9 +136,14 @@ class Database
                                $connect .= ";charset=" . $charset;
                        }
 
+                       if ($socket) {
+                               $connect .= ";$unix_socket=" . $socket;
+                       }
+
                        try {
                                $this->connection = @new PDO($connect, $user, $pass, [PDO::ATTR_PERSISTENT => $persistent]);
                                $this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares);
+                               $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
                                $this->connected = true;
                        } catch (PDOException $e) {
                                $this->connected = false;
@@ -157,6 +165,11 @@ class Database
                                if ($charset) {
                                        $this->connection->set_charset($charset);
                                }
+
+                               if ($socket) {
+                                       $this->connection->set_socket($socket);
+                               }
+
                        }
                }
 
@@ -432,7 +445,7 @@ class Database
        private function replaceParameters($sql, $args)
        {
                $offset = 0;
-               foreach ($args AS $param => $value) {
+               foreach ($args as $param => $value) {
                        if (is_int($args[$param]) || is_float($args[$param]) || is_bool($args[$param])) {
                                $replace = intval($args[$param]);
                        } elseif (is_null($args[$param])) {
@@ -453,7 +466,7 @@ class Database
        /**
         * Executes a prepared statement that returns data
         *
-        * @usage Example: $r = p("SELECT * FROM `item` WHERE `guid` = ?", $guid);
+        * @usage Example: $r = p("SELECT * FROM `post` WHERE `guid` = ?", $guid);
         *
         * Please only use it with complicated queries.
         * For all regular queries please use DBA::select or DBA::exists
@@ -466,6 +479,7 @@ class Database
        public function p($sql)
        {
 
+               $this->profiler->startRecording('database');
                $stamp1 = microtime(true);
 
                $params = DBA::getParam(func_get_args());
@@ -473,7 +487,7 @@ class Database
                // Renumber the array keys to be sure that they fit
                $i    = 0;
                $args = [];
-               foreach ($params AS $param) {
+               foreach ($params as $param) {
                        // Avoid problems with some MySQL servers and boolean values. See issue #3645
                        if (is_bool($param)) {
                                $param = (int)$param;
@@ -517,13 +531,13 @@ class Database
                $called_from_e = ($called_from['function'] == 'e');
 
                if (!isset($this->connection)) {
-                       throw new InternalServerErrorException('The Connection is empty, although connected is set true.');
+                       throw new ServiceUnavailableException('The Connection is empty, although connected is set true.');
                }
 
                switch ($this->driver) {
                        case self::PDO:
                                // If there are no arguments we use "query"
-                               if ($this->emulate_prepares || count($args) == 0) {
+                               if (count($args) == 0) {
                                        if (!$retval = $this->connection->query($this->replaceParameters($sql, $args))) {
                                                $errorInfo     = $this->connection->errorInfo();
                                                $this->error   = $errorInfo[2];
@@ -546,12 +560,14 @@ class Database
                                        break;
                                }
 
-                               foreach ($args AS $param => $value) {
+                               foreach (array_keys($args) as $param) {
+                                       $data_type = PDO::PARAM_STR;
                                        if (is_int($args[$param])) {
                                                $data_type = PDO::PARAM_INT;
-                                       } else {
-                                               $data_type = PDO::PARAM_STR;
+                                       } elseif ($args[$param] !== null) {
+                                               $args[$param] = (string)$args[$param];
                                        }
+
                                        $stmt->bindParam($param, $args[$param], $data_type);
                                }
 
@@ -573,7 +589,7 @@ class Database
                                $can_be_prepared = in_array($command, ['select', 'update', 'insert', 'delete']);
 
                                // The fallback routine is called as well when there are no arguments
-                               if ($this->emulate_prepares || !$can_be_prepared || (count($args) == 0)) {
+                               if (!$can_be_prepared || (count($args) == 0)) {
                                        $retval = $this->connection->query($this->replaceParameters($sql, $args));
                                        if ($this->connection->errno) {
                                                $this->error   = $this->connection->error;
@@ -602,13 +618,16 @@ class Database
 
                                $param_types = '';
                                $values      = [];
-                               foreach ($args AS $param => $value) {
+                               foreach (array_keys($args) as $param) {
                                        if (is_int($args[$param])) {
                                                $param_types .= 'i';
                                        } elseif (is_float($args[$param])) {
                                                $param_types .= 'd';
                                        } elseif (is_string($args[$param])) {
                                                $param_types .= 's';
+                                       } elseif (is_object($args[$param]) && method_exists($args[$param], '__toString')) {
+                                               $param_types .= 's';
+                                               $args[$param] = (string)$args[$param];
                                        } else {
                                                $param_types .= 'b';
                                        }
@@ -693,7 +712,7 @@ class Database
                        $this->errorno = $errorno;
                }
 
-               $this->profiler->saveTimestamp($stamp1, 'database');
+               $this->profiler->stopRecording();
 
                if ($this->configCache->get('system', 'db_log')) {
                        $stamp2   = microtime(true);
@@ -725,7 +744,7 @@ class Database
        public function e($sql)
        {
 
-               $stamp = microtime(true);
+               $this->profiler->startRecording('database_write');
 
                $params = DBA::getParam(func_get_args());
 
@@ -777,7 +796,7 @@ class Database
                        $this->errorno = $errorno;
                }
 
-               $this->profiler->saveTimestamp($stamp, "database_write");
+               $this->profiler->stopRecording();
 
                return $retval;
        }
@@ -906,14 +925,13 @@ class Database
        /**
         * Fetch a single row
         *
-        * @param mixed $stmt statement object
+        * @param bool|PDOStatement|mysqli_stmt $stmt statement object
         *
-        * @return array current row
+        * @return array|false current row
         */
        public function fetch($stmt)
        {
-
-               $stamp1 = microtime(true);
+               $this->profiler->startRecording('database');
 
                $columns = [];
 
@@ -924,10 +942,13 @@ class Database
                switch ($this->driver) {
                        case self::PDO:
                                $columns = $stmt->fetch(PDO::FETCH_ASSOC);
+                               if (!empty($stmt->table) && is_array($columns)) {
+                                       $columns = $this->castFields($stmt->table, $columns);
+                               }
                                break;
                        case self::MYSQLI:
                                if (get_class($stmt) == 'mysqli_result') {
-                                       $columns = $stmt->fetch_assoc();
+                                       $columns = $stmt->fetch_assoc() ?? false;
                                        break;
                                }
 
@@ -953,42 +974,50 @@ class Database
                                $result = $stmt->result_metadata();
                                $fields = $result->fetch_fields();
 
-                               foreach ($cols_num AS $param => $col) {
+                               foreach ($cols_num as $param => $col) {
                                        $columns[$fields[$param]->name] = $col;
                                }
                }
 
-               $this->profiler->saveTimestamp($stamp1, 'database');
+               $this->profiler->stopRecording();
 
                return $columns;
        }
 
        /**
-        * Insert a row into a table
+        * Insert a row into a table. Field value objects will be cast as string.
         *
-        * @param string|array $table               Table name or array [schema => table]
-        * @param array        $param               parameter array
-        * @param bool         $on_duplicate_update Do an update on a duplicate entry
+        * @param string|array $table          Table name or array [schema => table]
+        * @param array        $param          parameter array
+        * @param int          $duplicate_mode What to do on a duplicated entry
         *
         * @return boolean was the insert successful?
         * @throws \Exception
         */
-       public function insert($table, array $param, bool $on_duplicate_update = false)
+       public function insert($table, array $param, int $duplicate_mode = self::INSERT_DEFAULT)
        {
                if (empty($table) || empty($param)) {
                        $this->logger->info('Table and fields have to be set');
                        return false;
                }
 
+               $param = $this->castFields($table, $param);
+
                $table_string = DBA::buildTableString($table);
 
                $fields_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], array_keys($param)));
 
                $values_string = substr(str_repeat("?, ", count($param)), 0, -2);
 
-               $sql = "INSERT INTO " . $table_string . " (" . $fields_string . ") VALUES (" . $values_string . ")";
+               $sql = "INSERT ";
 
-               if ($on_duplicate_update) {
+               if ($duplicate_mode == self::INSERT_IGNORE) {
+                       $sql .= "IGNORE ";
+               }
+
+               $sql .= "INTO " . $table_string . " (" . $fields_string . ") VALUES (" . $values_string . ")";
+
+               if ($duplicate_mode == self::INSERT_UPDATE) {
                        $fields_string = implode(' = ?, ', array_map([DBA::class, 'quoteIdentifier'], array_keys($param)));
 
                        $sql .= " ON DUPLICATE KEY UPDATE " . $fields_string . " = ?";
@@ -997,7 +1026,12 @@ class Database
                        $param  = array_merge_recursive($values, $values);
                }
 
-               return $this->e($sql, $param);
+               $result = $this->e($sql, $param);
+               if (!$result || ($duplicate_mode != self::INSERT_IGNORE)) {
+                       return $result;
+               }
+
+               return $this->affectedRows() != 0;
        }
 
        /**
@@ -1017,6 +1051,8 @@ class Database
                        return false;
                }
 
+               $param = $this->castFields($table, $param);
+
                $table_string = DBA::buildTableString($table);
 
                $fields_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], array_keys($param)));
@@ -1198,181 +1234,35 @@ class Database
                return $ret;
        }
 
-       /**
-        * Build the array with the table relations
-        *
-        * The array is build from the database definitions in DBStructure.php
-        *
-        * This process must only be started once, since the value is cached.
-        */
-       private function buildRelationData()
-       {
-               $definition = DBStructure::definition($this->configCache->get('system', 'basepath'));
-
-               foreach ($definition AS $table => $structure) {
-                       foreach ($structure['fields'] AS $field => $field_struct) {
-                               if (isset($field_struct['relation'])) {
-                                       foreach ($field_struct['relation'] AS $rel_table => $rel_field) {
-                                               $this->relation[$rel_table][$rel_field][$table][] = $field;
-                                       }
-                               }
-                       }
-               }
-       }
-
        /**
         * Delete a row from a table
         *
-        * Note: this methods does NOT accept schema => table arrays because of the complex relation stuff.
-        *
         * @param string $table      Table name
         * @param array  $conditions Field condition(s)
-        * @param array  $options
-        *                           - cascade: If true we delete records in other tables that depend on the one we're deleting through
-        *                           relations (default: true)
-        * @param array  $callstack  Internal use: prevent endless loops
         *
         * @return boolean was the delete successful?
         * @throws \Exception
         */
-       public function delete($table, array $conditions, array $options = [], array &$callstack = [])
+       public function delete($table, array $conditions)
        {
                if (empty($table) || empty($conditions)) {
                        $this->logger->info('Table and conditions have to be set');
                        return false;
                }
 
-               $commands = [];
-
-               // Create a key for the loop prevention
-               $key = $table . ':' . json_encode($conditions);
-
-               // We quit when this key already exists in the callstack.
-               if (isset($callstack[$key])) {
-                       return true;
-               }
-
-               $callstack[$key] = true;
-
-               $commands[$key] = ['table' => $table, 'conditions' => $conditions];
-
-               // Don't use "defaults" here, since it would set "false" to "true"
-               if (isset($options['cascade'])) {
-                       $cascade = $options['cascade'];
-               } else {
-                       $cascade = true;
-               }
-
-               // To speed up the whole process we cache the table relations
-               if ($cascade && count($this->relation) == 0) {
-                       $this->buildRelationData();
-               }
-
-               // Is there a relation entry for the table?
-               if ($cascade && isset($this->relation[$table])) {
-                       // We only allow a simple "one field" relation.
-                       $field   = array_keys($this->relation[$table])[0];
-                       $rel_def = array_values($this->relation[$table])[0];
-
-                       // Create a key for preventing double queries
-                       $qkey = $field . '-' . $table . ':' . json_encode($conditions);
-
-                       // When the search field is the relation field, we don't need to fetch the rows
-                       // This is useful when the leading record is already deleted in the frontend but the rest is done in the backend
-                       if ((count($conditions) == 1) && ($field == array_keys($conditions)[0])) {
-                               foreach ($rel_def AS $rel_table => $rel_fields) {
-                                       foreach ($rel_fields AS $rel_field) {
-                                               $this->delete($rel_table, [$rel_field => array_values($conditions)[0]], $options, $callstack);
-                                       }
-                               }
-                               // We quit when this key already exists in the callstack.
-                       } elseif (!isset($callstack[$qkey])) {
-                               $callstack[$qkey] = true;
-
-                               // Fetch all rows that are to be deleted
-                               $data = $this->select($table, [$field], $conditions);
-
-                               while ($row = $this->fetch($data)) {
-                                       $this->delete($table, [$field => $row[$field]], $options, $callstack);
-                               }
-
-                               $this->close($data);
-
-                               // Since we had split the delete command we don't need the original command anymore
-                               unset($commands[$key]);
-                       }
-               }
-
-               // Now we finalize the process
-               $do_transaction = !$this->in_transaction;
-
-               if ($do_transaction) {
-                       $this->transaction();
-               }
-
-               $compacted = [];
-               $counter   = [];
-
-               foreach ($commands AS $command) {
-                       $conditions = $command['conditions'];
-                       reset($conditions);
-                       $first_key = key($conditions);
-
-                       $condition_string = DBA::buildCondition($conditions);
-
-                       if ((count($command['conditions']) > 1) || is_int($first_key)) {
-                               $sql = "DELETE FROM " . DBA::quoteIdentifier($command['table']) . " " . $condition_string;
-                               $this->logger->info($this->replaceParameters($sql, $conditions), ['callstack' => System::callstack(6), 'internal_callstack' => $callstack]);
-
-                               if (!$this->e($sql, $conditions)) {
-                                       if ($do_transaction) {
-                                               $this->rollback();
-                                       }
-                                       return false;
-                               }
-                       } else {
-                               $key_table     = $command['table'];
-                               $key_condition = array_keys($command['conditions'])[0];
-                               $value         = array_values($command['conditions'])[0];
-
-                               // Split the SQL queries in chunks of 100 values
-                               // We do the $i stuff here to make the code better readable
-                               $i = isset($counter[$key_table][$key_condition]) ? $counter[$key_table][$key_condition] : 0;
-                               if (isset($compacted[$key_table][$key_condition][$i]) && count($compacted[$key_table][$key_condition][$i]) > 100) {
-                                       ++$i;
-                               }
-
-                               $compacted[$key_table][$key_condition][$i][$value] = $value;
-                               $counter[$key_table][$key_condition]               = $i;
-                       }
-               }
-               foreach ($compacted AS $table => $values) {
-                       foreach ($values AS $field => $field_value_list) {
-                               foreach ($field_value_list AS $field_values) {
-                                       $sql = "DELETE FROM " . DBA::quoteIdentifier($table) . " WHERE " . DBA::quoteIdentifier($field) . " IN (" .
-                                              substr(str_repeat("?, ", count($field_values)), 0, -2) . ");";
+               $table_string = DBA::buildTableString($table);
 
-                                       $this->logger->info($this->replaceParameters($sql, $field_values), ['callstack' => System::callstack(6), 'internal_callstack' => $callstack]);
+               $condition_string = DBA::buildCondition($conditions);
 
-                                       if (!$this->e($sql, $field_values)) {
-                                               if ($do_transaction) {
-                                                       $this->rollback();
-                                               }
-                                               return false;
-                                       }
-                               }
-                       }
-               }
-               if ($do_transaction) {
-                       $this->commit();
-               }
-               return true;
+               $sql = "DELETE FROM " . $table_string . " " . $condition_string;
+               $this->logger->debug($this->replaceParameters($sql, $conditions), ['callstack' => System::callstack(6)]);
+               return $this->e($sql, $conditions);
        }
 
        /**
-        * Updates rows
+        * Updates rows in the database. Field value objects will be cast as string.
         *
-        * Updates rows in the database. When $old_fields is set to an array,
+        * When $old_fields is set to an array,
         * the system will only do an update if the fields in that array changed.
         *
         * Attention:
@@ -1393,7 +1283,7 @@ class Database
         * @param string|array  $table      Table name or array [schema => table]
         * @param array         $fields     contains the fields that are updated
         * @param array         $condition  condition array with the key values
-        * @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate)
+        * @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate, false = don't update identical fields)
         *
         * @return boolean was the update successfull?
         * @throws \Exception
@@ -1419,7 +1309,7 @@ class Database
                        }
                }
 
-               foreach ($old_fields AS $fieldname => $content) {
+               foreach ($old_fields as $fieldname => $content) {
                        if (isset($fields[$fieldname]) && !is_null($content) && ($fields[$fieldname] == $content)) {
                                unset($fields[$fieldname]);
                        }
@@ -1429,6 +1319,8 @@ class Database
                        return true;
                }
 
+               $fields = $this->castFields($table, $fields);
+
                $table_string = DBA::buildTableString($table);
 
                $condition_string = DBA::buildCondition($condition);
@@ -1486,12 +1378,51 @@ class Database
                return $this->toArray($this->select($table, $fields, $condition, $params));
        }
 
+       /**
+        * Escape fields, adding special treatment for "group by" handling
+        *
+        * @param array $fields 
+        * @param array $options 
+        * @return array 
+        */
+       private function escapeFields(array $fields, array $options)
+       {
+               // In the case of a "GROUP BY" we have to add all the ORDER fields to the fieldlist.
+               // This needs to done to apply the "ANY_VALUE(...)" treatment from below to them.
+               // Otherwise MySQL would report errors.
+               if (!empty($options['group_by']) && !empty($options['order'])) {
+                       foreach ($options['order'] as $key => $field) {
+                               if (!is_int($key)) {
+                                       if (!in_array($key, $fields)) {
+                                               $fields[] = $key;
+                                       }
+                               } else {
+                                       if (!in_array($field, $fields)) {
+                                               $fields[] = $field;
+                                       }
+                               }
+                       }
+               }
+
+               array_walk($fields, function(&$value, $key) use ($options)
+               {
+                       $field = $value;
+                       $value = '`' . str_replace('`', '``', $value) . '`';
+
+                       if (!empty($options['group_by']) && !in_array($field, $options['group_by'])) {
+                               $value = 'ANY_VALUE(' . $value . ') AS ' . $value;
+                       }
+               });
+
+               return $fields;
+       }
+
        /**
         * Select rows from a table
         *
         *
         * Example:
-        * $table = 'item';
+        * $table = 'post';
         * or:
         * $table = ['schema' => 'table'];
         * @see DBA::buildTableString()
@@ -1522,7 +1453,8 @@ class Database
                }
 
                if (count($fields) > 0) {
-                       $select_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], $fields));
+                       $fields = $this->escapeFields($fields, $params);
+                       $select_string = implode(', ', $fields);
                } else {
                        $select_string = '*';
                }
@@ -1537,6 +1469,10 @@ class Database
 
                $result = $this->p($sql, $condition);
 
+               if (($this->driver == self::PDO) && !empty($result) && is_string($table)) {
+                       $result->table = $table;
+               }
+
                return $result;
        }
 
@@ -1550,7 +1486,7 @@ class Database
         * @return int
         *
         * Example:
-        * $table = "item";
+        * $table = "post";
         *
         * $condition = ["uid" => 1, "network" => 'dspr'];
         * or:
@@ -1581,18 +1517,24 @@ class Database
 
                $row = $this->fetchFirst($sql, $condition);
 
-               return $row['count'];
+               if (!isset($row['count'])) {
+                       $this->logger->notice('Invalid count.', ['table' => $table, 'row' => $row, 'expression' => $expression, 'condition' => $condition_string, 'callstack' => System::callstack()]);
+                       return 0;
+               } else {
+                       return (int)$row['count'];
+               }
        }
 
        /**
         * Fills an array with data from a query
         *
-        * @param object $stmt statement object
-        * @param bool   $do_close
+        * @param object $stmt     statement object
+        * @param bool   $do_close Close database connection after last row
+        * @param int    $count    maximum number of rows to be fetched
         *
         * @return array Data array
         */
-       public function toArray($stmt, $do_close = true)
+       public function toArray($stmt, $do_close = true, int $count = 0)
        {
                if (is_bool($stmt)) {
                        return [];
@@ -1601,6 +1543,9 @@ class Database
                $data = [];
                while ($row = $this->fetch($stmt)) {
                        $data[] = $row;
+                       if (($count != 0) && (count($data) == $count)) {
+                               return $data;
+                       }
                }
 
                if ($do_close) {
@@ -1610,6 +1555,71 @@ class Database
                return $data;
        }
 
+       /**
+        * Cast field types according to the table definition
+        *
+        * @param string $table
+        * @param array  $fields
+        * @return array casted fields
+        */
+       public function castFields(string $table, array $fields) {
+               // When there is no data, we don't need to do something
+               if (empty($fields)) {
+                       return $fields;
+               }
+
+               // We only need to cast fields with PDO
+               if ($this->driver != self::PDO) {
+                       return $fields;
+               }
+
+               // We only need to cast when emulating the prepares
+               if (!$this->connection->getAttribute(PDO::ATTR_EMULATE_PREPARES)) {
+                       return $fields;
+               }
+
+               $types = [];
+
+               $tables = DBStructure::definition('', false);
+               if (empty($tables[$table])) {
+                       // When a matching table wasn't found we check if it is a view
+                       $views = View::definition('', false);
+                       if (empty($views[$table])) {
+                               return $fields;
+                       }
+
+                       foreach(array_keys($fields) as $field) {
+                               if (!empty($views[$table]['fields'][$field])) {
+                                       $viewdef = $views[$table]['fields'][$field];
+                                       if (!empty($tables[$viewdef[0]]['fields'][$viewdef[1]]['type'])) {
+                                               $types[$field] = $tables[$viewdef[0]]['fields'][$viewdef[1]]['type'];
+                                       }
+                               }
+                       }
+               } else {
+                       foreach ($tables[$table]['fields'] as $field => $definition) {
+                               $types[$field] = $definition['type'];
+                       }
+               }
+
+               foreach ($fields as $field => $content) {
+                       if (is_null($content) || empty($types[$field])) {
+                               continue;
+                       }
+
+                       if ((substr($types[$field], 0, 7) == 'tinyint') || (substr($types[$field], 0, 8) == 'smallint') ||
+                               (substr($types[$field], 0, 9) == 'mediumint') || (substr($types[$field], 0, 3) == 'int') ||
+                               (substr($types[$field], 0, 6) == 'bigint') || (substr($types[$field], 0, 7) == 'boolean')) {
+                               $fields[$field] = (int)$content;
+                       }
+                       if ((substr($types[$field], 0, 5) == 'float') || (substr($types[$field], 0, 6) == 'double')) {
+                               $fields[$field] = (float)$content;
+                       }
+               }
+
+               return $fields;
+       }
+
        /**
         * Returns the error number of the last query
         *
@@ -1640,7 +1650,7 @@ class Database
        public function close($stmt)
        {
 
-               $stamp1 = microtime(true);
+               $this->profiler->startRecording('database');
 
                if (!is_object($stmt)) {
                        return false;
@@ -1666,7 +1676,7 @@ class Database
                                break;
                }
 
-               $this->profiler->saveTimestamp($stamp1, 'database');
+               $this->profiler->stopRecording();
 
                return $ret;
        }