<?php
/**
 * This file defines PDOEngine class.
 *
 * @package SQLite Integration
 * @author Kojima Toshiyasu
 *
 */

/**
 * This class extends PDO class and does the real work.
 *
 * It accepts a request from wpdb class, initialize PDO instance,
 * execute SQL statement, and returns the results to WordPress.
 */
class PDOEngine extends PDO {
	/**
	 * Class variable to check if there is an error.
	 *
	 * @var boolean
	 */
	public  $is_error = false;
	/**
	 * Class variable which is used for CALC_FOUND_ROW query.
	 *
	 * @var unsigned integer
	 */
	public  $found_rows_result = null;
	/**
	 * Class variable used for query with ORDER BY FIELD()
	 *
	 * @var array of the object
	 */
	public $pre_ordered_results = null;
	/**
	 * Class variable to store the rewritten queries.
	 *
	 * @var array
	 * @access private
	 */
	private $rewritten_query;
	/**
	 * Class variable to have what kind of query to execute.
	 *
	 * @var string
	 * @access private
	 */
	private $query_type;
	/**
	 * Class variable to store the result of the query.
	 *
	 * @var reference to the PHP object
	 * @access private
	 */
	private $results = null;
	/**
	 * Class variable to store the results of the query.
	 *
	 * This is for the backward compatibility.
	 *
	 * @var reference to the PHP object
	 * @access private
	 */
	private $_results = null;
	/**
	 * Class variable to reference to the PDO instance.
	 *
	 * @var PDO object
	 * @access private
	 */
	private $pdo;
	/**
	 * Class variable to store the query string prepared to execute.
	 *
	 * @var string|array
	 */
	private $prepared_query;
	/**
	 * Class variable to store the values in the query string.
	 *
	 * @var array
	 * @access private
	 */
	private $extracted_variables = array();
	/**
	 * Class variable to store the error messages.
	 *
	 * @var array
	 * @access private
	 */
	private $error_messages = array();
	/**
	 * Class variable to store the file name and function to cause error.
	 *
	 * @var array
	 * @access private
	 */
	private $errors;
	/**
	 * Class variable to store the query strings.
	 *
	 * @var array
	 */
	public  $queries = array();
	/**
	 * Class variable to store the affected row id.
	 *
	 * @var unsigned integer
	 * @access private
	 */
	private $last_insert_id;
	/**
	 * Class variable to store the number of rows affected.
	 *
	 * @var unsigned integer
	 */
	private $affected_rows;
	/**
	 * Class variable to store the queried column info.
	 *
	 * @var array
	 */
	private $column_data;
	/**
	 * Variable to emulate MySQL affected row.
	 *
	 * @var integer
	 */
	private $num_rows;
	/**
	 * Return value from query().
	 *
	 * Each query has its own return value.
	 *
	 * @var mixed
	 */
	private $return_value;
	/**
	 * Variable to determine which insert query to use.
	 *
	 * Whether VALUES clause in the INSERT query can take multiple values or not
	 * depends on the version of SQLite library. We check the version and set
	 * this varable to true or false.
	 *
	 * @var boolean
	 */
	private $can_insert_multiple_rows = false;
	/**
	 *
	 * @var integer
	 */
	private $param_num;
	/**
	 * Varible to check if there is an active transaction.
	 * @var boolean
	 * @access protected
	 */
	protected $has_active_transaction = false;

