<?php

/**
 * [Discuz!] (C)2001-2099 Discuz! Team
 * This is NOT a freeware, use is subject to license terms
 * https://license.discuz.vip
 */

if(!defined('IN_DISCUZ')) {
	exit('Access Denied');
}

class db_driver_pdo extends db_driver_mysqli {
	var $tablepre;
	var $version = '';
	var $drivertype = 'pdo';
	var $querynum = 0;
	var $slaveid = 0;
	var $curlink;
	var $link = [];
	var $config = [];
	var $sqldebug = [];
	var $map = [];

	function db_mysql($config = []) {
		if(!empty($config)) {
			$this->set_config($config);
		}
	}

	function connect($serverid = 1) {

		if(empty($this->config) || empty($this->config[$serverid])) {
			$this->halt('config_db_not_found');
		}

		if(!empty($this->config[$serverid]['dsn'])) {
			$this->link[$serverid] = $this->_dbconnectWithDSN(
				$this->config[$serverid]['dsn'],
				$this->config[$serverid]['dbuser'],
				$this->config[$serverid]['dbpw'],
				$this->config[$serverid]['pconnect']
			);
		} else {
			$this->link[$serverid] = $this->_dbconnect(
				$this->config[$serverid]['dbhost'],
				$this->config[$serverid]['dbuser'],
				$this->config[$serverid]['dbpw'],
				$this->config[$serverid]['dbcharset'],
				$this->config[$serverid]['dbname'],
				$this->config[$serverid]['pconnect'],
				$this->config[$serverid]['dbport'],
				$this->config[$serverid]['compress']
			);
		}
		$this->curlink = $this->link[$serverid];

	}

	function _dbconnect($dbhost, $dbuser, $dbpw, $dbcharset, $dbname, $pconnect, $dbport = 3306, $dbcompress = 1, $halt = true) {
		$option = [];
		$dsn = 'mysql:host='.$dbhost.';port='.$dbport.';dbname='.$dbname.';charset='.$dbcharset;
		if(intval($pconnect) === 1) {
			$option = [PDO::ATTR_PERSISTENT => true];
		}
		if(intval($dbcompress) === 1) {
			$option[PDO::MYSQL_ATTR_COMPRESS] = true;
		}
		try {
			$link = new PDO($dsn, $dbuser, $dbpw, $option);
		} catch (PDOException $e) {
			$halt && $this->halt($e->getMessage(), $e->getCode());
		}

		$this->curlink = $link;
		$link->query('SET sql_mode=\'\',character_set_client=binary');
		return $link;
	}

	function _dbconnectWithDSN($dsn, $dbuser, $dbpw, $pconnect, $halt = true) {
		$option = [];
		if(intval($pconnect) === 1) {
			$option = [PDO::ATTR_PERSISTENT => true];
		}
		$link = new PDO($dsn, $dbuser, $dbpw, $option);

		if(!$link) {
			$halt && $this->halt('notconnect', $this->errno());
		} else {
			$this->curlink = $link;
			$link->query('SET sql_mode=\'\',character_set_client=binary');
		}
		return $link;
	}

	function select_db($dbname) {
		return false;
	}

	function fetch_array($query, $result_type = MYSQLI_ASSOC) {
		$result_type = match ($result_type) {
			'MYSQL_ASSOC', MYSQLI_ASSOC, 1 => PDO::FETCH_ASSOC,
			'MYSQL_NUM', MYSQLI_NUM, 2 => PDO::FETCH_NUM,
			default => PDO::FETCH_BOTH,
		};
		return $query ? $query->fetch($result_type) : null;
	}

	function fetch_first($sql) {
		return $this->fetch_array($this->query($sql));
	}

	function result_first($sql) {
		return $this->result($this->query($sql), 0);
	}

	public function query($sql, $silent = false, $unbuffered = false) {
		$arg = [];
		if(is_array($sql)) {
			$arg = !empty($sql[1]) ? (array)$sql[1] : [];
			$sql = $sql[0];
		}
		if(defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) {
			$starttime = microtime(true);
		}

		if('UNBUFFERED' === $silent) {
			$silent = false;
			$unbuffered = true;
		} elseif('SILENT' === $silent) {
			$silent = true;
			$unbuffered = false;
		}

		if(!$unbuffered) {
			$this->curlink->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, TRUE);
		}

		$query = $this->curlink->prepare($sql);
		try {
			$query->execute($arg);
		} catch (Exception $e) {
			list($stateCode, $errCode, $errMsg) = $query->errorInfo();
			if(in_array($errCode, [2006, 2013]) && !str_starts_with($silent, 'RETRY')) {
				$this->connect();
				return $this->query([$sql, $arg], 'RETRY'.$silent);
			}

			if(!$silent) {
				$this->halt($errMsg, $errCode, $sql);
			}
		}

		if(defined('DISCUZ_DEBUG') && DISCUZ_DEBUG) {
			$this->sqldebug[] = [$sql, number_format((microtime(true) - $starttime), 6), debug_backtrace(), $this->curlink, $arg];
		}

		$this->querynum++;

		$cmd = trim(strtoupper(substr($sql, 0, strpos($sql, ' '))));
		if($cmd === 'UPDATE' || $cmd === 'DELETE' || $cmd === 'INSERT') {
			$this->rowCount = $query->rowCount();
		}

		return $query;
	}

	function affected_rows() {
		return $this->rowCount;
	}

	function error() {
		return (($this->curlink) ? $this->curlink->errorInfo()[2] : 'pdo_error');
	}

	function errno() {
		return intval(($this->curlink) ? $this->curlink->errorCode() : 99999);
	}

	function result($query, $row = 0) {
		if(!$query || $query->rowCount() == 0) {
			return null;
		}
		return $query->fetchColumn($row);
	}

	function num_rows($query) {
		return $query ? $query->rowCount() : 0;
	}

	function num_fields($query) {
		return $query ? $query->columnCount() : null;
	}

	function free_result($query) {
		return true;
	}

	function insert_id() {
		return ($id = $this->curlink->lastInsertId()) >= 0 ? $id : $this->result($this->query('SELECT last_insert_id()'), 0);
	}

	function fetch_row($query) {
		return $query ? $query->fetch_row() : null;
	}

	function fetch_fields($query) {
		return $query ? $query->fetch_field() : null;
	}

	function version() {
		if(empty($this->version) && $this->curlink) {
			$this->version = $this->curlink->getAttribute(PDO::ATTR_SERVER_VERSION);
		}
		return $this->version;
	}

	function escape_string($str) {
		return substr($this->curlink->quote($str), 1, -1);
	}

	function close() {
		return true;
	}

	function halt($message = '', $code = 0, $sql = '') {
		throw new DbException($message, $code, $sql);
	}

	function begin_transaction() {
		if($this->curlink->beginTransaction()) {
			return true;
		} else {
			return false;
		}
	}

	function commit() {
		if($this->curlink->commit()) {
			return true;
		} else {
			return false;
		}
	}

	function rollback() {
		if($this->curlink->rollBack()) {
			return true;
		} else {
			return false;
		}
	}

}

