fix pgsql

This commit is contained in:
joyqi 2021-08-24 01:30:05 +08:00
parent 75672fe259
commit c422952c89
11 changed files with 267 additions and 245 deletions

View File

@ -20,7 +20,7 @@ if (!file_exists(dirname(__FILE__) . '/config.inc.php')) {
\Typecho\Common::init();
} else {
require_once dirname(__FILE__) . '/config.inc.php';
$installDb = Typecho_Db::get();
$installDb = \Typecho\Db::get();
}
/**
@ -259,9 +259,9 @@ require_once __TYPECHO_ROOT_DIR__ . '/var/Typecho/Common.php';
\Typecho\Common::init();
// config db
\$db = new Typecho_Db('{$adapter}', '{$dbPrefix}');
\$db->addServer(" . (var_export($dbConfig, true)) . ", Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set(\$db);
\$db = new \Typecho\Db('{$adapter}', '{$dbPrefix}');
\$db->addServer(" . (var_export($dbConfig, true)) . ", \Typecho\Db::READ | \Typecho\Db::WRITE);
\Typecho\Db::set(\$db);
";
$configWritten = false;
@ -551,7 +551,7 @@ function install_step_2()
$type = install_get_db_type($adapter);
if (!empty($installDb)) {
$config = $installDb->getConfig(Typecho_Db::WRITE)->toArray();
$config = $installDb->getConfig(\Typecho\Db::WRITE)->toArray();
$config['prefix'] = $installDb->getPrefix();
$config['adapter'] = $adapter;
}
@ -748,7 +748,7 @@ function install_step_2_perform()
$dbConfig = [];
foreach ($configMap[$type] as $key => $value) {
$config[$key] = $config[$key] === null ? (install_is_cli() ? $value : null) : $config[$key];
$config[$key] = !isset($config[$key]) ? (install_is_cli() ? $value : null) : $config[$key];
}
switch ($type) {
@ -801,8 +801,8 @@ function install_step_2_perform()
} elseif (empty($installDb)) {
// detect db config
try {
$installDb = new Typecho_Db($config['dbAdapter'], $config['dbPrefix']);
$installDb->addServer($dbConfig, Typecho_Db::READ | Typecho_Db::WRITE);
$installDb = new \Typecho\Db($config['dbAdapter'], $config['dbPrefix']);
$installDb->addServer($dbConfig, \Typecho\Db::READ | \Typecho\Db::WRITE);
$installDb->query('SELECT 1=1');
} catch (\Typecho\Db\Adapter_Exception $e) {
install_raise_error(_t('对不起, 无法连接数据库, 请先检查数据库配置再继续进行安装'));
@ -867,7 +867,7 @@ function install_step_2_perform()
foreach ($scripts as $script) {
$script = trim($script);
if ($script) {
$installDb->query($script, Typecho_Db::WRITE);
$installDb->query($script, \Typecho\Db::WRITE);
}
}
} catch (\Typecho\Db\Exception $e) {

View File

@ -812,7 +812,7 @@ EOF;
*/
public static function hashValidate(?string $from, ?string $to): bool
{
if ($from === null || $to === null) {
if (!isset($from) || !isset($to)) {
return false;
}
@ -836,7 +836,7 @@ EOF;
*/
public static function hash(?string $string, ?string $salt = null): string
{
if ($string === null) {
if (!isset($string)) {
return '';
}
@ -929,19 +929,19 @@ EOF;
/**
* 获取gravatar头像地址
*
* @param string $mail
* @param string|null $mail
* @param int $size
* @param string $rating
* @param string $default
* @param string|null $rating
* @param string|null $default
* @param bool $isSecure
*
* @return string
*/
public static function gravatarUrl(
string $mail,
?string $mail,
int $size,
string $rating,
string $default,
?string $rating = null,
?string $default = null,
bool $isSecure = true
): string {
if (defined('__TYPECHO_GRAVATAR_PREFIX__')) {
@ -956,8 +956,14 @@ EOF;
}
$url .= '?s=' . $size;
$url .= '&r=' . $rating;
$url .= '&d=' . $default;
if (isset($rating)) {
$url .= '&r=' . $rating;
}
if (isset($default)) {
$url .= '&d=' . $default;
}
return $url;
}

View File

@ -105,7 +105,7 @@ class Db
public function __construct($adapterName, string $prefix = 'typecho_')
{
/** 获取适配器名称 */
$this->adapterName = $adapterName;
$this->adapterName = $adapterName == 'Mysql' ? 'Mysqli' : $adapterName;
/** 数据库适配器 */
$adapterName = '\Typecho\Db\Adapter\\' . str_replace('_', '\\', $adapterName);

View File

@ -71,7 +71,7 @@ interface Adapter
* @param resource $resource 查询的资源数据
* @return array
*/
public function fetchAll($resource): ?array;
public function fetchAll($resource): array;
/**
* 将数据查询的其中一行作为对象取出,其中字段名对应对象属性

View File

@ -4,6 +4,8 @@ namespace Typecho\Db\Adapter;
trait MysqlTrait
{
use QueryTrait;
/**
* 清空数据表
*
@ -25,17 +27,6 @@ trait MysqlTrait
*/
public function parseSelect(array $sql): string
{
if (!empty($sql['join'])) {
foreach ($sql['join'] as $val) {
[$table, $condition, $op] = $val;
$sql['table'] = "{$sql['table']} {$op} JOIN {$table} ON {$condition}";
}
}
$sql['limit'] = (0 == strlen($sql['limit'])) ? null : ' LIMIT ' . $sql['limit'];
$sql['offset'] = (0 == strlen($sql['offset'])) ? null : ' OFFSET ' . $sql['offset'];
return 'SELECT ' . $sql['fields'] . ' FROM ' . $sql['table'] .
$sql['where'] . $sql['group'] . $sql['having'] . $sql['order'] . $sql['limit'] . $sql['offset'];
return $this->buildQuery($sql);
}
}

View File

@ -59,7 +59,11 @@ class Mysql extends Pdo
$config->password
);
$pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
$pdo->exec("SET NAMES '{$config->charset}'");
if ($config->charset) {
$pdo->exec("SET NAMES '{$config->charset}'");
}
return $pdo;
}

View File

@ -1,33 +1,25 @@
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
* Typecho Blog Platform
*
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
* @version $Id$
*/
namespace Typecho\Db\Adapter\Pdo;
use Typecho\Config;
use Typecho\Db;
use Typecho\Db\Adapter\Exception;
use Typecho\Db\Adapter\Pdo;
use Typecho\Db\Adapter\PgsqlTrait;
if (!defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
/**
* 数据库Pdo_Pgsql适配器
*
* @package Db
*/
class Typecho_Db_Adapter_Pdo_Pgsql extends Typecho_Db_Adapter_Pdo
class Pgsql extends Pdo
{
/**
* 主键列表
*
* @var array
*/
private $_pk = [];
/**
* 兼容的插入模式
*
* @var bool
*/
private $_compatibleInsert = false;
use PgsqlTrait;
/**
* 判断适配器是否可用
@ -35,134 +27,51 @@ class Typecho_Db_Adapter_Pdo_Pgsql extends Typecho_Db_Adapter_Pdo
* @access public
* @return boolean
*/
public static function isAvailable()
public static function isAvailable(): bool
{
return parent::isAvailable() && in_array('pgsql', PDO::getAvailableDrivers());
return parent::isAvailable() && in_array('pgsql', \PDO::getAvailableDrivers());
}
/**
* 清空数据表
* 执行数据库查询
*
* @param string $table
* @param mixed $handle 连接对象
* @return mixed|void
* @throws Typecho_Db_Exception
* @param string $query 数据库查询SQL字符串
* @param \PDO $handle 连接对象
* @param integer $op 数据库读写状态
* @param string|null $action 数据库动作
* @param string|null $table 数据表
* @return \PDOStatement
* @throws Exception
*/
public function truncate($table, $handle)
{
$this->query('TRUNCATE TABLE ' . $this->quoteColumn($table) . ' RESTART IDENTITY', $handle);
}
/**
* 覆盖标准动作
* fix #710
*
* @param string $query
* @param mixed $handle
* @param int $op
* @param null $action
* @param null $table
* @return resource
* @throws Typecho_Db_Exception
*/
public function query($query, $handle, $op = Typecho_Db::READ, $action = null, $table = null)
{
if (Typecho_Db::INSERT == $action && !empty($table)) {
if (!isset($this->_pk[$table])) {
$result = $handle->query("SELECT
pg_attribute.attname,
format_type(pg_attribute.atttypid, pg_attribute.atttypmod)
FROM pg_index, pg_class, pg_attribute, pg_namespace
WHERE
pg_class.oid = " . $this->quoteValue($table) . "::regclass AND
indrelid = pg_class.oid AND
nspname = 'public' AND
pg_class.relnamespace = pg_namespace.oid AND
pg_attribute.attrelid = pg_class.oid AND
pg_attribute.attnum = any(pg_index.indkey)
AND indisprimary")->fetch(PDO::FETCH_ASSOC);
if (!empty($result)) {
$this->_pk[$table] = $result['attname'];
}
}
// 使用兼容模式监听插入结果
if (isset($this->_pk[$table])) {
$this->_compatibleInsert = true;
$query .= ' RETURNING ' . $this->quoteColumn($this->_pk[$table]);
}
}
return parent::query($query, $handle, $op, $action, $table); // TODO: Change the autogenerated stub
}
/**
* 对象引号过滤
*
* @access public
* @param string $string
* @return string
*/
public function quoteColumn($string)
{
return '"' . $string . '"';
public function query(
string $query,
$handle,
int $op = Db::READ,
?string $action = null,
?string $table = null
): \PDOStatement {
$this->prepareQuery($query, $handle, $action, $table);
return parent::query($query, $handle, $op, $action, $table);
}
/**
* 初始化数据库
*
* @param Typecho_Config $config 数据库配置
* @access public
* @return PDO
* @param Config $config 数据库配置
* @return \PDO
*/
public function init(Typecho_Config $config)
public function init(Config $config): \PDO
{
$pdo = new PDO("pgsql:dbname={$config->database};host={$config->host};port={$config->port}", $config->user, $config->password);
$pdo->exec("SET NAMES '{$config->charset}'");
$pdo = new \PDO(
"pgsql:dbname={$config->database};host={$config->host};port={$config->port}",
$config->user,
$config->password
);
if ($config->charset) {
$pdo->exec("SET NAMES '{$config->charset}'");
}
return $pdo;
}
/**
* 合成查询语句
*
* @access public
* @param array $sql 查询对象词法数组
* @return string
*/
public function parseSelect(array $sql)
{
if (!empty($sql['join'])) {
foreach ($sql['join'] as $val) {
[$table, $condition, $op] = $val;
$sql['table'] = "{$sql['table']} {$op} JOIN {$table} ON {$condition}";
}
}
$sql['limit'] = (0 == strlen($sql['limit'])) ? null : ' LIMIT ' . $sql['limit'];
$sql['offset'] = (0 == strlen($sql['offset'])) ? null : ' OFFSET ' . $sql['offset'];
return 'SELECT ' . $sql['fields'] . ' FROM ' . $sql['table'] .
$sql['where'] . $sql['group'] . $sql['having'] . $sql['order'] . $sql['limit'] . $sql['offset'];
}
/**
* 取出最后一次插入返回的主键值
*
* @param resource $resource 查询的资源数据
* @param mixed $handle 连接对象
* @return integer
*/
public function lastInsertId($resource, $handle)
{
if ($this->_compatibleInsert) {
$this->_compatibleInsert = false;
return $resource->fetchColumn(0);
} elseif ($handle->query('SELECT oid FROM pg_class WHERE relname = ' . $this->quoteValue($this->_lastTable . '_seq'))->fetchAll()) {
/** 查看是否存在序列,可能需要更严格的检查 */
return $handle->lastInsertId($this->_lastTable . '_seq');
}
return 0;
}
}

View File

@ -17,21 +17,7 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
*/
class Pgsql implements Adapter
{
/**
* 最后一次操作的数据表
*
* @access protected
* @var string
*/
protected $_lastTable;
/**
* 数据库连接字符串标示
*
* @access private
* @var resource
*/
private $_dbLink;
use PgsqlTrait;
/**
* 判断适配器是否可用
@ -54,17 +40,17 @@ class Pgsql implements Adapter
public function connect(Config $config)
{
if (
$this->_dbLink = pg_connect("host={$config->host} port={$config->port}"
$dbLink = pg_connect("host={$config->host} port={$config->port}"
. " dbname={$config->database} user={$config->user} password={$config->password}")
) {
if ($config->charset) {
pg_query($this->_dbLink, "SET NAMES '{$config->charset}'");
pg_query($dbLink, "SET NAMES '{$config->charset}'");
}
return $this->_dbLink;
return $dbLink;
}
/** 数据库异常 */
throw new Exception(pg_last_error($this->_dbLink));
throw new Exception(pg_last_error($dbLink));
}
/**
@ -79,18 +65,6 @@ class Pgsql implements Adapter
return 'pgsql:pgsql ' . $version['server'];
}
/**
* 清空数据表
*
* @param string $table
* @param resource $handle 连接对象
* @throws Exception
*/
public function truncate(string $table, $handle)
{
$this->query('TRUNCATE TABLE ' . $this->quoteColumn($table) . ' RESTART IDENTITY', $handle);
}
/**
* 执行数据库查询
*
@ -104,7 +78,7 @@ class Pgsql implements Adapter
*/
public function query(string $query, $handle, int $op = Db::READ, ?string $action = null, ?string $table = null)
{
$this->_lastTable = $table;
$this->prepareQuery($query, $handle, $action, $table);
if ($resource = pg_query($handle, $query)) {
return $resource;
}
@ -138,6 +112,15 @@ class Pgsql implements Adapter
return pg_fetch_object($resource) ?: null;
}
/**
* @param resource $resource
* @return array|null
*/
public function fetchAll($resource): array
{
return pg_fetch_all($resource, PGSQL_ASSOC);
}
/**
* 取出最后一次查询影响的行数
*
@ -150,23 +133,6 @@ class Pgsql implements Adapter
return pg_affected_rows($resource);
}
/**
* 取出最后一次插入返回的主键值
*
* @param resource $resource 查询的资源数据
* @param resource $handle 连接对象
* @return integer
*/
public function lastInsertId($resource, $handle)
{
/** 查看是否存在序列,可能需要更严格的检查 */
if (pg_fetch_assoc(pg_query($handle, 'SELECT oid FROM pg_class WHERE relname = ' . $this->quoteValue($this->_lastTable . '_seq')))) {
return pg_fetch_result(pg_query($handle, 'SELECT CURRVAL(' . $this->quoteValue($this->_lastTable . '_seq') . ')'), 0, 0);
}
return 0;
}
/**
* 引号转义函数
*

View File

@ -2,8 +2,39 @@
namespace Typecho\Db\Adapter;
use Typecho\Db;
trait PgsqlTrait
{
use QueryTrait;
/**
* @var array
*/
private $pk = [];
/**
* @var bool
*/
private $compatibleInsert = false;
/**
* @var string|null
*/
private $lastTable = null;
/**
* 清空数据表
*
* @param string $table
* @param resource $handle 连接对象
* @throws Exception
*/
public function truncate(string $table, $handle)
{
$this->query('TRUNCATE TABLE ' . $this->quoteColumn($table) . ' RESTART IDENTITY', $handle);
}
/**
* 合成查询语句
*
@ -13,18 +44,7 @@ trait PgsqlTrait
*/
public function parseSelect(array $sql): string
{
if (!empty($sql['join'])) {
foreach ($sql['join'] as $val) {
[$table, $condition, $op] = $val;
$sql['table'] = "{$sql['table']} {$op} JOIN {$table} ON {$condition}";
}
}
$sql['limit'] = (0 == strlen($sql['limit'])) ? null : ' LIMIT ' . $sql['limit'];
$sql['offset'] = (0 == strlen($sql['offset'])) ? null : ' OFFSET ' . $sql['offset'];
return 'SELECT ' . $sql['fields'] . ' FROM ' . $sql['table'] .
$sql['where'] . $sql['group'] . $sql['having'] . $sql['order'] . $sql['limit'] . $sql['offset'];
return $this->buildQuery($sql);
}
/**
@ -38,4 +58,110 @@ trait PgsqlTrait
{
return '"' . $string . '"';
}
/**
* @param string $query
* @param $handle
* @param string|null $action
* @param string|null $table
* @throws Exception
*/
protected function prepareQuery(string &$query, $handle, ?string $action = null, ?string $table = null)
{
if (Db::INSERT == $action && !empty($table)) {
$this->compatibleInsert = false;
$this->lastTable = $table;
if (!isset($this->pk[$table])) {
$resource = $this->query("SELECT
pg_attribute.attname,
format_type(pg_attribute.atttypid, pg_attribute.atttypmod)
FROM pg_index, pg_class, pg_attribute, pg_namespace
WHERE
pg_class.oid = " . $this->quoteValue($table) . "::regclass AND
indrelid = pg_class.oid AND
nspname = 'public' AND
pg_class.relnamespace = pg_namespace.oid AND
pg_attribute.attrelid = pg_class.oid AND
pg_attribute.attnum = any(pg_index.indkey)
AND indisprimary", $handle, Db::READ, Db::SELECT, $table);
$result = $this->fetch($resource);
if (!empty($result)) {
$this->pk[$table] = $result['attname'];
}
}
// 使用兼容模式监听插入结果
if (isset($this->pk[$table])) {
$this->compatibleInsert = true;
$query .= ' RETURNING ' . $this->quoteColumn($this->pk[$table]);
}
} else {
$this->lastTable = null;
}
}
/**
* 取出最后一次插入返回的主键值
*
* @param resource $resource 查询的资源数据
* @param resource $handle 连接对象
* @return integer
* @throws Exception
*/
public function lastInsertId($resource, $handle): int
{
$lastTable = $this->lastTable;
if ($this->compatibleInsert) {
$result = $this->fetch($resource);
$pk = $this->pk[$lastTable];
if (!empty($result) && isset($result[$pk])) {
return (int) $result[$pk];
}
} else {
$resource = $this->query(
'SELECT oid FROM pg_class WHERE relname = '
. $this->quoteValue($lastTable . '_seq'),
$handle,
Db::READ,
Db::SELECT,
$lastTable
);
$result = $this->fetch($resource);
if (!empty($result)) {
$resource = $this->query(
'SELECT CURRVAL(' . $this->quoteValue($lastTable . '_seq') . ') AS last_insert_id',
$handle,
Db::READ,
Db::SELECT,
$lastTable
);
$result = $this->fetch($resource);
if (!empty($result)) {
return (int) $result['last_insert_id'];
}
}
}
return 0;
}
abstract public function query(
string $query,
$handle,
int $op = Db::READ,
?string $action = null,
?string $table = null
);
abstract public function quoteValue(string $string): string;
abstract public function fetch($resource): ?array;
}

View File

@ -0,0 +1,29 @@
<?php
namespace Typecho\Db\Adapter;
/**
* Build Sql
*/
trait QueryTrait
{
/**
* @param array $sql
* @return string
*/
private function buildQuery(array $sql): string
{
if (!empty($sql['join'])) {
foreach ($sql['join'] as $val) {
[$table, $condition, $op] = $val;
$sql['table'] = "{$sql['table']} {$op} JOIN {$table} ON {$condition}";
}
}
$sql['limit'] = (0 == strlen($sql['limit'])) ? null : ' LIMIT ' . $sql['limit'];
$sql['offset'] = (0 == strlen($sql['offset'])) ? null : ' OFFSET ' . $sql['offset'];
return 'SELECT ' . $sql['fields'] . ' FROM ' . $sql['table'] .
$sql['where'] . $sql['group'] . $sql['having'] . $sql['order'] . $sql['limit'] . $sql['offset'];
}
}

View File

@ -7,6 +7,8 @@ namespace Typecho\Db\Adapter;
*/
trait SQLiteTrait
{
use QueryTrait;
private $isSQLite2 = false;
/**
@ -89,21 +91,10 @@ trait SQLiteTrait
*/
public function parseSelect(array $sql): string
{
if (!empty($sql['join'])) {
foreach ($sql['join'] as $val) {
[$table, $condition, $op] = $val;
$sql['table'] = "{$sql['table']} {$op} JOIN {$table} ON {$condition}";
}
}
$sql['limit'] = (0 == strlen($sql['limit'])) ? null : ' LIMIT ' . $sql['limit'];
$sql['offset'] = (0 == strlen($sql['offset'])) ? null : ' OFFSET ' . $sql['offset'];
$query = $this->filterCountQuery('SELECT ' . $sql['fields'] . ' FROM ' . $sql['table'] .
$sql['where'] . $sql['group'] . $sql['having'] . $sql['order'] . $sql['limit'] . $sql['offset']);
$query = $this->filterCountQuery($this->buildQuery($sql));
if ($this->isSQLite2) {
$query = $this->filterSQLite2CountQuery($query);
$query = $this->filterCountQuery($query);
}
return $query;