	/**
	 * Constructor
	 *
	 * Create PDO object, set user defined functions and initialize other settings.
	 * Don't use parent::__construct() because this class does not only returns
	 * PDO instance but many others jobs.
	 *
	 * Constructor definition is changed since version 1.7.1.
	 *
	 * @param none
	 */
	function __construct() {
		register_shutdown_function(array($this, '__destruct'));
        if (!is_file(FQDB)) {
            $this->prepare_directory();
        }
		$dsn = 'sqlite:' . FQDB;
		if (isset($GLOBALS['@pdo'])) {
			$this->pdo = $GLOBALS['@pdo'];
		} else {
			$locked = false;
			$status = 0;
			do {
				try {
					$this->pdo = new PDO($dsn, null, null, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
					require_once UDF_FILE;
					new PDOSQLiteUDFS($this->pdo);
					$GLOBALS['@pdo'] = $this->pdo;
				} catch (PDOException $ex) {
					$status = $ex->getCode();
					if ($status == 5 || $status == 6) {
						$locked = true;
					} else {
						$err_message = $ex->getMessage();
					}
				}
			} while ($locked);
			if ($status > 0) {
				$message = 'Database initialization error!<br />' .
						   'Code: ' . $status . '<br />Error Message: ' . $err_message;
				$this->set_error(__LINE__, __FILE__, $message);
				return false;
			}
		}
		$this->init();
	}
	/**
	 * Destructor
	 *
	 * If SQLITE_MEM_DEBUG constant is defined, append information about
	 * memory usage into database/mem_debug.txt.
	 *
	 * This definition is changed since version 1.7.
	 *
	 * @return boolean
	 */
	function __destruct() {
		if (defined('SQLITE_MEM_DEBUG') && SQLITE_MEM_DEBUG) {
			$max = ini_get('memory_limit');
			if (is_null($max)) {
				$message = sprintf("[%s] Memory_limit is not set in php.ini file.", date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']));
				file_put_contents(FQDBDIR . 'mem_debug.txt', $message, FILE_APPEND);
				return true;
			}
			if (stripos($max, 'M') !== false) {
				$max = (int) $max * 1024 * 1024;
			}
			$peak = memory_get_peak_usage(true);
			$used = round((int) $peak / (int) $max * 100, 2);
			if ($used > 90) {
				$message = sprintf("[%s] Memory peak usage warning: %s %% used. (max: %sM, now: %sM)\n", date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']), $used, $max, $peak);
				file_put_contents(FQDBDIR . 'mem_debug.txt', $message, FILE_APPEND);
			}
		}
		//$this->pdo = null;
		return true;
	}

	/**
	 * Method to initialize database, executed in the contructor.
	 *
	 * It checks if WordPress is in the installing process and does the required
	 * jobs. SQLite library version specific settings are also in this function.
	 *
	 * Some developers use WP_INSTALLING constant for other purposes, if so, this
	 * function will do no harms.
	 *
	 * @return boolean
	 */
	private function init() {
		if (defined('WP_INSTALLING') && WP_INSTALLING) {
			$statement = $this->pdo->query("SELECT COUNT(*) FROM sqlite_master WHERE type='table'");
			$number_of_tables = $statement->fetchColumn(0);
			$statement = null;
			if ($number_of_tables == 0) $this->make_sqlite_tables();
		}
		if (version_compare($this->get_sqlite_version(), '3.7.11', '>=')) {
			$this->can_insert_multiple_rows = true;
		}
		$statement = $this->pdo->query('PRAGMA foreign_keys');
		if ($statement->fetchColumn(0) == '0') $this->pdo->query('PRAGMA foreign_keys = ON');
	}

	/**
	 * This method makes database direcotry and .htaccess file.
	 *
	 * It is executed only once when the installation begins.
	 */
	private function prepare_directory() {
		global $wpdb;
		$u = umask(0000);
		if (!is_dir(FQDBDIR)) {
			if (!@mkdir(FQDBDIR, 0704, true)) {
				umask($u);
				$message = 'Unable to create the required directory! Please check your server settings.';
				wp_die($message, 'Error!');
			}
		}
		if (!is_writable(FQDBDIR)) {
			umask($u);
			$message = 'Unable to create a file in the directory! Please check your server settings.';
			wp_die($message, 'Error!');
		}
		if (!is_file(FQDBDIR . '.htaccess')) {
			$fh = fopen(FQDBDIR . '.htaccess', "w");
			if (!$fh) {
				umask($u);
				$message = 'Unable to create a file in the directory! Please check your server settings.';
				echo $message;
				return false;
			}
			fwrite($fh, 'DENY FROM ALL');
			fclose($fh);
		}
		if (!is_file(FQDBDIR . 'index.php')) {
			$fh = fopen(FQDBDIR . 'index.php', "w");
			if (!$fh) {
				umask($u);
				$message = 'Unable to create a file in the directory! Please check your server settings.';
				echo $message;
				return false;
			}
			fwrite($fh, '<?php // Silence is gold. ?>');
			fclose($fh);
		}
		umask($u);
		return true;
	}
	/**
	 * Method to call install() function which overrides WordPress install().
	 *
	 * This function is executed only once during the installation process.
	 */
	private function make_sqlite_tables() {
		require_once PDODIR . 'install.php';
	}
	/**
	 * Method to execute query().
	 *
	 * Divide the query types into seven different ones. That is to say:
	 *
	 * 1. SELECT SQL_CALC_FOUND_ROWS
	 * 2. INSERT
	 * 3. CREATE TABLE(INDEX)
	 * 4. ALTER TABLE
	 * 5. SHOW VARIABLES
	 * 6. DROP INDEX
	 * 7. THE OTHERS
	 *
	 * #1 is just a tricky play. See the private function handle_sql_count() in query.class.php.
	 * From #2 through #5 call different functions respectively.
	 * #6 call the ALTER TABLE query.
	 * #7 is a normal process: sequentially call prepare_query() and execute_query().
	 *
	 * #1 process has been changed since version 1.5.1.
	 *
	 * @param $query full SQL statement string
	 * @return mixed according to the query type
	 * @see PDO::query()
	 */
	public function query($query) {
		$this->flush();

		$this->queries[] = "Raw query:\n$query";
		$res = $this->determine_query_type($query);
		if (!$res && defined(PDO_DEBUG) && PDO_DEBUG) {
			$bailoutString = sprintf(__("<h1>Unknown query type</h1><p>Sorry, we cannot determine the type of query that is requested.</p><p>The query is %s</p>", 'sqlite-integration'), $query);
			$this->set_error(__LINE__, __FUNCTION__, $bailoutString);
		}
		switch (strtolower($this->query_type)) {
			case 'set':
				$this->return_value = false;
				break;
			case 'foundrows':
				$_column = array('FOUND_ROWS()' => '');
				$column  = array();
				if (!is_null($this->found_rows_result)) {
					$this->num_rows = $this->found_rows_result;
					$_column['FOUND_ROWS()'] = $this->num_rows;
					//foreach ($this->found_rows_result[0] as $key => $value) {
						//$_column['FOUND_ROWS()'] = $value;
					//}
					$column[]                = new ObjectArray($_column);
					$this->results           = $column;
					$this->found_rows_result = null;
				}
				break;
			case 'insert':
				if ($this->can_insert_multiple_rows) {
					$this->execute_insert_query_new($query);
				} else {
					$this->execute_insert_query($query);
				}
				break;
			case 'create':
				$result = $this->execute_create_query($query);
				$this->return_value = $result;
				break;
			case 'alter':
				$result = $this->execute_alter_query($query);
				$this->return_value = $result;
				break;
			case 'show_variables':
				$result = $this->show_variables_workaround($query);
				break;
			case 'showstatus':
				$result = $this->show_status_workaround($query);
				break;
			case 'drop_index':
				$pattern = '/^\\s*(DROP\\s*INDEX\\s*.*?)\\s*ON\\s*(.*)/im';
				if (preg_match($pattern, $query, $match)) {
					$drop_query         = 'ALTER TABLE ' . trim($match[2]) . ' ' . trim($match[1]);
					$this->query_type   = 'alter';
					$result             = $this->execute_alter_query($drop_query);
					$this->return_value = $result;
				} else {
					$this->return_value = false;
				}
				break;
			default:
				$engine = $this->prepare_engine($this->query_type);
				$this->rewritten_query = $engine->rewrite_query($query, $this->query_type);
				if (!is_null($this->pre_ordered_results)) {
					$this->results = $this->pre_ordered_results;
					$this->num_rows = $this->return_value = count($this->results);
					$this->pre_ordered_results = null;
					break;
				}
				$this->queries[]       = "Rewritten:\n$this->rewritten_query";
				$this->extract_variables();
				$statement = $this->prepare_query();
				$this->execute_query($statement);
				if (!$this->is_error) {
					$this->process_results($engine);
				} else {// Error
					;
				}
				break;
		}
		if (defined('PDO_DEBUG') && PDO_DEBUG === true) {
			file_put_contents(FQDBDIR . 'debug.txt', $this->get_debug_info(), FILE_APPEND);
		}
		return $this->return_value;
	}
	/**
	 * Method to return inserted row id.
	 *
	 * @return unsigned integer
	 */
	public function get_insert_id() {
		return $this->last_insert_id;
	}
	/**
	 * Method to return the number of rows affected.
	 *
	 * @return unsigned integer
	 */
	public function get_affected_rows() {
		return $this->affected_rows;
	}
	/**
	 * Method to return the queried column names.
	 *
	 * These data are meaningless for SQLite. So they are dummy emulating
	 * MySQL columns data.
	 *
	 * @return array of the object
	 */
	public function get_columns() {
		if (!empty($this->results)) {
			$primary_key = array(
				'meta_id', 'comment_ID', 'link_ID', 'option_id',
				'blog_id', 'option_name', 'ID', 'term_id', 'object_id',
				'term_taxonomy_id', 'umeta_id', 'id');
			$unique_key = array('term_id', 'taxonomy', 'slug');
			$data = array(
				'name'         => '', // column name
				'table'        => '', // table name
				'max_length'   => 0,  // max length of the column
				'not_null'     => 1,  // 1 if not null
				'primary_key'  => 0,  // 1 if column has primary key
				'unique_key'   => 0,  // 1 if column has unique key
				'multiple_key' => 0,  // 1 if column doesn't have unique key
				'numeric'      => 0,  // 1 if column has numeric value
				'blob'         => 0,  // 1 if column is blob
				'type'         => '', // type of the column
				'unsigned'     => 0,  // 1 if column is unsigned integer
				'zerofill'     => 0   // 1 if column is zero-filled
				);
			if (preg_match("/\s*FROM\s*(.*)?\s*/i", $this->rewritten_query, $match)) {
				$table_name = trim($match[1]);
			} else {
				$table_name = '';
			}
			foreach ($this->results[0] as $key => $value) {
				$data['name']  = $key;
				$data['table'] = $table_name;
				if (in_array($key, $primary_key)) {
					$data['primary_key'] = 1;
				} elseif (in_array($key, $unique_key)) {
					$data['unique_key'] = 1;
				} else {
					$data['multiple_key'] = 1;
				}
				$this->column_data[] = new ObjectArray($data);
				$data['name']         = '';
				$data['table']        = '';
				$data['primary_key']  = 0;
				$data['unique_key']   = 0;
				$data['multiple_key'] = 0;
			}
		return $this->column_data;
		} else {
			return null;
		}
	}
	/**
	 * Method to return the queried result data.
	 *
	 * @return mixed
	 */
	public function get_query_results() {
		return $this->results;
	}
	/**
	 * Method to return the number of rows from the queried result.
	 *
	 * @return unsigned integer
	 */
	public function get_num_rows() {
		return $this->num_rows;
	}
	/**
	 * Method to return the queried results according to the query types.
	 *
	 * @return mixed
	 */
	public function get_return_value() {
		return $this->return_value;
	}
	/**
	 * Method to return error messages.
	 *
	 * @return string
	 */
	public function get_error_message(){
		if (count($this->error_messages) === 0){
			$this->is_error       = false;
			$this->error_messages = array();
			return '';
		}
		$output = '<div style="clear:both">&nbsp;</div>';
		if ($this->is_error === false){
 			//return $output;
			return '';
		}
		$output .= "<div class=\"queries\" style=\"clear:both; margin_bottom:2px; border: red dotted thin;\">Queries made or created this session were<br/>\r\n\t<ol>\r\n";
		foreach ($this->queries as $q){
			$output .= "\t\t<li>".$q."</li>\r\n";
		}
		$output .= "\t</ol>\r\n</div>";
		foreach ($this->error_messages as $num=>$m){
			$output .= "<div style=\"clear:both; margin_bottom:2px; border: red dotted thin;\" class=\"error_message\" style=\"border-bottom:dotted blue thin;\">Error occurred at line {$this->errors[$num]['line']} in Function {$this->errors[$num]['function']}. <br/> Error message was: $m </div>";
		}

		ob_start();
		debug_print_backtrace();
		$output .= '<pre>' . ob_get_contents() . '</pre>';
		ob_end_clean();
		return $output;

	}
	/**
	 * Method to return information about query string for debugging.
	 *
	 * @return string
	 */
	private function get_debug_info(){
		$output = '';
		foreach ($this->queries as $q){
			$output .= $q ."\n";
		}
		return $output;
	}
	/**
	 * Method to clear previous data.
	 */
	private function flush(){
		$this->rewritten_query     = '';
		$this->query_type          = '';
		$this->results             = null;
		$this->_results            = null;
		$this->last_insert_id      = null;
		$this->affected_rows       = null;
		$this->column_data         = array();
		$this->num_rows            = null;
		$this->return_value        = null;
		$this->extracted_variables = array();
		$this->error_messages      = array();
		$this->is_error            = false;
		$this->queries             = array();
		$this->param_num           = 0;
	}
	/**
	 * Method to include the apropreate class files.
	 *
	 * It is not a good habit to change the include files programatically.
	 * Needs to be fixed some other way.
	 *
	 * @param string $query_type
	 * @return object reference to apropreate driver
	 */
	private function prepare_engine($query_type = null) {
		if (stripos($query_type, 'create') !== false) {
			require_once PDODIR . 'query_create.class.php';
			$engine = new CreateQuery();
		} elseif (stripos($query_type, 'alter') !== false) {
			require_once PDODIR . 'query_alter.class.php';
			$engine = new AlterQuery();
		} else {
			require_once PDODIR . 'query.class.php';
			$engine = new PDOSQLiteDriver();
		}
		return $engine;
	}
	/**
	 * Method to create a PDO statement object from the query string.
	 *
	 * @return PDOStatement
	 */
	private function prepare_query(){
		$this->queries[] = "Prepare:\n" . $this->prepared_query;
		$reason          = 0;
		$message         = '';
		$statement       = null;
		do {
			try {
				$statement = $this->pdo->prepare($this->prepared_query);
			} catch (PDOException $err) {
				$reason  = $err->getCode();
				$message = $err->getMessage();
			}
		} while (5 == $reason || 6 == $reason);

		if ($reason > 0){
			$err_message = sprintf("Problem preparing the PDO SQL Statement.  Error was: %s", $message);
			$this->set_error(__LINE__, __FUNCTION__, $err_message);
		}
		return $statement;
	}
	/**
	 * Method to execute PDO statement object.
	 *
	 * This function executes query and sets the variables to give back to WordPress.
	 * The variables are class fields. So if success, no return value. If failure, it
	 * returns void and stops.
	 *
	 * @param object $statement of PDO statement
	 * @return boolean
	 */
	private function execute_query($statement) {
		$reason  = 0;
		$message = '';
		if (!is_object($statement))
		  return false;
		if (count($this->extracted_variables) > 0) {
			$this->queries[] = "Executing:\n" . var_export($this->extracted_variables, true);
			do {
				if ($this->query_type == 'update' || $this->query_type == 'replace') {
					try {
						$this->beginTransaction();
						$statement->execute($this->extracted_variables);
						$this->commit();
					} catch (PDOException $err) {
						$reason  = $err->getCode();
						$message = $err->getMessage();
						$this->rollBack();
					}
				} else {
					try {
						$statement->execute($this->extracted_variables);
					} catch (PDOException $err) {
						$reason  = $err->getCode();
						$message = $err->getMessage();
					}
				}
			} while (5 == $reason || 6 == $reason);
		} else {
			$this->queries[] = 'Executing: (no parameters)';
			do{
				if ($this->query_type == 'update' || $this->query_type == 'replace') {
					try {
						$this->beginTransaction();
						$statement->execute();
						$this->commit();
					} catch (PDOException $err) {
						$reason  = $err->getCode();
						$message = $err->getMessage();
						$this->rollBack();
					}
				} else {
					try {
						$statement->execute();
					} catch (PDOException $err) {
						$reason  = $err->getCode();
						$message = $err->getMessage();
					}
				}
			} while (5 == $reason || 6 == $reason);
		}
		if ($reason > 0) {
			$err_message = sprintf("Error while executing query! Error message was: %s", $message);
			$this->set_error(__LINE__, __FUNCTION__, $err_message);
			return false;
		} else {
			$this->_results = $statement->fetchAll(PDO::FETCH_OBJ);
		}
		//generate the results that $wpdb will want to see
		switch ($this->query_type) {
			case 'insert':
			case 'update':
			case 'replace':
				$this->last_insert_id = $this->pdo->lastInsertId();
				$this->affected_rows  = $statement->rowCount();
				$this->return_value   = $this->affected_rows;
			  break;
			case 'select':
			case 'show':
			case 'showcolumns':
			case 'showindex':
			case 'describe':
			case 'desc':
			case 'check':
			case 'analyze':
			//case "foundrows":
				$this->num_rows     = count($this->_results);
				$this->return_value = $this->num_rows;
				break;
			case 'delete':
				$this->affected_rows = $statement->rowCount();
				$this->return_value  = $this->affected_rows;
				break;
			case 'alter':
			case 'drop':
			case 'create':
			case 'optimize':
			case 'truncate':
				if ($this->is_error) {
					$this->return_value = false;
				} else {
					$this->return_value = true;
				}
			  break;
		}
	}
	/**
	 * Method to extract field data to an array and prepare the query statement.
	 *
	 * If original SQL statement is CREATE query, this function do nothing and return
	 * true. This returned value is not used.
	 *
	 * @return boolean
	 */
	private function extract_variables() {
		if ($this->query_type == 'create') {
			$this->prepared_query = $this->rewritten_query;
			return true;
		}

		//long queries can really kill this
		$pattern = '/(?<!\\\\)([\'"])(.*?)(?<!\\\\)\\1/imsx';
		$_limit  = $limit = ini_get('pcre.backtrack_limit');
		// if user's setting is more than default * 10, make PHP do the job.
		if ($limit > 10000000) {
			$query = preg_replace_callback($pattern, array($this, 'replace_variables_with_placeholders'), $this->rewritten_query);
		} else {
			do {
				if ($limit > 10000000) {
					$message = 'The query is too big to parse properly';
					$this->set_error(__LINE__, __FUNCTION__, $message);
					break; //no point in continuing execution, would get into a loop
				} else {
					ini_set('pcre.backtrack_limit', $limit);
					$query = preg_replace_callback($pattern, array($this,'replace_variables_with_placeholders'), $this->rewritten_query);
				}
				$limit = $limit * 10;
			} while (is_null($query));

			//reset the pcre.backtrack_limist
			ini_set('pcre.backtrack_limit', $_limit);
		}
		$this->queries[]      = "With Placeholders:\n" . $query;
		$this->prepared_query = $query;
	}
	/**
	 * Call back function to replace field data with PDO parameter.
	 *
	 * @param string $matches
	 * @return string
	 */
	private function replace_variables_with_placeholders($matches) {
		//remove the wordpress escaping mechanism
		$param = stripslashes($matches[0]);

		//remove trailing spaces
		$param = trim($param);

		//remove the quotes at the end and the beginning
		if (in_array($param{strlen($param)-1}, array("'",'"'))) {
			$param = substr($param,0,-1) ;//end
		}
		if (in_array($param{0}, array("'",'"'))) {
			$param = substr($param, 1); //start
		}
		//$this->extracted_variables[] = $param;
		$key = ':param_'.$this->param_num++;
		$this->extracted_variables[] = $param;
		//return the placeholder
		//return ' ? ';
		return ' '.$key.' ';
	}

	/**
	 * Method to determine which query type the argument is.
	 *
	 * It takes the query string ,determines the type and returns the type string.
	 * If the query is the type that SQLite Integration can't executes, returns false.
	 *
	 * @param string $query
	 * @return boolean|string
	 */
	private function determine_query_type($query) {
		$result = preg_match('/^\\s*(SET|EXPLAIN|PRAGMA|SELECT\\s*FOUND_ROWS|SELECT|INSERT|UPDATE|REPLACE|DELETE|ALTER|CREATE|DROP\\s*INDEX|DROP|SHOW\\s*\\w+\\s*\\w+\\s*|DESCRIBE|DESC|TRUNCATE|OPTIMIZE|CHECK|ANALYZE)/i', $query, $match);

		if (!$result) {
			return false;
		}
		$this->query_type = strtolower($match[1]);
		if (stripos($this->query_type, 'found') !== false) {
			$this->query_type = 'foundrows';
		}
		if (stripos($this->query_type, 'show') !== false) {
			if (stripos($this->query_type, 'show table status') !== false) {
				$this->query_type = 'showstatus';
			} elseif (stripos($this->query_type, 'show tables') !== false || stripos($this->query_type, 'show full tables') !== false) {
				$this->query_type = 'show';
			} elseif (stripos($this->query_type, 'show columns') !== false || stripos($this->query_type, 'show fields') !== false || stripos($this->query_type, 'show full columns') !== false) {
				$this->query_type = 'showcolumns';
			} elseif (stripos($this->query_type, 'show index') !== false || stripos($this->query_type, 'show indexes') !== false || stripos($this->query_type, 'show keys') !== false) {
				$this->query_type = 'showindex';
			} elseif (stripos($this->query_type, 'show variables') !== false || stripos($this->query_type, 'show global variables') !== false || stripos($this->query_type, 'show session variables') !== false) {
				$this->query_type = 'show_variables';
			} else {
				return false;
			}
		}
		if (stripos($this->query_type, 'drop index') !== false) {
			$this->query_type = 'drop_index';
		}
		return true;
	}

	/**
	 * Method to execute INSERT query for SQLite version 3.7.11 or later.
	 *
	 * SQLite version 3.7.11 began to support multiple rows insert with values
	 * clause. This is for that version or later.
	 *
	 * @param string $query
	 */
	private function execute_insert_query_new($query) {
		$engine                = $this->prepare_engine($this->query_type);
		$this->rewritten_query = $engine->rewrite_query($query, $this->query_type);
		$this->queries[]       = "Rewritten:\n" . $this->rewritten_query;
		$this->extract_variables();
		$statement             = $this->prepare_query();
		$this->execute_query($statement);
	}
	/**
	 * Method to execute INSERT query for SQLite version 3.7.10 or lesser.
	 *
	 * It executes the INSERT query for SQLite version 3.7.10 or lesser. It is
	 * necessary to rewrite multiple row values.
	 *
	 * @param string $query
	 */
	private function execute_insert_query($query) {
		global $wpdb;
		$multi_insert = false;
		$statement    = null;
		$engine       = $this->prepare_engine($this->query_type);
		if (preg_match('/(INSERT.*?VALUES\\s*)(\(.*\))/imsx', $query, $matched)) {
			$query_prefix = $matched[1];
			$values_data  = $matched[2];
			if (stripos($values_data, 'ON DUPLICATE KEY') !== false) {
				$exploded_parts = $values_data;
			} elseif (stripos($query_prefix, "INSERT INTO $wpdb->comments") !== false) {
				$exploded_parts = $values_data;
			} else {
				$exploded_parts = $this->parse_multiple_inserts($values_data);
			}
			$count = count($exploded_parts);
			if ($count > 1) {
				$multi_insert = true;
			}
		}
		if ($multi_insert) {
			$first = true;
			foreach ($exploded_parts as $value) {
				if (substr($value, -1, 1) === ')') {
					$suffix = '';
				} else {
					$suffix = ')';
				}
				$query_string              = $query_prefix . ' ' . $value . $suffix;
				$this->rewritten_query     = $engine->rewrite_query($query_string, $this->query_type);
				$this->queries[]           = "Rewritten:\n" . $this->rewritten_query;
				$this->extracted_variables = array();
				$this->extract_variables();
				if ($first) {
					$statement = $this->prepare_query();
					$this->execute_query($statement);
					$first = false;
				} else {
					$this->execute_query($statement);
				}
			}
		} else {
			$this->rewritten_query = $engine->rewrite_query($query, $this->query_type);
			$this->queries[]       = "Rewritten:\n" . $this->rewritten_query;
			$this->extract_variables();
			$statement = $this->prepare_query();
			$this->execute_query($statement);
		}
	}

	/**
	 * Method to help rewriting multiple row values insert query.
	 *
	 * It splits the values clause into an array to execute separately.
	 *
	 * @param string $values
	 * @return array
	 */
	private function parse_multiple_inserts($values) {
		$tokens = preg_split("/(''|(?<!\\\\)'|(?<!\()\),(?=\s*\())/s", $values, -1, PREG_SPLIT_DELIM_CAPTURE);
		$exploded_parts = array();
		$part = '';
		$literal = false;
		foreach ($tokens as $token) {
			switch ($token) {
				case "),":
					if (!$literal) {
						$exploded_parts[] = $part;
						$part = '';
					} else {
						$part .= $token;
					}
					break;
				case "'":
					if ($literal) {
						$literal = false;
					} else {
						$literal = true;
					}
					$part .= $token;
					break;
				default:
					$part .= $token;
					break;
			}
		}
		if (!empty($part)) {
			$exploded_parts[] = $part;
		}
		return $exploded_parts;
	}

	/**
	 * Method to execute CREATE query.
	 *
	 * @param string
	 * @return boolean
	 */
	private function execute_create_query($query) {
		$engine          = $this->prepare_engine($this->query_type);
		$rewritten_query = $engine->rewrite_query($query);
		$reason          = 0;
		$message         = '';
		//$queries = explode(";", $this->rewritten_query);
		try {
			$this->beginTransaction();
			foreach ($rewritten_query as $single_query) {
				$this->queries[] = "Executing:\n" . $single_query;
				$single_query    = trim($single_query);
				if (empty($single_query)) continue;
					$this->pdo->exec($single_query);
			}
			$this->commit();
		} catch (PDOException $err) {
			$reason  = $err->getCode();
			$message = $err->getMessage();
			if (5 == $reason || 6 == $reason) {
				$this->commit();
			} else {
				$this->rollBack();
			}
		}
		if ($reason > 0) {
			$err_message = sprintf("Problem in creating table or index. Error was: %s", $message);
			$this->set_error(__LINE__, __FUNCTION__, $err_message);
			return false;
		}
		return true;
	}
	/**
	 * Method to execute ALTER TABLE query.
	 *
	 * @param string
	 * @return boolean
	 */
	private function execute_alter_query($query) {
		$engine          = $this->prepare_engine($this->query_type);
		$reason          = 0;
		$message         = '';
		$re_query        = '';
		$rewritten_query = $engine->rewrite_query($query, $this->query_type);
		if (is_array($rewritten_query) && array_key_exists('recursion', $rewritten_query)) {
			$re_query = $rewritten_query['recursion'];
			unset($rewritten_query['recursion']);
		}
		try {
			$this->beginTransaction();
			if (is_array($rewritten_query)) {
				foreach ($rewritten_query as $single_query) {
					$this->queries[] = "Executing:\n" . $single_query;
					$single_query    = trim($single_query);
					if (empty($single_query)) continue;
					$this->pdo->exec($single_query);
				}
			} else {
				$this->queries[] = "Executing:\n" . $rewritten_query;
				$rewritten_query = trim($rewritten_query);
				$this->pdo->exec($rewritten_query);
			}
			$this->commit();
		} catch (PDOException $err) {
			$reason  = $err->getCode();
			$message = $err->getMessage();
			if (5 == $reason || 6 == $reason) {
				$this->commit();
				usleep(10000);
			} else {
				$this->rollBack();
			}
		}
		if ($re_query != '') {
			$this->query($re_query);
		}
		if ($reason > 0) {
			$err_message = sprintf("Problem in executing alter query. Error was: %s", $message);
			$this->set_error(__LINE__, __FUNCTION__, $err_message);
			return false;
		}
		return true;
	}

	/**
	 * Method to execute SHOW VARIABLES query
	 *
	 * This query is meaningless for SQLite. This function returns null data with some
	 * exceptions and only avoids the error message.
	 *
	 * @param string
	 * @return ObjectArray
	 */
	private function show_variables_workaround($query) {
		$dummy_data = array('Variable_name' => '', 'Value' => null);
		$pattern    = '/SHOW\\s*VARIABLES\\s*LIKE\\s*(.*)?$/im';
		if (preg_match($pattern, $query, $match)) {
			$value = str_replace("'", '', $match[1]);
			$dummy_data['Variable_name'] = trim($value);
			// this is set for Wordfence Security Plugin
			if ($value == 'max_allowed_packet') {
				$dummy_data['Value'] = 1047552;
			} else {
				$dummy_data['Value'] = '';
			}
		}
		$_results[]         = new ObjectArray($dummy_data);
		$this->results      = $_results;
		$this->num_rows     = count($this->results);
		$this->return_value = $this->num_rows;
		return true;
	}
	/**
	 * Method to execute SHOW TABLE STATUS query.
	 *
	 * This query is meaningless for SQLite. This function return dummy data.
	 *
	 * @param string
	 * @return ObjectArray
	 */
	private function show_status_workaround($query) {
		$pattern = '/^SHOW\\s*TABLE\\s*STATUS\\s*LIKE\\s*(.*?)$/im';
		if (preg_match($pattern, $query, $match)) {
			$table_name = str_replace("'", '', $match[1]);
		} else {
			$table_name = '';
		}
		$dummy_data = array(
			'Name'   => $table_name, 'Engine'          => '', 'Version'        => '',
			'Row_format'      => '', 'Rows'            =>  0, 'Avg_row_length' =>  0,
			'Data_length'     =>  0, 'Max_data_length' =>  0, 'Index_length'   =>  0,
			'Data_free'       =>  0, 'Auto_increment'  =>  0, 'Create_time'    => '',
			'Update_time'     => '', 'Check_time'      => '', 'Collation'      => '',
			'Checksum'        => '', 'Create_options'  => '', 'Comment'        => ''
		);
		$_results[]         = new ObjectArray($dummy_data);
		$this->results      = $_results;
		$this->num_rows     = count($this->results);
		$this->return_value = $this->num_rows;
		return true;
	}
	/**
	 * Method to format the queried data to that of MySQL.
	 *
	 * @param string $engine
	 */
	private function process_results($engine) {
		if (in_array($this->query_type, array('describe', 'desc', 'showcolumns'))) {
			$this->convert_to_columns_object();
		} elseif ('showindex' === $this->query_type){
			$this->convert_to_index_object();
		} elseif (in_array($this->query_type, array('check', 'analyze'))) {
			$this->convert_result_check_or_analyze();
		} else {
			$this->results = $this->_results;
		}
	}
	/**
	 * Method to format the error messages and put out to the file.
	 *
	 * When $wpdb::suppress_errors is set to true or $wpdb::show_errors is set to false,
	 * the error messages are ignored.
	 *
	 * @param string $line where the error occurred.
	 * @param string $function to indicate the function name where the error occurred.
	 * @param string $message
	 * @return boolean
	 */
	private function set_error ($line, $function, $message){
		global $wpdb;
		$this->errors[]         = array("line"=>$line, "function"=>$function);
		$this->error_messages[] = $message;
		$this->is_error         = true;
		if ($wpdb->suppress_errors) return false;
		if (!$wpdb->show_errors) return false;
		file_put_contents (FQDBDIR .'debug.txt', "Line $line, Function: $function, Message: $message \n", FILE_APPEND);
	}
	/**
	 * Method to change the queried data to PHP object format.
	 *
	 * It takes the associative array of query results and creates a numeric
	 * array of anonymous objects
	 *
	 * @access private
	 */
	private function convert_to_object(){
		$_results = array();
		if (count ($this->results) === 0){
			echo $this->get_error_message();
		} else {
			foreach($this->results as $row){
				$_results[] = new ObjectArray($row);
			}
		}
		$this->results = $_results;
	}
	/**
	 * Method to convert the SHOW COLUMNS query data to an object.
	 *
	 * It rewrites pragma results to mysql compatible array
	 * when query_type is describe, we use sqlite pragma function.
	 *
	 * @access private
	 */
	private function convert_to_columns_object() {
		$_results = array();
		$_columns = array( //Field names MySQL SHOW COLUMNS returns
			'Field'   => "",
			'Type'    => "",
			'Null'    => "",
			'Key'     => "",
			'Default' => "",
			'Extra'   => ""
		);
		if (count($this->_results) === 0) {
			echo $this->get_error_message();
		} else {
			foreach ($this->_results as $row) {
				$_columns['Field']   = $row->name;
				$_columns['Type']    = $row->type;
				$_columns['Null']    = $row->notnull ? "NO" : "YES";
				$_columns['Key']     = $row->pk ? "PRI" : "";
				$_columns['Default'] = $row->dflt_value;
				$_results[]          = new ObjectArray($_columns);
			}
		}
		$this->results = $_results;
	}
	/**
	 * Method to convert SHOW INDEX query data to PHP object.
	 *
	 * It rewrites the result of SHOW INDEX to the Object compatible with MySQL
	 * added the WHERE clause manipulation (ver 1.3.1)
	 *
	 * @access private
	 */
	private function convert_to_index_object() {
		$_results = array();
		$_columns = array(
			'Table'        => "",
			'Non_unique'   => "",// unique -> 0, not unique -> 1
			'Key_name'     => "",// the name of the index
			'Seq_in_index' => "",// column sequence number in the index. begins at 1
			'Column_name'  => "",
			'Collation'    => "",//A(scend) or NULL
			'Cardinality'  => "",
			'Sub_part'     => "",// set to NULL
			'Packed'       => "",// How to pack key or else NULL
			'Null'         => "",// If column contains null, YES. If not, NO.
			'Index_type'   => "",// BTREE, FULLTEXT, HASH, RTREE
			'Comment'      => ""
		);
		if (count($this->_results) == 0) {
			echo $this->get_error_message();
		} else {
			foreach ($this->_results as $row) {
				if ($row->type == 'table' && !stripos($row->sql, 'primary'))
					continue;
				if ($row->type == 'index' && stripos($row->name, 'sqlite_autoindex') !== false)
					continue;
				switch ($row->type) {
					case 'table':
						$pattern1 = '/^\\s*PRIMARY.*\((.*)\)/im';
						$pattern2 = '/^\\s*(\\w+)?\\s*.*PRIMARY.*(?!\()/im';
						if (preg_match($pattern1, $row->sql, $match)) {
							$col_name = trim($match[1]);
							$_columns['Key_name']    = 'PRIMARY';
							$_columns['Non_unique']  = 0;
							$_columns['Column_name'] = $col_name;
						} elseif (preg_match($pattern2, $row->sql, $match)) {
							$col_name = trim($match[1]);
							$_columns['Key_name']    = 'PRIMARY';
							$_columns['Non_unique']  = 0;
							$_columns['Column_name'] = $col_name;
						}
						break;
					case 'index':
						if (stripos($row->sql, 'unique') !== false) {
							$_columns['Non_unique'] = 0;
						} else {
							$_columns['Non_unique'] = 1;
						}
						if (preg_match('/^.*\((.*)\)/i', $row->sql, $match)) {
							$col_name = str_replace("'", '', $match[1]);
							$_columns['Column_name'] = trim($col_name);
						}
						$_columns['Key_name'] = $row->name;
						break;
					default:
						break;
				}
				$_columns['Table']       = $row->tbl_name;
				$_columns['Collation']   = NULL;
				$_columns['Cardinality'] = 0;
				$_columns['Sub_part']    = NULL;
				$_columns['Packed']      = NULL;
				$_columns['Null']        = 'NO';
				$_columns['Index_type']  = 'BTREE';
				$_columns['Comment']     = '';
				$_results[]              = new ObjectArray($_columns);
			}
			if (stripos($this->queries[0], 'WHERE') !== false) {
				preg_match('/WHERE\\s*(.*)$/im', $this->queries[0], $match);
				list($key, $value) = explode('=', $match[1]);
				$key = trim($key);
				$value = preg_replace("/[\';]/", '', $value);
				$value = trim($value);
				foreach ($_results as $result) {
					if (stripos($value, $result->$key) !== false) {
						unset($_results);
						$_results[] = $result;
						break;
					}
				}
			}
		}
		$this->results = $_results;
	}
	/**
	 * Method to the CHECK query data to an object.
	 *
	 * @access private
	 */
	private function convert_result_check_or_analyze() {
		$results = array();
		if ($this->query_type == 'check') {
			$_columns = array(
				'Table' => '',
				'Op'    => 'check',
				'Msg_type' => 'status',
				'Msg_text' => 'OK'
			);
		} else {
			$_columns = array(
				'Table'    => '',
				'Op'       => 'analyze',
				'Msg_type' => 'status',
				'Msg_text' => 'Table is already up to date'
			);
		}
		$_results[]    = new ObjectArray($_columns);
		$this->results = $_results;
	}
	/**
	 * Method to check SQLite library version.
	 *
	 * This is used for checking if SQLite can execute multiple rows insert.
	 *
	 * @return version number string or 0
	 * @access private
	 */
	private function get_sqlite_version() {
		try {
			$statement = $this->pdo->prepare('SELECT sqlite_version()');
			$statement->execute();
			$result = $statement->fetch(PDO::FETCH_NUM);
			return $result[0];
		} catch (PDOException $err) {
			return '0';
		}
	}
	/**
	 * Method to call PDO::beginTransaction().
	 *
	 * @see PDO::beginTransaction()
	 * @return boolean
	 */
	public function beginTransaction() {
		if ($this->has_active_transaction) {
			return false;
		} else {
			$this->has_active_transaction = $this->pdo->beginTransaction();
			return $this->has_active_transaction;
		}
	}
	/**
	 * Method to call PDO::commit().
	 *
	 * @see PDO::commit()
	 */
	public function commit() {
		$this->pdo->commit();
		$this->has_active_transaction = false;
	}
	/**
	 * Method to call PDO::rollBack().
	 *
	 * @see PDO::rollBack()
	 */
	public function rollBack() {
		$this->pdo->rollBack();
		$this->has_active_transaction = false;
	}
}

/**
 * Class to change queried data to PHP object.
 *
 * @author kjm
 */
class ObjectArray {
	function __construct($data = null,&$node= null) {
		foreach ($data as $key => $value) {
			if ( is_array($value) ) {
				if (!$node) {
					$node =& $this;
				}
				$node->$key = new stdClass();
				self::__construct($value,$node->$key);
			} else {
				if (!$node) {
					$node =& $this;
				}
				$node->$key = $value;
			}
		}
	}
}
?>