From 75d94397cfcf96e66ab3d32c0903627a80704b17 Mon Sep 17 00:00:00 2001
From: Manuel Herrmann <0@0x17.de>
Date: Thu, 19 Oct 2017 23:42:28 +0200
Subject: [PATCH] updated the medoo db library

---
 .../frameworks/Environment.php                |    3 +-
 registration-system/frameworks/medoo.php      | 1228 +++++++++++------
 2 files changed, 809 insertions(+), 422 deletions(-)

diff --git a/registration-system/frameworks/Environment.php b/registration-system/frameworks/Environment.php
index e4e1dbb..8af7c90 100644
--- a/registration-system/frameworks/Environment.php
+++ b/registration-system/frameworks/Environment.php
@@ -6,6 +6,7 @@ require_once __DIR__ . '/medoo.php';
 require_once __DIR__ . '/soft_protect.php';
 require_once __DIR__ . '/Fahrt.php';
 require_once __DIR__ . '/Bachelor.php';
+use Medoo\Medoo;
 
 class Environment {
 
@@ -42,7 +43,7 @@ class Environment {
 
         $this->adminEnv = $admin;
 
-        $this->database = new medoo(array(
+        $this->database = new Medoo(array(
             'database_type' => $config_db["type"],
             'database_name' => $config_db["name"],
             'server' => $config_db["host"],
diff --git a/registration-system/frameworks/medoo.php b/registration-system/frameworks/medoo.php
index cc10923..097d998 100644
--- a/registration-system/frameworks/medoo.php
+++ b/registration-system/frameworks/medoo.php
@@ -1,57 +1,45 @@
 <?php
 /*!
  * Medoo database framework
- * http://medoo.in
- * Version 1.1.3
+ * https://medoo.in
+ * Version 1.4.5
  *
- * Copyright 2016, Angel Lai
+ * Copyright 2017, Angel Lai
  * Released under the MIT license
  */
-class medoo
-{
-	// General
-	protected $database_type;
-
-	protected $charset;
-
-	protected $database_name;
 
-	// For MySQL, MariaDB, MSSQL, Sybase, PostgreSQL, Oracle
-	protected $server;
+namespace Medoo;
 
-	protected $username;
+use PDO;
+use Exception;
+use PDOException;
 
-	protected $password;
-
-	// For SQLite
-	protected $database_file;
+class Medoo
+{
+	protected $database_type;
 
-	// For MySQL or MariaDB with unix_socket
-	protected $socket;
+	protected $prefix;
 
-	// Optional
-	protected $port;
+	protected $statement;
 
-	protected $prefix;
+	protected $option = [];
 
-	protected $option = array();
+	protected $logs = [];
 
-	// Variable
-	protected $logs = array();
+	protected $logging = false;
 
 	protected $debug_mode = false;
 
+	protected $guid = 0;
+
 	public function __construct($options = null)
 	{
 		try {
-			$commands = array();
-			$dsn = '';
-
 			if (is_array($options))
 			{
-				foreach ($options as $option => $value)
+				if (isset($options[ 'database_type' ]))
 				{
-					$this->$option = $value;
+					$this->database_type = strtolower($options[ 'database_type' ]);
 				}
 			}
 			else
@@ -59,86 +47,188 @@ class medoo
 				return false;
 			}
 
-			if (
-					isset($this->port) &&
-					is_int($this->port * 1)
-			)
+			if (isset($options[ 'prefix' ]))
 			{
-				$port = $this->port;
+				$this->prefix = $options[ 'prefix' ];
 			}
 
-			$type = strtolower($this->database_type);
-			$is_port = isset($port);
+			if (isset($options[ 'option' ]))
+			{
+				$this->option = $options[ 'option' ];
+			}
 
-			if (isset($options[ 'prefix' ]))
+			if (isset($options[ 'logging' ]) && is_bool($options[ 'logging' ]))
 			{
-				$this->prefix = $options[ 'prefix' ];
+				$this->logging = $options[ 'logging' ];
 			}
 
-			switch ($type)
+			if (isset($options[ 'command' ]) && is_array($options[ 'command' ]))
+			{
+				$commands = $options[ 'command' ];
+			}
+			else
 			{
-				case 'mariadb':
-					$type = 'mysql';
+				$commands = [];
+			}
 
-				case 'mysql':
-					if ($this->socket)
-					{
-						$dsn = $type . ':unix_socket=' . $this->socket . ';dbname=' . $this->database_name;
-					}
-					else
-					{
-						$dsn = $type . ':host=' . $this->server . ($is_port ? ';port=' . $port : '') . ';dbname=' . $this->database_name;
-					}
+			if (isset($options[ 'dsn' ]))
+			{
+				if (isset($options[ 'dsn' ][ 'driver' ]))
+				{
+					$attr = $options[ 'dsn' ];
+				}
+				else
+				{
+					return false;
+				}
+			}
+			else
+			{
+				if (
+					isset($options[ 'port' ]) &&
+					is_int($options[ 'port' ] * 1)
+				)
+				{
+					$port = $options[ 'port' ];
+				}
 
-					// Make MySQL using standard quoted identifier
-					$commands[] = 'SET SQL_MODE=ANSI_QUOTES';
-					break;
+				$is_port = isset($port);
 
-				case 'pgsql':
-					$dsn = $type . ':host=' . $this->server . ($is_port ? ';port=' . $port : '') . ';dbname=' . $this->database_name;
-					break;
+				switch ($this->database_type)
+				{
+					case 'mariadb':
+					case 'mysql':
+						$attr = [
+							'driver' => 'mysql',
+							'dbname' => $options[ 'database_name' ]
+						];
+
+						if (isset($options[ 'socket' ]))
+						{
+							$attr[ 'unix_socket' ] = $options[ 'socket' ];
+						}
+						else
+						{
+							$attr[ 'host' ] = $options[ 'server' ];
 
-				case 'sybase':
-					$dsn = 'dblib:host=' . $this->server . ($is_port ? ':' . $port : '') . ';dbname=' . $this->database_name;
-					break;
+							if ($is_port)
+							{
+								$attr[ 'port' ] = $port;
+							}
+						}
 
-				case 'oracle':
-					$dbname = $this->server ?
-							'//' . $this->server . ($is_port ? ':' . $port : ':1521') . '/' . $this->database_name :
-							$this->database_name;
+						// Make MySQL using standard quoted identifier
+						$commands[] = 'SET SQL_MODE=ANSI_QUOTES';
+						break;
+
+					case 'pgsql':
+						$attr = [
+							'driver' => 'pgsql',
+							'host' => $options[ 'server' ],
+							'dbname' => $options[ 'database_name' ]
+						];
 
-					$dsn = 'oci:dbname=' . $dbname . ($this->charset ? ';charset=' . $this->charset : '');
-					break;
+						if ($is_port)
+						{
+							$attr[ 'port' ] = $port;
+						}
 
-				case 'mssql':
-					$dsn = strstr(PHP_OS, 'WIN') ?
-							'sqlsrv:server=' . $this->server . ($is_port ? ',' . $port : '') . ';database=' . $this->database_name :
-							'dblib:host=' . $this->server . ($is_port ? ':' . $port : '') . ';dbname=' . $this->database_name;
+						break;
 
-					// Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting
-					$commands[] = 'SET QUOTED_IDENTIFIER ON';
-					break;
+					case 'sybase':
+						$attr = [
+							'driver' => 'dblib',
+							'host' => $options[ 'server' ],
+							'dbname' => $options[ 'database_name' ]
+						];
 
-				case 'sqlite':
-					$dsn = $type . ':' . $this->database_file;
-					$this->username = null;
-					$this->password = null;
-					break;
+						if ($is_port)
+						{
+							$attr[ 'port' ] = $port;
+						}
+
+						break;
+
+					case 'oracle':
+						$attr = [
+							'driver' => 'oci',
+							'dbname' => $options[ 'server' ] ?
+								'//' . $options[ 'server' ] . ($is_port ? ':' . $port : ':1521') . '/' . $options[ 'database_name' ] :
+								$options[ 'database_name' ]
+						];
+
+						if (isset($options[ 'charset' ]))
+						{
+							$attr[ 'charset' ] = $options[ 'charset' ];
+						}
+
+						break;
+
+					case 'mssql':
+						if (strstr(PHP_OS, 'WIN'))
+						{
+							$attr = [
+								'driver' => 'sqlsrv',
+								'Server' => $options[ 'server' ] . ($is_port ? ',' . $port : ''),
+								'Database' => $options[ 'database_name' ]
+							];
+						}
+						else
+						{
+							$attr = [
+								'driver' => 'dblib',
+								'host' => $options[ 'server' ] . ($is_port ? ':' . $port : ''),
+								'dbname' => $options[ 'database_name' ]
+							];
+						}
+
+						// Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting
+						$commands[] = 'SET QUOTED_IDENTIFIER ON';
+
+						// Make ANSI_NULLS is ON for NULL value
+						$commands[] = 'SET ANSI_NULLS ON';
+						break;
+
+					case 'sqlite':
+						$this->pdo = new PDO('sqlite:' . $options[ 'database_file' ], null, null, $this->option);
+
+						return;
+				}
+			}
+
+			$driver = $attr[ 'driver' ];
+
+			unset($attr[ 'driver' ]);
+
+			$stack = [];
+
+			foreach ($attr as $key => $value)
+			{
+				if (is_int($key))
+				{
+					$stack[] = $value;
+				}
+				else
+				{
+					$stack[] = $key . '=' . $value;
+				}
 			}
 
+			$dsn = $driver . ':' . implode($stack, ';');
+
 			if (
-					in_array($type, array('mariadb', 'mysql', 'pgsql', 'sybase', 'mssql')) &&
-					$this->charset
+				in_array($this->database_type, ['mariadb', 'mysql', 'pgsql', 'sybase', 'mssql']) &&
+				isset($options[ 'charset' ])
 			)
 			{
-				$commands[] = "SET NAMES '" . $this->charset . "'";
+				$commands[] = "SET NAMES '" . $options[ 'charset' ] . "'";
 			}
 
 			$this->pdo = new PDO(
-					$dsn,
-					$this->username,
-					$this->password,
-					$this->option
+				$dsn,
+				$options[ 'username' ],
+				$options[ 'password' ],
+				$this->option
 			);
 
 			foreach ($commands as $value)
@@ -151,36 +241,101 @@ class medoo
 		}
 	}
 
-	public function query($query)
+	public function query($query, $map = [])
 	{
-		if ($this->debug_mode)
+		if (!empty($map))
 		{
-			echo $query;
+			foreach ($map as $key => $value)
+			{
+				switch (gettype($value))
+				{
+					case 'NULL':
+						$map[ $key ] = [null, PDO::PARAM_NULL];
+						break;
 
-			$this->debug_mode = false;
+					case 'resource':
+						$map[ $key ] = [$value, PDO::PARAM_LOB];
+						break;
 
-			return false;
-		}
+					case 'boolean':
+						$map[ $key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+						break;
 
-		$this->logs[] = $query;
+					case 'integer':
+					case 'double':
+						$map[ $key ] = [$value, PDO::PARAM_INT];
+						break;
+
+					case 'string':
+						$map[ $key ] = [$value, PDO::PARAM_STR];
+						break;
+				}
+			}
+		}
 
-		return $this->pdo->query($query);
+		return $this->exec($query, $map);
 	}
 
-	public function exec($query)
+	public function exec($query, $map = [])
 	{
 		if ($this->debug_mode)
 		{
-			echo $query;
+			echo $this->generate($query, $map);
 
 			$this->debug_mode = false;
 
 			return false;
 		}
 
-		$this->logs[] = $query;
+		if ($this->logging)
+		{
+			$this->logs[] = [$query, $map];
+		}
+		else
+		{
+			$this->logs = [[$query, $map]];
+		}
+
+		$statement = $this->pdo->prepare($query);
+
+		if ($statement)
+		{
+			foreach ($map as $key => $value)
+			{
+				$statement->bindValue($key, $value[ 0 ], $value[ 1 ]);
+			}
+
+			$statement->execute();
+
+			$this->statement = $statement;
+
+			return $statement;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	protected function generate($query, $map)
+	{
+		foreach ($map as $key => $value)
+		{
+			if ($value[ 1 ] === PDO::PARAM_STR)
+			{
+				$query = str_replace($key, $this->quote($value[ 0 ]), $query);
+			}
+			elseif ($value[ 1 ] === PDO::PARAM_NULL)
+			{
+				$query = str_replace($key, 'NULL', $query);
+			}
+			else
+			{
+				$query = str_replace($key, $value[ 0 ], $query);
+			}
+		}
 
-		return $this->pdo->exec($query);
+		return $query;
 	}
 
 	public function quote($string)
@@ -188,14 +343,19 @@ class medoo
 		return $this->pdo->quote($string);
 	}
 
-	protected function table_quote($table)
+	protected function tableQuote($table)
 	{
 		return '"' . $this->prefix . $table . '"';
 	}
 
-	protected function column_quote($string)
+	protected function mapKey()
 	{
-		preg_match('/(\(JSON\)\s*|^#)?([a-zA-Z0-9_]*)\.([a-zA-Z0-9_]*)/', $string, $column_match);
+		return ':MeDoO_' . $this->guid++ . '_mEdOo';
+	}
+
+	protected function columnQuote($string)
+	{
+		preg_match('/(^#)?([a-zA-Z0-9_]*)\.([a-zA-Z0-9_]*)(\s*\[JSON\]$)?/', $string, $column_match);
 
 		if (isset($column_match[ 2 ], $column_match[ 3 ]))
 		{
@@ -205,39 +365,39 @@ class medoo
 		return '"' . $string . '"';
 	}
 
-	protected function column_push(&$columns)
+	protected function columnPush(&$columns)
 	{
-		if ($columns == '*')
+		if ($columns === '*')
 		{
 			return $columns;
 		}
 
+		$stack = [];
+
 		if (is_string($columns))
 		{
-			$columns = array($columns);
+			$columns = [$columns];
 		}
 
-		$stack = array();
-
 		foreach ($columns as $key => $value)
 		{
 			if (is_array($value))
 			{
-				$stack[] = $this->column_push($value);
+				$stack[] = $this->columnPush($value);
 			}
 			else
 			{
-				preg_match('/([a-zA-Z0-9_\-\.]*)\s*\(([a-zA-Z0-9_\-]*)\)/i', $value, $match);
+				preg_match('/(?<column>[a-zA-Z0-9_\.]+)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\)|\s*\[(?<type>(String|Bool|Int|Number|Object|JSON))\])?/i', $value, $match);
 
-				if (isset($match[ 1 ], $match[ 2 ]))
+				if (!empty($match[ 'alias' ]))
 				{
-					$stack[] = $this->column_quote( $match[ 1 ] ) . ' AS ' . $this->column_quote( $match[ 2 ] );
+					$stack[] = $this->columnQuote( $match[ 'column' ] ) . ' AS ' . $this->columnQuote( $match[ 'alias' ] );
 
-					$columns[ $key ] = $match[ 2 ];
+					$columns[ $key ] = $match[ 'alias' ];
 				}
 				else
 				{
-					$stack[] = $this->column_quote( $value );
+					$stack[] = $this->columnQuote( $match[ 'column' ] );
 				}
 			}
 		}
@@ -245,180 +405,216 @@ class medoo
 		return implode($stack, ',');
 	}
 
-	protected function array_quote($array)
+	protected function arrayQuote($array)
 	{
-		$temp = array();
+		$stack = [];
 
 		foreach ($array as $value)
 		{
-			$temp[] = is_int($value) ? $value : $this->pdo->quote($value);
+			$stack[] = is_int($value) ? $value : $this->pdo->quote($value);
 		}
 
-		return implode($temp, ',');
+		return implode($stack, ',');
 	}
 
-	protected function inner_conjunct($data, $conjunctor, $outer_conjunctor)
+	protected function innerConjunct($data, $map, $conjunctor, $outer_conjunctor)
 	{
-		$haystack = array();
+		$stack = [];
 
 		foreach ($data as $value)
 		{
-			$haystack[] = '(' . $this->data_implode($value, $conjunctor) . ')';
+			$stack[] = '(' . $this->dataImplode($value, $map, $conjunctor) . ')';
 		}
 
-		return implode($outer_conjunctor . ' ', $haystack);
+		return implode($outer_conjunctor . ' ', $stack);
 	}
 
-	protected function fn_quote($column, $string)
+	protected function fnQuote($column, $string)
 	{
 		return (strpos($column, '#') === 0 && preg_match('/^[A-Z0-9\_]*\([^)]*\)$/', $string)) ?
 
-				$string :
+			$string :
 
-				$this->quote($string);
+			$this->quote($string);
 	}
 
-	protected function data_implode($data, $conjunctor, $outer_conjunctor = null)
+	protected function dataImplode($data, &$map, $conjunctor)
 	{
-		$wheres = array();
+		$wheres = [];
 
 		foreach ($data as $key => $value)
 		{
+			$map_key = $this->mapKey();
+
 			$type = gettype($value);
 
 			if (
-					preg_match("/^(AND|OR)(\s+#.*)?$/i", $key, $relation_match) &&
-					$type == 'array'
+				preg_match("/^(AND|OR)(\s+#.*)?$/i", $key, $relation_match) &&
+				$type === 'array'
 			)
 			{
 				$wheres[] = 0 !== count(array_diff_key($value, array_keys(array_keys($value)))) ?
-						'(' . $this->data_implode($value, ' ' . $relation_match[ 1 ]) . ')' :
-						'(' . $this->inner_conjunct($value, ' ' . $relation_match[ 1 ], $conjunctor) . ')';
+					'(' . $this->dataImplode($value, $map, ' ' . $relation_match[ 1 ]) . ')' :
+					'(' . $this->innerConjunct($value, $map, ' ' . $relation_match[ 1 ], $conjunctor) . ')';
 			}
 			else
 			{
-				preg_match('/(#?)([\w\.\-]+)(\[(\>|\>\=|\<|\<\=|\!|\<\>|\>\<|\!?~)\])?/i', $key, $match);
-				$column = $this->column_quote($match[ 2 ]);
-
-				if (isset($match[ 4 ]))
+				if (
+					is_int($key) &&
+					preg_match('/([a-zA-Z0-9_\.]+)\[(?<operator>\>|\>\=|\<|\<\=|\!|\=)\]([a-zA-Z0-9_\.]+)/i', $value, $match)
+				)
 				{
-					$operator = $match[ 4 ];
+					$wheres[] = $this->columnQuote($match[ 1 ]) . ' ' . $match[ 'operator' ] . ' ' . $this->columnQuote($match[ 3 ]);
+				}
+				else
+				{
+					preg_match('/(#?)([a-zA-Z0-9_\.]+)(\[(?<operator>\>|\>\=|\<|\<\=|\!|\<\>|\>\<|\!?~)\])?/i', $key, $match);
+					$column = $this->columnQuote($match[ 2 ]);
 
-					if ($operator == '!')
+					if (!empty($match[ 1 ]))
 					{
-						switch ($type)
+						$wheres[] = $column .
+							(isset($match[ 'operator' ]) ? ' ' . $match[ 'operator' ] . ' ' : ' = ') .
+							$this->fnQuote($key, $value);
+
+						continue;
+					}
+
+					if (isset($match[ 'operator' ]))
+					{
+						$operator = $match[ 'operator' ];
+
+						if ($operator === '!')
 						{
-							case 'NULL':
-								$wheres[] = $column . ' IS NOT NULL';
-								break;
+							switch ($type)
+							{
+								case 'NULL':
+									$wheres[] = $column . ' IS NOT NULL';
+									break;
+
+								case 'array':
+									$wheres[] = $column . ' NOT IN (' . $this->arrayQuote($value) . ')';
+									break;
+
+								case 'integer':
+								case 'double':
+									$wheres[] = $column . ' != ' . $map_key;
+									$map[ $map_key ] = [$value, PDO::PARAM_INT];
+									break;
+
+								case 'boolean':
+									$wheres[] = $column . ' != ' . $map_key;
+									$map[ $map_key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+									break;
+
+								case 'string':
+									$wheres[] = $column . ' != ' . $map_key;
+									$map[ $map_key ] = [$value, PDO::PARAM_STR];
+									break;
+							}
+						}
 
-							case 'array':
-								$wheres[] = $column . ' NOT IN (' . $this->array_quote($value) . ')';
-								break;
+						if ($operator === '<>' || $operator === '><')
+						{
+							if ($type === 'array')
+							{
+								if ($operator === '><')
+								{
+									$column .= ' NOT';
+								}
 
-							case 'integer':
-							case 'double':
-								$wheres[] = $column . ' != ' . $value;
-								break;
+								$wheres[] = '(' . $column . ' BETWEEN ' . $map_key . 'a AND ' . $map_key . 'b)';
 
-							case 'boolean':
-								$wheres[] = $column . ' != ' . ($value ? '1' : '0');
-								break;
+								$data_type = (is_numeric($value[ 0 ]) && is_numeric($value[ 1 ])) ? PDO::PARAM_INT : PDO::PARAM_STR;
 
-							case 'string':
-								$wheres[] = $column . ' != ' . $this->fn_quote($key, $value);
-								break;
+								$map[ $map_key . 'a' ] = [$value[ 0 ], $data_type];
+								$map[ $map_key . 'b' ] = [$value[ 1 ], $data_type];
+							}
 						}
-					}
 
-					if ($operator == '<>' || $operator == '><')
-					{
-						if ($type == 'array')
+						if ($operator === '~' || $operator === '!~')
 						{
-							if ($operator == '><')
+							if ($type !== 'array')
 							{
-								$column .= ' NOT';
+								$value = [ $value ];
 							}
 
-							if (is_numeric($value[ 0 ]) && is_numeric($value[ 1 ]))
+							$connector = ' OR ';
+							$stack = array_values($value);
+
+							if (is_array($stack[ 0 ]))
 							{
-								$wheres[] = '(' . $column . ' BETWEEN ' . $value[ 0 ] . ' AND ' . $value[ 1 ] . ')';
+								if (isset($value[ 'AND' ]) || isset($value[ 'OR' ]))
+								{
+									$connector = ' ' . array_keys($value)[ 0 ] . ' ';
+									$value = $stack[ 0 ];
+								}
 							}
-							else
+
+							$like_clauses = [];
+
+							foreach ($value as $index => $item)
 							{
-								$wheres[] = '(' . $column . ' BETWEEN ' . $this->quote($value[ 0 ]) . ' AND ' . $this->quote($value[ 1 ]) . ')';
+								$item = strval($item);
+
+								if (!preg_match('/(\[.+\]|_|%.+|.+%)/', $item))
+								{
+									$item = '%' . $item . '%';
+								}
+
+								$like_clauses[] = $column . ($operator === '!~' ? ' NOT' : '') . ' LIKE ' . $map_key . 'L' . $index;
+								$map[ $map_key . 'L' . $index ] = [$item, PDO::PARAM_STR];
 							}
-						}
-					}
 
-					if ($operator == '~' || $operator == '!~')
-					{
-						if ($type != 'array')
-						{
-							$value = array($value);
+							$wheres[] = '(' . implode($connector, $like_clauses) . ')';
 						}
 
-						$like_clauses = array();
-
-						foreach ($value as $item)
+						if (in_array($operator, ['>', '>=', '<', '<=']))
 						{
-							$item = strval($item);
+							$condition = $column . ' ' . $operator . ' ';
 
-							if (preg_match('/^(?!(%|\[|_])).+(?<!(%|\]|_))$/', $item))
+							if (is_numeric($value))
+							{
+								$condition .= $map_key;
+								$map[ $map_key ] = [$value, PDO::PARAM_INT];
+							}
+							else
 							{
-								$item = '%' . $item . '%';
+								$condition .= $map_key;
+								$map[ $map_key ] = [$value, PDO::PARAM_STR];
 							}
 
-							$like_clauses[] = $column . ($operator === '!~' ? ' NOT' : '') . ' LIKE ' . $this->fn_quote($key, $item);
+							$wheres[] = $condition;
 						}
-
-						$wheres[] = implode(' OR ', $like_clauses);
 					}
-
-					if (in_array($operator, array('>', '>=', '<', '<=')))
+					else
 					{
-						$condition = $column . ' ' . $operator . ' ';
-
-						if (is_numeric($value))
-						{
-							$condition .= $value;
-						}
-						elseif (strpos($key, '#') === 0)
-						{
-							$condition .= $this->fn_quote($key, $value);
-						}
-						else
+						switch ($type)
 						{
-							$condition .= $this->quote($value);
-						}
-
-						$wheres[] = $condition;
-					}
-				}
-				else
-				{
-					switch ($type)
-					{
-						case 'NULL':
-							$wheres[] = $column . ' IS NULL';
-							break;
+							case 'NULL':
+								$wheres[] = $column . ' IS NULL';
+								break;
 
-						case 'array':
-							$wheres[] = $column . ' IN (' . $this->array_quote($value) . ')';
-							break;
+							case 'array':
+								$wheres[] = $column . ' IN (' . $this->arrayQuote($value) . ')';
+								break;
 
-						case 'integer':
-						case 'double':
-							$wheres[] = $column . ' = ' . $value;
-							break;
+							case 'integer':
+							case 'double':
+								$wheres[] = $column . ' = ' . $map_key;
+								$map[ $map_key ] = [$value, PDO::PARAM_INT];
+								break;
 
-						case 'boolean':
-							$wheres[] = $column . ' = ' . ($value ? '1' : '0');
-							break;
+							case 'boolean':
+								$wheres[] = $column . ' = ' . $map_key;
+								$map[ $map_key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+								break;
 
-						case 'string':
-							$wheres[] = $column . ' = ' . $this->fn_quote($key, $value);
-							break;
+							case 'string':
+								$wheres[] = $column . ' = ' . $map_key;
+								$map[ $map_key ] = [$value, PDO::PARAM_STR];
+								break;
+						}
 					}
 				}
 			}
@@ -427,7 +623,7 @@ class medoo
 		return implode($conjunctor . ' ', $wheres);
 	}
 
-	protected function where_clause($where)
+	protected function whereClause($where, &$map)
 	{
 		$where_clause = '';
 
@@ -438,14 +634,14 @@ class medoo
 			$where_OR = preg_grep("/^OR\s*#?$/i", $where_keys);
 
 			$single_condition = array_diff_key($where, array_flip(
-					array('AND', 'OR', 'GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH')
+				['AND', 'OR', 'GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH']
 			));
 
-			if ($single_condition != array())
+			if (!empty($single_condition))
 			{
-				$condition = $this->data_implode($single_condition, '');
+				$condition = $this->dataImplode($single_condition, $map, ' AND');
 
-				if ($condition != '')
+				if ($condition !== '')
 				{
 					$where_clause = ' WHERE ' . $condition;
 				}
@@ -454,13 +650,13 @@ class medoo
 			if (!empty($where_AND))
 			{
 				$value = array_values($where_AND);
-				$where_clause = ' WHERE ' . $this->data_implode($where[ $value[ 0 ] ], ' AND');
+				$where_clause = ' WHERE ' . $this->dataImplode($where[ $value[ 0 ] ], $map, ' AND');
 			}
 
 			if (!empty($where_OR))
 			{
 				$value = array_values($where_OR);
-				$where_clause = ' WHERE ' . $this->data_implode($where[ $value[ 0 ] ], ' OR');
+				$where_clause = ' WHERE ' . $this->dataImplode($where[ $value[ 0 ] ], $map, ' OR');
 			}
 
 			if (isset($where[ 'MATCH' ]))
@@ -469,17 +665,51 @@ class medoo
 
 				if (is_array($MATCH) && isset($MATCH[ 'columns' ], $MATCH[ 'keyword' ]))
 				{
-					$where_clause .= ($where_clause != '' ? ' AND ' : ' WHERE ') . ' MATCH ("' . str_replace('.', '"."', implode($MATCH[ 'columns' ], '", "')) . '") AGAINST (' . $this->quote($MATCH[ 'keyword' ]) . ')';
+					$mode = '';
+
+					$mode_array = [
+						'natural' => 'IN NATURAL LANGUAGE MODE',
+						'natural+query' => 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION',
+						'boolean' => 'IN BOOLEAN MODE',
+						'query' => 'WITH QUERY EXPANSION'
+					];
+
+					if (isset($MATCH[ 'mode' ], $mode_array[ $MATCH[ 'mode' ] ]))
+					{
+						$mode = ' ' . $mode_array[ $MATCH[ 'mode' ] ];
+					}
+
+					$columns = implode(array_map([$this, 'columnQuote'], $MATCH[ 'columns' ]), ', ');
+					$map_key = $this->mapKey();
+					$map[ $map_key ] = [$MATCH[ 'keyword' ], PDO::PARAM_STR];
+
+					$where_clause .= ($where_clause !== '' ? ' AND ' : ' WHERE') . ' MATCH (' . $columns . ') AGAINST (' . $map_key . $mode . ')';
 				}
 			}
 
 			if (isset($where[ 'GROUP' ]))
 			{
-				$where_clause .= ' GROUP BY ' . $this->column_quote($where[ 'GROUP' ]);
+				$GROUP = $where[ 'GROUP' ];
+
+				if (is_array($GROUP))
+				{
+					$stack = [];
+
+					foreach ($GROUP as $column => $value)
+					{
+						$stack[] = $this->columnQuote($value);
+					}
+
+					$where_clause .= ' GROUP BY ' . implode($stack, ',');
+				}
+				else
+				{
+					$where_clause .= ' GROUP BY ' . $this->columnQuote($where[ 'GROUP' ]);
+				}
 
 				if (isset($where[ 'HAVING' ]))
 				{
-					$where_clause .= ' HAVING ' . $this->data_implode($where[ 'HAVING' ], ' AND');
+					$where_clause .= ' HAVING ' . $this->dataImplode($where[ 'HAVING' ], $map, ' AND');
 				}
 			}
 
@@ -489,21 +719,21 @@ class medoo
 
 				if (is_array($ORDER))
 				{
-					$stack = array();
+					$stack = [];
 
 					foreach ($ORDER as $column => $value)
 					{
 						if (is_array($value))
 						{
-							$stack[] = 'FIELD(' . $this->column_quote($column) . ', ' . $this->array_quote($value) . ')';
+							$stack[] = 'FIELD(' . $this->columnQuote($column) . ', ' . $this->arrayQuote($value) . ')';
 						}
 						else if ($value === 'ASC' || $value === 'DESC')
 						{
-							$stack[] = $this->column_quote($column) . ' ' . $value;
+							$stack[] = $this->columnQuote($column) . ' ' . $value;
 						}
 						else if (is_int($column))
 						{
-							$stack[] = $this->column_quote($value);
+							$stack[] = $this->columnQuote($value);
 						}
 					}
 
@@ -511,11 +741,33 @@ class medoo
 				}
 				else
 				{
-					$where_clause .= ' ORDER BY ' . $this->column_quote($ORDER);
+					$where_clause .= ' ORDER BY ' . $this->columnQuote($ORDER);
+				}
+
+				if (
+					isset($where[ 'LIMIT' ]) &&
+					in_array($this->database_type, ['oracle', 'mssql'])
+				)
+				{
+					$LIMIT = $where[ 'LIMIT' ];
+
+					if (is_numeric($LIMIT))
+					{
+						$where_clause .= ' FETCH FIRST ' . $LIMIT . ' ROWS ONLY';
+					}
+
+					if (
+						is_array($LIMIT) &&
+						is_numeric($LIMIT[ 0 ]) &&
+						is_numeric($LIMIT[ 1 ])
+					)
+					{
+						$where_clause .= ' OFFSET ' . $LIMIT[ 0 ] . ' ROWS FETCH NEXT ' . $LIMIT[ 1 ] . ' ROWS ONLY';
+					}
 				}
 			}
 
-			if (isset($where[ 'LIMIT' ]))
+			if (isset($where[ 'LIMIT' ]) && !in_array($this->database_type, ['oracle', 'mssql']))
 			{
 				$LIMIT = $where[ 'LIMIT' ];
 
@@ -525,25 +777,18 @@ class medoo
 				}
 
 				if (
-						is_array($LIMIT) &&
-						is_numeric($LIMIT[ 0 ]) &&
-						is_numeric($LIMIT[ 1 ])
+					is_array($LIMIT) &&
+					is_numeric($LIMIT[ 0 ]) &&
+					is_numeric($LIMIT[ 1 ])
 				)
 				{
-					if ($this->database_type === 'pgsql')
-					{
-						$where_clause .= ' OFFSET ' . $LIMIT[ 0 ] . ' LIMIT ' . $LIMIT[ 1 ];
-					}
-					else
-					{
-						$where_clause .= ' LIMIT ' . $LIMIT[ 0 ] . ',' . $LIMIT[ 1 ];
-					}
+					$where_clause .= ' LIMIT ' . $LIMIT[ 1 ] . ' OFFSET ' . $LIMIT[ 0 ];
 				}
 			}
 		}
 		else
 		{
-			if ($where != null)
+			if ($where !== null)
 			{
 				$where_clause .= ' ' . $where;
 			}
@@ -552,19 +797,19 @@ class medoo
 		return $where_clause;
 	}
 
-	protected function select_context($table, $join, &$columns = null, $where = null, $column_fn = null)
+	protected function selectContext($table, &$map, $join, &$columns = null, $where = null, $column_fn = null)
 	{
-		preg_match('/([a-zA-Z0-9_\-]*)\s*\(([a-zA-Z0-9_\-]*)\)/i', $table, $table_match);
+		preg_match('/(?<table>[a-zA-Z0-9_]+)\s*\((?<alias>[a-zA-Z0-9_]+)\)/i', $table, $table_match);
 
-		if (isset($table_match[ 1 ], $table_match[ 2 ]))
+		if (isset($table_match[ 'table' ], $table_match[ 'alias' ]))
 		{
-			$table = $this->table_quote($table_match[ 1 ]);
+			$table = $this->tableQuote($table_match[ 'table' ]);
 
-			$table_query = $this->table_quote($table_match[ 1 ]) . ' AS ' . $this->table_quote($table_match[ 2 ]);
+			$table_query = $table . ' AS ' . $this->tableQuote($table_match[ 'alias' ]);
 		}
 		else
 		{
-			$table = $this->table_quote($table);
+			$table = $this->tableQuote($table);
 
 			$table_query = $table;
 		}
@@ -572,24 +817,24 @@ class medoo
 		$join_key = is_array($join) ? array_keys($join) : null;
 
 		if (
-				isset($join_key[ 0 ]) &&
-				strpos($join_key[ 0 ], '[') === 0
+			isset($join_key[ 0 ]) &&
+			strpos($join_key[ 0 ], '[') === 0
 		)
 		{
-			$table_join = array();
+			$table_join = [];
 
-			$join_array = array(
-					'>' => 'LEFT',
-					'<' => 'RIGHT',
-					'<>' => 'FULL',
-					'><' => 'INNER'
-			);
+			$join_array = [
+				'>' => 'LEFT',
+				'<' => 'RIGHT',
+				'<>' => 'FULL',
+				'><' => 'INNER'
+			];
 
 			foreach($join as $sub_table => $relation)
 			{
-				preg_match('/(\[(\<|\>|\>\<|\<\>)\])?([a-zA-Z0-9_\-]*)\s?(\(([a-zA-Z0-9_\-]*)\))?/', $sub_table, $match);
+				preg_match('/(\[(?<join>\<|\>|\>\<|\<\>)\])?(?<table>[a-zA-Z0-9_]+)\s?(\((?<alias>[a-zA-Z0-9_]+)\))?/', $sub_table, $match);
 
-				if ($match[ 2 ] != '' && $match[ 3 ] != '')
+				if ($match[ 'join' ] !== '' && $match[ 'table' ] !== '')
 				{
 					if (is_string($relation))
 					{
@@ -605,34 +850,34 @@ class medoo
 						}
 						else
 						{
-							$joins = array();
+							$joins = [];
 
 							foreach ($relation as $key => $value)
 							{
 								$joins[] = (
-										strpos($key, '.') > 0 ?
-											// For ['tableB.column' => 'column']
-												$this->column_quote($key) :
-
-											// For ['column1' => 'column2']
-												$table . '."' . $key . '"'
-										) .
-										' = ' .
-										$this->table_quote(isset($match[ 5 ]) ? $match[ 5 ] : $match[ 3 ]) . '."' . $value . '"';
+									strpos($key, '.') > 0 ?
+										// For ['tableB.column' => 'column']
+										$this->columnQuote($key) :
+
+										// For ['column1' => 'column2']
+										$table . '."' . $key . '"'
+								) .
+								' = ' .
+								$this->tableQuote(isset($match[ 'alias' ]) ? $match[ 'alias' ] : $match[ 'table' ]) . '."' . $value . '"';
 							}
 
 							$relation = 'ON ' . implode($joins, ' AND ');
 						}
 					}
 
-					$table_name = $this->table_quote($match[ 3 ]) . ' ';
+					$table_name = $this->tableQuote($match[ 'table' ]) . ' ';
 
-					if (isset($match[ 5 ]))
+					if (isset($match[ 'alias' ]))
 					{
-						$table_name .= 'AS ' . $this->table_quote($match[ 5 ]) . ' ';
+						$table_name .= 'AS ' . $this->tableQuote($match[ 'alias' ]) . ' ';
 					}
 
-					$table_join[] = $join_array[ $match[ 2 ] ] . ' JOIN ' . $table_name . $relation;
+					$table_join[] = $join_array[ $match[ 'join' ] ] . ' JOIN ' . $table_name . $relation;
 				}
 			}
 
@@ -645,8 +890,8 @@ class medoo
 				if (is_null($where))
 				{
 					if (
-							is_array($join) &&
-							isset($column_fn)
+						is_array($join) &&
+						isset($column_fn)
 					)
 					{
 						$where = $join;
@@ -673,7 +918,7 @@ class medoo
 
 		if (isset($column_fn))
 		{
-			if ($column_fn == 1)
+			if ($column_fn === 1)
 			{
 				$column = '1';
 
@@ -690,70 +935,118 @@ class medoo
 					$where = $join;
 				}
 
-				$column = $column_fn . '(' . $this->column_push($columns) . ')';
+				$column = $column_fn . '(' . $this->columnPush($columns) . ')';
 			}
 		}
 		else
 		{
-			$column = $this->column_push($columns);
+			$column = $this->columnPush($columns);
 		}
 
-		return 'SELECT ' . $column . ' FROM ' . $table_query . $this->where_clause($where);
+		return 'SELECT ' . $column . ' FROM ' . $table_query . $this->whereClause($where, $map);
 	}
 
-	protected function data_map($index, $key, $value, $data, &$stack)
+	protected function columnMap($columns, &$stack)
 	{
-		if (is_array($value))
+		if ($columns === '*')
 		{
-			$sub_stack = array();
+			return $stack;
+		}
 
-			foreach ($value as $sub_key => $sub_value)
+		foreach ($columns as $key => $value)
+		{
+			if (is_int($key))
 			{
-				if (is_array($sub_value))
-				{
-					$current_stack = $stack[ $index ][ $key ];
+				preg_match('/(?<column>[a-zA-Z0-9_\.]*)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\)|\s*\[(?<type>(String|Bool|Int|Number|Object|JSON))\])?/i', $value, $key_match);
 
-					$this->data_map(false, $sub_key, $sub_value, $data, $current_stack);
+				$column_key = !empty($key_match[ 'alias' ]) ?
+					$key_match[ 'alias' ] :
+					preg_replace('/^[\w]*\./i', '', $key_match[ 'column' ]);
 
-					$stack[ $index ][ $key ][ $sub_key ] = $current_stack[ 0 ][ $sub_key ];
+				if (isset($key_match[ 'type' ]))
+				{
+					$stack[ $value ] = [$column_key, $key_match[ 'type' ]];
 				}
 				else
 				{
-					$this->data_map(false, preg_replace('/^[\w]*\./i', "", $sub_value), $sub_key, $data, $sub_stack);
-
-					$stack[ $index ][ $key ] = $sub_stack;
+					$stack[ $value ] = [$column_key, 'String'];
 				}
 			}
+			else
+			{
+				$this->columnMap($value, $stack);
+			}
 		}
-		else
+
+		return $stack;
+	}
+
+	protected function dataMap($data, $columns, $column_map, &$stack)
+	{
+		foreach ($columns as $key => $value)
 		{
-			if ($index !== false)
+			if (is_int($key))
 			{
-				$stack[ $index ][ $value ] = $data[ $value ];
+				$map = $column_map[ $value ];
+				$column_key = $map[ 0 ];
+
+				if (isset($map[ 1 ]))
+				{
+					switch ($map[ 1 ])
+					{
+						case 'Number':
+						case 'Int':
+							$stack[ $column_key ] = (int) $data[ $column_key ];
+							break;
+
+						case 'Bool':
+							$stack[ $column_key ] = (bool) $data[ $column_key ];
+							break;
+
+						case 'Object':
+							$stack[ $column_key ] = unserialize($data[ $column_key ]);
+							break;
+
+						case 'JSON':
+							$stack[ $column_key ] = json_decode($data[ $column_key ], true);
+							break;
+
+						case 'String':
+							$stack[ $column_key ] = $data[ $column_key ];
+							break;
+					}
+				}
+				else
+				{
+					$stack[ $column_key ] = $data[ $column_key ];
+				}
 			}
 			else
 			{
-				if (preg_match('/[a-zA-Z0-9_\-\.]*\s*\(([a-zA-Z0-9_\-]*)\)/i', $key, $key_match))
-				{
-					$key = $key_match[ 1 ];
-				}
+				$current_stack = [];
+
+				$this->dataMap($data, $value, $column_map, $current_stack);
 
-				$stack[ $key ] = $data[ $key ];
+				$stack[ $key ] = $current_stack;
 			}
 		}
 	}
 
 	public function select($table, $join, $columns = null, $where = null)
 	{
-		$column = $where == null ? $join : $columns;
+		$map = [];
+		$stack = [];
+		$column_map = [];
 
-		$is_single_column = (is_string($column) && $column !== '*');
+		$index = 0;
 
-		$query = $this->query($this->select_context($table, $join, $columns, $where));
+		$column = $where === null ? $join : $columns;
 
-		$stack = array();
+		$is_single_column = (is_string($column) && $column !== '*');
 
-		$index = 0;
+		$query = $this->exec($this->selectContext($table, $map, $join, $columns, $where), $map);
+
+		$this->columnMap($columns, $column_map);
 
 		if (!$query)
 		{
@@ -770,19 +1063,13 @@ class medoo
 			return $query->fetchAll(PDO::FETCH_COLUMN);
 		}
 
-		while ($row = $query->fetch(PDO::FETCH_ASSOC))
+		while ($data = $query->fetch(PDO::FETCH_ASSOC))
 		{
-			foreach ($columns as $key => $value)
-			{
-				if (is_array($value))
-				{
-					$this->data_map($index, $key, $value, $row, $stack);
-				}
-				else
-				{
-					$this->data_map($index, $key, preg_replace('/^[\w]*\./i', "", $value), $row, $stack);
-				}
-			}
+			$current_stack = [];
+
+			$this->dataMap($data, $columns, $column_map, $current_stack);
+
+			$stack[ $index ] = $current_stack;
 
 			$index++;
 		}
@@ -792,158 +1079,229 @@ class medoo
 
 	public function insert($table, $datas)
 	{
-		$lastId = array();
+		$stack = [];
+		$columns = [];
+		$fields = [];
+		$map = [];
 
-		// Check indexed or associative array
 		if (!isset($datas[ 0 ]))
 		{
-			$datas = array($datas);
+			$datas = [$datas];
 		}
 
 		foreach ($datas as $data)
 		{
-			$values = array();
-			$columns = array();
-
 			foreach ($data as $key => $value)
 			{
-				$columns[] = $this->column_quote(preg_replace("/^(\(JSON\)\s*|#)/i", "", $key));
+				$columns[] = $key;
+			}
+		}
 
-				switch (gettype($value))
+		$columns = array_unique($columns);
+
+		foreach ($datas as $data)
+		{
+			$values = [];
+
+			foreach ($columns as $key)
+			{
+				if (strpos($key, '#') === 0)
 				{
-					case 'NULL':
-						$values[] = 'NULL';
-						break;
+					$values[] = $this->fnQuote($key, $data[ $key ]);	
+					continue;
+				}
 
-					case 'array':
-						preg_match("/\(JSON\)\s*([\w]+)/i", $key, $column_match);
+				$map_key =$this->mapKey();
 
-						$values[] = isset($column_match[ 0 ]) ?
-								$this->quote(json_encode($value)) :
-								$this->quote(serialize($value));
-						break;
+				$values[] = $map_key;
 
-					case 'boolean':
-						$values[] = ($value ? '1' : '0');
-						break;
+				if (!isset($data[ $key ]))
+				{
+					$map[ $map_key ] = [null, PDO::PARAM_NULL];
+				}
+				else
+				{
+					$value = $data[ $key ];
 
-					case 'integer':
-					case 'double':
-					case 'string':
-						$values[] = $this->fn_quote($key, $value);
-						break;
+					switch (gettype($value))
+					{
+						case 'NULL':
+							$map[ $map_key ] = [null, PDO::PARAM_NULL];
+							break;
+
+						case 'array':
+							$map[ $map_key ] = [
+								strpos($key, '[JSON]') === strlen($key) - 6 ?
+									json_encode($value) :
+									serialize($value),
+								PDO::PARAM_STR
+							];
+							break;
+
+						case 'object':
+							$map[ $map_key ] = [serialize($value), PDO::PARAM_STR];
+							break;
+
+						case 'resource':
+							$map[ $map_key ] = [$value, PDO::PARAM_LOB];
+							break;
+
+						case 'boolean':
+							$map[ $map_key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+							break;
+
+						case 'integer':
+						case 'double':
+							$map[ $map_key ] = [$value, PDO::PARAM_INT];
+							break;
+
+						case 'string':
+							$map[ $map_key ] = [$value, PDO::PARAM_STR];
+							break;
+					}
 				}
 			}
 
-			$this->exec('INSERT INTO ' . $this->table_quote($table) . ' (' . implode(', ', $columns) . ') VALUES (' . implode($values, ', ') . ')');
+			$stack[] = '(' . implode($values, ', ') . ')';
+		}
 
-			$lastId[] = $this->pdo->lastInsertId();
+		foreach ($columns as $key)
+		{
+			$fields[] = $this->columnQuote(preg_replace("/(^#|\s*\[JSON\]$)/i", '', $key));
 		}
 
-		return count($lastId) > 1 ? $lastId : $lastId[ 0 ];
+		return $this->exec('INSERT INTO ' . $this->tableQuote($table) . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $stack), $map);
 	}
 
 	public function update($table, $data, $where = null)
 	{
-		$fields = array();
+		$fields = [];
+		$map = [];
 
 		foreach ($data as $key => $value)
 		{
-			preg_match('/([\w]+)(\[(\+|\-|\*|\/)\])?/i', $key, $match);
+			$column = $this->columnQuote(preg_replace("/(^#|\s*\[(JSON|\+|\-|\*|\/)\]$)/i", '', $key));
 
-			if (isset($match[ 3 ]))
+			if (strpos($key, '#') === 0)
+			{
+				$fields[] = $column . ' = ' . $value;
+				continue;
+			}
+
+			$map_key = $this->mapKey();
+
+			preg_match('/(?<column>[a-zA-Z0-9_]+)(\[(?<operator>\+|\-|\*|\/)\])?/i', $key, $match);
+
+			if (isset($match[ 'operator' ]))
 			{
 				if (is_numeric($value))
 				{
-					$fields[] = $this->column_quote($match[ 1 ]) . ' = ' . $this->column_quote($match[ 1 ]) . ' ' . $match[ 3 ] . ' ' . $value;
+					$fields[] = $column . ' = ' . $column . ' ' . $match[ 'operator' ] . ' ' . $value;
 				}
 			}
 			else
 			{
-				$column = $this->column_quote(preg_replace("/^(\(JSON\)\s*|#)/i", "", $key));
+				$fields[] = $column . ' = ' . $map_key;
 
 				switch (gettype($value))
 				{
 					case 'NULL':
-						$fields[] = $column . ' = NULL';
+						$map[ $map_key ] = [null, PDO::PARAM_NULL];
 						break;
 
 					case 'array':
-						preg_match("/\(JSON\)\s*([\w]+)/i", $key, $column_match);
+						$map[ $map_key ] = [
+							strpos($key, '[JSON]') === strlen($key) - 6 ?
+								json_encode($value) :
+								serialize($value),
+							PDO::PARAM_STR
+						];
+						break;
 
-						$fields[] = $column . ' = ' . $this->quote(
-										isset($column_match[ 0 ]) ? json_encode($value) : serialize($value)
-								);
+					case 'object':
+						$map[ $map_key ] = [serialize($value), PDO::PARAM_STR];
+						break;
+
+					case 'resource':
+						$map[ $map_key ] = [$value, PDO::PARAM_LOB];
 						break;
 
 					case 'boolean':
-						$fields[] = $column . ' = ' . ($value ? '1' : '0');
+						$map[ $map_key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
 						break;
 
 					case 'integer':
 					case 'double':
+						$map[ $map_key ] = [$value, PDO::PARAM_INT];
+						break;
+
 					case 'string':
-						$fields[] = $column . ' = ' . $this->fn_quote($key, $value);
+						$map[ $map_key ] = [$value, PDO::PARAM_STR];
 						break;
 				}
 			}
 		}
 
-		return $this->exec('UPDATE ' . $this->table_quote($table) . ' SET ' . implode(', ', $fields) . $this->where_clause($where));
+		return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $fields) . $this->whereClause($where, $map), $map);
 	}
 
 	public function delete($table, $where)
 	{
-		return $this->exec('DELETE FROM ' . $this->table_quote($table) . $this->where_clause($where));
+		$map = [];
+
+		return $this->exec('DELETE FROM ' . $this->tableQuote($table) . $this->whereClause($where, $map), $map);
 	}
 
-	public function replace($table, $columns, $search = null, $replace = null, $where = null)
+	public function replace($table, $columns, $where = null)
 	{
+		$map = [];
+
 		if (is_array($columns))
 		{
-			$replace_query = array();
+			$replace_query = [];
 
 			foreach ($columns as $column => $replacements)
 			{
-				foreach ($replacements as $replace_search => $replace_replacement)
+				if (is_array($replacements[ 0 ]))
 				{
-					$replace_query[] = $column . ' = REPLACE(' . $this->column_quote($column) . ', ' . $this->quote($replace_search) . ', ' . $this->quote($replace_replacement) . ')';
-				}
-			}
+					foreach ($replacements as $replacement)
+					{
+						$map_key = $this->mapKey();
 
-			$replace_query = implode(', ', $replace_query);
-			$where = $search;
-		}
-		else
-		{
-			if (is_array($search))
-			{
-				$replace_query = array();
+						$replace_query[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)';
 
-				foreach ($search as $replace_search => $replace_replacement)
-				{
-					$replace_query[] = $columns . ' = REPLACE(' . $this->column_quote($columns) . ', ' . $this->quote($replace_search) . ', ' . $this->quote($replace_replacement) . ')';
+						$map[ $map_key . 'a' ] = [$replacement[ 0 ], PDO::PARAM_STR];
+						$map[ $map_key . 'b' ] = [$replacement[ 1 ], PDO::PARAM_STR];
+					}
 				}
+				else
+				{
+					$map_key = $this->mapKey();
 
-				$replace_query = implode(', ', $replace_query);
-				$where = $replace;
-			}
-			else
-			{
-				$replace_query = $columns . ' = REPLACE(' . $this->column_quote($columns) . ', ' . $this->quote($search) . ', ' . $this->quote($replace) . ')';
+					$replace_query[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)';
+
+					$map[ $map_key . 'a' ] = [$replacements[ 0 ], PDO::PARAM_STR];
+					$map[ $map_key . 'b' ] = [$replacements[ 1 ], PDO::PARAM_STR];
+				}
 			}
+
+			$replace_query = implode(', ', $replace_query);
 		}
 
-		return $this->exec('UPDATE ' . $this->table_quote($table) . ' SET ' . $replace_query . $this->where_clause($where));
+		return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . $replace_query . $this->whereClause($where, $map), $map);
 	}
 
 	public function get($table, $join = null, $columns = null, $where = null)
 	{
-		$column = $where == null ? $join : $columns;
+		$map = [];
+		$stack = [];
+		$column_map = [];
+
+		$column = $where === null ? $join : $columns;
 
 		$is_single_column = (is_string($column) && $column !== '*');
 
-		$query = $this->query($this->select_context($table, $join, $columns, $where) . ' LIMIT 1');
+		$query = $this->exec($this->selectContext($table, $map, $join, $columns, $where) . ' LIMIT 1', $map);
 
 		if ($query)
 		{
@@ -951,31 +1309,21 @@ class medoo
 
 			if (isset($data[ 0 ]))
 			{
-				if ($is_single_column)
-				{
-					return $data[ 0 ][ preg_replace('/^[\w]*\./i', "", $column) ];
-				}
-
 				if ($column === '*')
 				{
 					return $data[ 0 ];
 				}
 
-				$stack = array();
+				$this->columnMap($columns, $column_map);
 
-				foreach ($columns as $key => $value)
+				$this->dataMap($data[ 0 ], $columns, $column_map, $stack);
+
+				if ($is_single_column)
 				{
-					if (is_array($value))
-					{
-						$this->data_map(0, $key, $value, $data[ 0 ], $stack);
-					}
-					else
-					{
-						$this->data_map(0, $key, preg_replace('/^[\w]*\./i', "", $value), $data[ 0 ], $stack);
-					}
+					return $stack[ $column_map[ $column ][ 0 ] ];
 				}
 
-				return $stack[ 0 ];
+				return $stack;
 			}
 			else
 			{
@@ -990,9 +1338,10 @@ class medoo
 
 	public function has($table, $join, $where = null)
 	{
+		$map = [];
 		$column = null;
 
-		$query = $this->query('SELECT EXISTS(' . $this->select_context($table, $join, $column, $where, 1) . ')');
+		$query = $this->exec('SELECT EXISTS(' . $this->selectContext($table, $map, $join, $column, $where, 1) . ')', $map);
 
 		if ($query)
 		{
@@ -1006,14 +1355,18 @@ class medoo
 
 	public function count($table, $join = null, $column = null, $where = null)
 	{
-		$query = $this->query($this->select_context($table, $join, $column, $where, 'COUNT'));
+		$map = [];
+
+		$query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'COUNT'), $map);
 
 		return $query ? 0 + $query->fetchColumn() : false;
 	}
 
 	public function max($table, $join, $column = null, $where = null)
 	{
-		$query = $this->query($this->select_context($table, $join, $column, $where, 'MAX'));
+		$map = [];
+
+		$query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'MAX'), $map);
 
 		if ($query)
 		{
@@ -1029,7 +1382,9 @@ class medoo
 
 	public function min($table, $join, $column = null, $where = null)
 	{
-		$query = $this->query($this->select_context($table, $join, $column, $where, 'MIN'));
+		$map = [];
+
+		$query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'MIN'), $map);
 
 		if ($query)
 		{
@@ -1045,14 +1400,18 @@ class medoo
 
 	public function avg($table, $join, $column = null, $where = null)
 	{
-		$query = $this->query($this->select_context($table, $join, $column, $where, 'AVG'));
+		$map = [];
+
+		$query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'AVG'), $map);
 
 		return $query ? 0 + $query->fetchColumn() : false;
 	}
 
 	public function sum($table, $join, $column = null, $where = null)
 	{
-		$query = $this->query($this->select_context($table, $join, $column, $where, 'SUM'));
+		$map = [];
+
+		$query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'SUM'), $map);
 
 		return $query ? 0 + $query->fetchColumn() : false;
 	}
@@ -1080,6 +1439,26 @@ class medoo
 		}
 	}
 
+	public function id()
+	{
+		$type = $this->database_type;
+
+		if ($type === 'oracle')
+		{
+			return 0;
+		}
+		elseif ($type === 'mssql')
+		{
+			return $this->pdo->query('SELECT SCOPE_IDENTITY()')->fetchColumn();
+		}
+		elseif ($type === 'pgsql')
+		{
+			return $this->pdo->query('SELECT LASTVAL()')->fetchColumn();
+		}
+
+		return $this->pdo->lastInsertId();
+	}
+
 	public function debug()
 	{
 		$this->debug_mode = true;
@@ -1089,32 +1468,39 @@ class medoo
 
 	public function error()
 	{
-		return $this->pdo->errorInfo();
+		return $this->statement ? $this->statement->errorInfo() : null;
 	}
 
-	public function last_query()
+	public function last()
 	{
-		return end($this->logs);
+		$log = end($this->logs);
+
+		return $this->generate($log[ 0 ], $log[ 1 ]);
 	}
 
 	public function log()
 	{
-		return $this->logs;
+		return array_map(function ($log)
+			{
+				return $this->generate($log[ 0 ], $log[ 1 ]);
+			},
+			$this->logs
+		);
 	}
 
 	public function info()
 	{
-		$output = array(
-				'server' => 'SERVER_INFO',
-				'driver' => 'DRIVER_NAME',
-				'client' => 'CLIENT_VERSION',
-				'version' => 'SERVER_VERSION',
-				'connection' => 'CONNECTION_STATUS'
-		);
+		$output = [
+			'server' => 'SERVER_INFO',
+			'driver' => 'DRIVER_NAME',
+			'client' => 'CLIENT_VERSION',
+			'version' => 'SERVER_VERSION',
+			'connection' => 'CONNECTION_STATUS'
+		];
 
 		foreach ($output as $key => $value)
 		{
-			$output[ $key ] = $this->pdo->getAttribute(constant('PDO::ATTR_' . $value));
+			$output[ $key ] = @$this->pdo->getAttribute(constant('PDO::ATTR_' . $value));
 		}
 
 		return $output;
-- 
GitLab