From ee1a5a993df4ad88d88b8c637786437a2c538364 Mon Sep 17 00:00:00 2001 From: whwyy Date: Tue, 16 Dec 2025 20:20:08 +0800 Subject: [PATCH] eee --- Base/Model.php | 31 ++++++++++++++----- Base/PDO.php | 17 ++++++++++- Command.php | 24 +++++++++++++-- Condition/Condition.php | 7 ++--- Condition/NotInCondition.php | 2 +- Connection.php | 29 +++++++++++------- DatabasesProviders.php | 1 + SqlBuilder.php | 58 ++++++++++++++++++++++++++++++++++++ Traits/HasBase.php | 2 -- 9 files changed, 142 insertions(+), 29 deletions(-) diff --git a/Base/Model.php b/Base/Model.php index d4a7096..e0c7630 100644 --- a/Base/Model.php +++ b/Base/Model.php @@ -30,6 +30,14 @@ use ReturnTypeWillChange; use ReflectionException; use validator\Validator; + +enum Driver +{ + case Mysql; + case Pgsql; +} + + /** * Class BOrm * @@ -139,7 +147,7 @@ abstract class Model extends Component implements ModelInterface, ArrayAccess, \ */ public function clean(): void { - $this->_attributes = []; + $this->_attributes = []; $this->_oldAttributes = []; } @@ -449,7 +457,7 @@ abstract class Model extends Component implements ModelInterface, ArrayAccess, \ */ private function insert(): bool|static { - $sql = SqlBuilder::builder($query = static::query())->insert($this->_attributes); + $sql = SqlBuilder::builder($query = static::query())->insert($this->_attributes); $lastId = $this->getConnection()->createCommand($sql, $query->params)->exec(); if ($lastId === FALSE) { return FALSE; @@ -512,7 +520,7 @@ abstract class Model extends Component implements ModelInterface, ArrayAccess, \ protected function arrayIntersect(array $params): array { $condition = []; - $oldPrams = []; + $oldPrams = []; foreach ($this->_oldAttributes as $key => $attribute) { if (!array_key_exists($key, $params) || $params[$key] == $attribute) { $condition[$key] = $attribute; @@ -547,7 +555,7 @@ abstract class Model extends Component implements ModelInterface, ArrayAccess, \ */ public function populates(array $value): static { - $this->_attributes = $value; + $this->_attributes = $value; $this->_oldAttributes = $value; $this->setIsNowExample(); return $this; @@ -635,7 +643,7 @@ abstract class Model extends Component implements ModelInterface, ArrayAccess, \ */ public function getTable(): string { - $connection = $this->getConnection(); + $connection = $this->getConnection(); $tablePrefix = $connection->tablePrefix; if (empty($this->table)) { throw new Exception('You need add static method `tableName` and return table name.'); @@ -644,6 +652,13 @@ abstract class Model extends Component implements ModelInterface, ArrayAccess, \ if (!empty($tablePrefix) && !str_starts_with($table, $tablePrefix)) { $table = $tablePrefix . $table; } + + $driver = strtolower($connection->driver ?? 'mysql'); + if (in_array($driver, ['pgsql', 'postgresql'])) { + // PostgreSQL 使用双引号,并且 schema.table 格式 + return '"' . $connection->database . '"."' . $table . '"'; + } + // MySQL 使用反引号 return '`' . $connection->database . '`.' . $table; } @@ -678,7 +693,7 @@ abstract class Model extends Component implements ModelInterface, ArrayAccess, \ public function refresh(): static { $this->_oldAttributes = $this->_attributes; - $this->isNewExample = FALSE; + $this->isNewExample = FALSE; return $this; } @@ -853,8 +868,8 @@ abstract class Model extends Component implements ModelInterface, ArrayAccess, \ */ public static function populate(array $data): static { - $model = new static(); - $model->_attributes = $data; + $model = new static(); + $model->_attributes = $data; $model->_oldAttributes = $data; $model->setIsNowExample(); return $model; diff --git a/Base/PDO.php b/Base/PDO.php index c722aac..6385a59 100644 --- a/Base/PDO.php +++ b/Base/PDO.php @@ -5,6 +5,7 @@ namespace Database\Base; class PDO extends \PDO { + readonly public string $driver; /** * @param string $database @@ -12,6 +13,7 @@ class PDO extends \PDO * @param string $username * @param string $password * @param array $options + * @param string $driver */ public function __construct( readonly public string $database, @@ -19,10 +21,23 @@ class PDO extends \PDO readonly public string $username, readonly public string $password, readonly public array $options, + string $driver = 'mysql', ) { - parent::__construct('mysql:dbname=' . $this->database . ';host=' . $this->host, $this->username, $this->password, $this->options); + $this->driver = strtolower($driver); + $dsn = $this->buildDsn(); + parent::__construct($dsn, $this->username, $this->password, $this->options); } + /** + * @return string + */ + private function buildDsn(): string + { + return match ($this->driver) { + 'pgsql', 'postgresql' => 'pgsql:host=' . $this->host . ';dbname=' . $this->database, + default => 'mysql:dbname=' . $this->database . ';host=' . $this->host, + }; + } } diff --git a/Command.php b/Command.php index 41234fe..b7101fd 100644 --- a/Command.php +++ b/Command.php @@ -188,15 +188,33 @@ class Command extends Component */ protected function isRefresh(Throwable $throwable): bool { - if (str_contains($throwable->getMessage(), 'MySQL server has gone away')) { + $message = $throwable->getMessage(); + + // MySQL 错误处理 + if (str_contains($message, 'MySQL server has gone away')) { return true; } - if (str_contains($throwable->getMessage(), 'Send of 14 bytes failed with errno=32 Broken pipe')) { + if (str_contains($message, 'Send of 14 bytes failed with errno=32 Broken pipe')) { return true; } - if (str_contains($throwable->getMessage(), 'Lost connection to MySQL server during query')) { + if (str_contains($message, 'Lost connection to MySQL server during query')) { return true; } + + // PostgreSQL 错误处理 + if (str_contains($message, 'server closed the connection unexpectedly')) { + return true; + } + if (str_contains($message, 'Connection refused')) { + return true; + } + if (str_contains($message, 'Broken pipe')) { + return true; + } + if (str_contains($message, 'connection to server was lost')) { + return true; + } + return false; } diff --git a/Condition/Condition.php b/Condition/Condition.php index 4e402b7..abfbdc8 100644 --- a/Condition/Condition.php +++ b/Condition/Condition.php @@ -43,10 +43,9 @@ abstract class Condition extends Component public function setValue($params): void { if (is_array($params)) { - $values = []; - foreach ($params as $item => $value) { - $values[$item] = is_numeric($value) ? $value : '\'' . $value . '\''; - } + $values = array_map(function ($value) { + return is_numeric($value) ? $value : '\'' . $value . '\''; + }, $params); $this->value = $values; } else { $this->value = $this->checkIsSqlString($params); diff --git a/Condition/NotInCondition.php b/Condition/NotInCondition.php index 990cb2d..859684c 100644 --- a/Condition/NotInCondition.php +++ b/Condition/NotInCondition.php @@ -17,7 +17,7 @@ class NotInCondition extends Condition * @return string|null * @throws */ - #[Pure] public function builder(): ?string + public function builder(): ?string { if (!is_array($this->value)) { throw new \Exception('Builder data by a empty string. need array'); diff --git a/Connection.php b/Connection.php index d354c0a..b37c3b4 100644 --- a/Connection.php +++ b/Connection.php @@ -14,6 +14,7 @@ namespace Database; use Database\Affair\BeginTransaction; use Database\Affair\Commit; use Database\Affair\Rollback; +use Database\Base\Driver; use Database\Base\PDO; use Exception; use Kiri\Abstracts\Component; @@ -45,6 +46,7 @@ class Connection extends Component public string $charset = 'utf-8'; public string $tablePrefix = ''; public string $database = ''; + public string $driver = 'mysql'; public int $timeout = 30; public int $waite_time = 3; public int $tick_time = 60000; @@ -154,7 +156,7 @@ class Connection extends Component */ private function getName(): string { - return 'mysql.' . $this->cds; + return strtolower($this->driver) . '.' . $this->cds; } @@ -355,15 +357,22 @@ class Connection extends Component */ public function newConnect(): \PDO { - $pdo = new PDO($this->database, $this->cds, $this->username, $this->password, [ - \PDO::ATTR_CASE => \PDO::CASE_NATURAL, - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, - \PDO::ATTR_ORACLE_NULLS => \PDO::NULL_NATURAL, - \PDO::ATTR_STRINGIFY_FETCHES => false, - \PDO::ATTR_EMULATE_PREPARES => true, - \PDO::ATTR_TIMEOUT => $this->timeout, - \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . $this->charset - ]); + $driver = strtolower($this->driver); + $options = [ + \PDO::ATTR_CASE => \PDO::CASE_NATURAL, + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_ORACLE_NULLS => \PDO::NULL_NATURAL, + \PDO::ATTR_STRINGIFY_FETCHES => false, + \PDO::ATTR_EMULATE_PREPARES => true, + \PDO::ATTR_TIMEOUT => $this->timeout, + ]; + + // MySQL 特定的选项 + if ($driver === 'mysql') { + $options[\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $this->charset; + } + + $pdo = new PDO($this->database, $this->cds, $this->username, $this->password, $options, $driver); foreach ($this->attributes as $key => $attribute) { $pdo->setAttribute($key, $attribute); } diff --git a/DatabasesProviders.php b/DatabasesProviders.php index 7604b4d..04b9b35 100644 --- a/DatabasesProviders.php +++ b/DatabasesProviders.php @@ -78,6 +78,7 @@ class DatabasesProviders extends Providers 'password' => $database['password'], 'tablePrefix' => $database['tablePrefix'], 'database' => $database['database'], + 'driver' => $database['driver'] ?? 'mysql', 'timeout' => $database['timeout'] ?? 10, 'tick_time' => $database['tick_time'] ?? 60000, 'waite_time' => $database['waite_time'] ?? 3, diff --git a/SqlBuilder.php b/SqlBuilder.php index 3a9ba21..a3067de 100644 --- a/SqlBuilder.php +++ b/SqlBuilder.php @@ -244,9 +244,26 @@ class SqlBuilder extends Component /** * @param string $table * @return string + * @throws */ public function columns(string $table): string { + $driver = $this->getDriver(); + if (in_array($driver, ['pgsql', 'postgresql'])) { + // PostgreSQL 使用 information_schema + $tableName = trim($table, '"`'); + return "SELECT + column_name as Field, + data_type as Type, + is_nullable as Null, + column_default as Default, + '' as Extra, + '' as Key + FROM information_schema.columns + WHERE table_name = '$tableName' + ORDER BY ordinal_position"; + } + // MySQL 使用 SHOW FULL FIELDS return 'SHOW FULL FIELDS FROM ' . $table; } @@ -317,9 +334,19 @@ class SqlBuilder extends Component } + /** + * @return string + * @throws + */ private function makeLimit(): string { if ($this->query->getOffset() >= 0 && $this->query->getLimit() >= 1) { + $driver = $this->getDriver(); + if (in_array($driver, ['pgsql', 'postgresql'])) { + // PostgreSQL 使用 LIMIT ... OFFSET ... 语法 + return ' LIMIT ' . $this->query->getLimit() . ' OFFSET ' . $this->query->getOffset(); + } + // MySQL 使用 LIMIT offset,limit 语法 return ' LIMIT ' . $this->query->getOffset() . ',' . $this->query->getLimit(); } return ''; @@ -399,5 +426,36 @@ class SqlBuilder extends Component return $_array; } + /** + * 获取数据库驱动类型 + * @return string + * @throws + */ + private function getDriver(): string + { + try { + // 尝试从 query 对象获取 connection + if ($this->query instanceof ActiveQuery && $this->query->modelClass !== null) { + if ($this->query->modelClass instanceof Model) { + $connection = $this->query->modelClass->getConnection(); + if ($connection instanceof Connection) { + return strtolower($connection->driver ?? 'mysql'); + } + } + } + // 如果是 Db 查询,尝试获取默认 connection + if (method_exists($this->query, 'getConnection')) { + $connection = $this->query->getConnection(); + if ($connection instanceof Connection) { + return strtolower($connection->driver ?? 'mysql'); + } + } + } catch (\Throwable $e) { + // 忽略错误,返回默认值 + } + // 默认返回 mysql + return 'mysql'; + } + } diff --git a/Traits/HasBase.php b/Traits/HasBase.php index 5b9c79e..b296e0c 100644 --- a/Traits/HasBase.php +++ b/Traits/HasBase.php @@ -12,8 +12,6 @@ namespace Database\Traits; use Database\ModelInterface; use Database\Collection; use Database\Relation; -use Kiri; -use Kiri\Di\Context; /** * Class HasBase