From b5a887492ac7c866531c7e88306b97710727617b Mon Sep 17 00:00:00 2001 From: Yurun Date: Fri, 7 Jan 2022 15:41:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=88=A4=E6=96=AD=E6=96=AD=E7=BA=BF=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=A0=81=EF=BC=8C=E8=87=AA=E5=8A=A8=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=20(#244)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Db/Contract/IPgsqlDb.php | 4 + src/Db/Drivers/PdoPgsql/Driver.php | 174 ++++++++++++++++++++++---- src/Db/Drivers/PdoPgsql/Statement.php | 41 ++++-- src/Db/Drivers/Swoole/Driver.php | 71 +++++++++-- src/Db/Drivers/Swoole/Statement.php | 10 +- src/Db/PgsqlBase.php | 15 ++- tests/TSwoolePgTest.php | 2 +- 7 files changed, 261 insertions(+), 56 deletions(-) diff --git a/src/Db/Contract/IPgsqlDb.php b/src/Db/Contract/IPgsqlDb.php index 9b95478..76738b3 100644 --- a/src/Db/Contract/IPgsqlDb.php +++ b/src/Db/Contract/IPgsqlDb.php @@ -8,4 +8,8 @@ interface IPgsqlDb extends IDb { + /** + * 检查错误码是否为掉线 + */ + public function checkCodeIsOffline(string $code): bool; } diff --git a/src/Db/Drivers/PdoPgsql/Driver.php b/src/Db/Drivers/PdoPgsql/Driver.php index f4e0aac..c060b37 100644 --- a/src/Db/Drivers/PdoPgsql/Driver.php +++ b/src/Db/Drivers/PdoPgsql/Driver.php @@ -10,7 +10,6 @@ use Imi\Db\Exception\DbException; use Imi\Db\Statement\StatementManager; use Imi\Db\Transaction\Transaction; -use Imi\Pgsql\Db\Contract\IPgsqlDb; use Imi\Pgsql\Db\Contract\IPgsqlStatement; use Imi\Pgsql\Db\PgsqlBase; use Imi\Pgsql\Db\Util\SqlUtil; @@ -21,7 +20,7 @@ * * @Bean("PdoPgsqlDriver") */ -class Driver extends PgsqlBase implements IPgsqlDb +class Driver extends PgsqlBase { /** * 连接对象 @@ -104,14 +103,30 @@ public function isConnected(): bool */ public function ping(): bool { + $instance = $this->instance; + if (!$instance) + { + return false; + } try { - $instance = $this->instance; + if ($instance->query('select 1')) + { + return true; + } + if ($this->checkCodeIsOffline($instance->errorInfo()[0] ?? '')) + { + $this->close(); + } - return $instance && false !== $instance->query('select 1'); + return false; } - catch (\Throwable $e) + catch (\PDOException $e) { + if ($this->checkCodeIsOffline($e->errorInfo[0])) + { + $this->close(); + } } return false; @@ -140,12 +155,13 @@ public function close(): void $this->lastStmt = null; } $this->instance = null; + $this->transaction->init(); } /** * {@inheritDoc} */ - public function getInstance(): PDO + public function getInstance(): ?PDO { return $this->instance; } @@ -155,12 +171,28 @@ public function getInstance(): PDO */ public function beginTransaction(): bool { - if (!$this->inTransaction() && !$this->instance->beginTransaction()) + try { - return false; + if (!$this->inTransaction() && !$this->instance->beginTransaction()) + { + if ($this->checkCodeIsOffline($this->instance->errorInfo()[0] ?? '')) + { + $this->close(); + } + + return false; + } + $this->exec('SAVEPOINT P' . $this->getTransactionLevels()); + $this->transaction->beginTransaction(); + } + catch (\PDOException $e) + { + if ($this->checkCodeIsOffline($e->errorInfo[0])) + { + $this->close(); + } + throw $e; } - $this->exec('SAVEPOINT P' . $this->getTransactionLevels()); - $this->transaction->beginTransaction(); return true; } @@ -170,7 +202,28 @@ public function beginTransaction(): bool */ public function commit(): bool { - return $this->instance->commit() && $this->transaction->commit(); + try + { + if (!$this->instance->commit()) + { + if ($this->checkCodeIsOffline($this->instance->errorInfo()[0] ?? '')) + { + $this->close(); + } + + return false; + } + } + catch (\PDOException $e) + { + if ($this->checkCodeIsOffline($e->errorInfo[0])) + { + $this->close(); + } + throw $e; + } + + return $this->transaction->commit(); } /** @@ -180,7 +233,18 @@ public function rollBack(?int $levels = null): bool { if (null === $levels) { - $result = $this->instance->rollback(); + try + { + $result = $this->instance->rollback(); + } + catch (\PDOException $e) + { + if ($this->checkCodeIsOffline($e->errorInfo[0])) + { + $this->close(); + } + throw $e; + } } else { @@ -191,6 +255,10 @@ public function rollBack(?int $levels = null): bool { $this->transaction->rollBack($levels); } + elseif ($this->checkCodeIsOffline($this->instance->errorInfo()[0] ?? '')) + { + $this->close(); + } return $result; } @@ -261,11 +329,29 @@ public function lastSql(): string public function exec(string $sql): int { $this->lastSql = $sql; + $this->lastStmt = null; - $result = $this->instance->exec($sql); - if (false === $result) + try { - throw new DbException('SQL prepare error [' . $this->errorCode() . '] ' . $this->errorInfo() . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); + $result = $this->instance->exec($sql); + if (false === $result) + { + $errorCode = $this->errorCode(); + $errorInfo = $this->errorInfo(); + if ($this->checkCodeIsOffline($this->instance->errorInfo()[0] ?? '')) + { + $this->close(); + } + throw new DbException('SQL exec error [' . $errorCode . '] ' . $errorInfo . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); + } + } + catch (\PDOException $e) + { + if ($this->checkCodeIsOffline($e->errorInfo[0])) + { + $this->close(); + } + throw $e; } return $result; @@ -330,17 +416,34 @@ public function prepare(string $sql, array $driverOptions = []): IPgsqlStatement } else { - $this->lastSql = $sql; - $lastStmt = $this->lastStmt = $this->instance->prepare($sql, $driverOptions); - // @phpstan-ignore-next-line - if (false === $lastStmt) + try { - throw new DbException('SQL prepare error [' . $this->errorCode() . '] ' . $this->errorInfo() . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); + $this->lastSql = $sql; + $lastStmt = $this->lastStmt = $this->instance->prepare($sql, $driverOptions); + // @phpstan-ignore-next-line + if (false === $lastStmt) + { + $errorCode = $this->errorCode(); + $errorInfo = $this->errorInfo(); + if ($this->checkCodeIsOffline($this->instance->errorInfo()[0] ?? '')) + { + $this->close(); + } + throw new DbException('SQL prepare error [' . $errorCode . '] ' . $errorInfo . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); + } + $stmt = App::getBean(Statement::class, $this, $lastStmt); + if ($this->isCacheStatement && !isset($stmtCache)) + { + StatementManager::setNX($stmt, true); + } } - $stmt = App::getBean(Statement::class, $this, $lastStmt); - if ($this->isCacheStatement && !isset($stmtCache)) + catch (\PDOException $e) { - StatementManager::setNX($stmt, true); + if ($this->checkCodeIsOffline($e->errorInfo[0])) + { + $this->close(); + } + throw $e; } } @@ -352,11 +455,28 @@ public function prepare(string $sql, array $driverOptions = []): IPgsqlStatement */ public function query(string $sql): IPgsqlStatement { - $this->lastSql = $sql; - $this->lastStmt = $lastStmt = $this->instance->query($sql); - if (false === $lastStmt) + try { - throw new DbException('SQL query error: [' . $this->errorCode() . '] ' . $this->errorInfo() . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); + $this->lastSql = $sql; + $this->lastStmt = $lastStmt = $this->instance->query($sql); + if (false === $lastStmt) + { + $errorCode = $this->errorCode(); + $errorInfo = $this->errorInfo(); + if ($this->checkCodeIsOffline($this->instance->errorInfo()[0] ?? '')) + { + $this->close(); + } + throw new DbException('SQL query error [' . $errorCode . '] ' . $errorInfo . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); + } + } + catch (\PDOException $e) + { + if ($this->checkCodeIsOffline($e->errorInfo[0])) + { + $this->close(); + } + throw $e; } return App::getBean(Statement::class, $this, $lastStmt); diff --git a/src/Db/Drivers/PdoPgsql/Statement.php b/src/Db/Drivers/PdoPgsql/Statement.php index ec1cd18..96b2707 100644 --- a/src/Db/Drivers/PdoPgsql/Statement.php +++ b/src/Db/Drivers/PdoPgsql/Statement.php @@ -128,28 +128,45 @@ public function getSql(): string */ public function execute(array $inputParameters = null): bool { - $statement = $this->statement; - $statement->closeCursor(); - if ($inputParameters) + try { - foreach ($inputParameters as $k => $v) + $statement = $this->statement; + $statement->closeCursor(); + if ($inputParameters) { - if (is_numeric($k)) + foreach ($inputParameters as $k => $v) { - $statement->bindValue($k + 1, $v, $this->getDataTypeByValue($v)); + if (is_numeric($k)) + { + $statement->bindValue($k + 1, $v, $this->getDataTypeByValue($v)); + } + else + { + $statement->bindValue($k, $v, $this->getDataTypeByValue($v)); + } } - else + } + $result = $statement->execute(); + if (!$result) + { + $errorCode = $this->errorCode(); + $errorInfo = $this->errorInfo(); + if ($this->db->checkCodeIsOffline($this->db->errorInfo()[0] ?? '')) { - $statement->bindValue($k, $v, $this->getDataTypeByValue($v)); + $this->db->close(); } + throw new DbException('SQL query error [' . $errorCode . '] ' . $errorInfo . \PHP_EOL . 'sql: ' . $this->getSql() . \PHP_EOL); } + $this->updateLastInsertId(); } - $result = $statement->execute(); - if (!$result) + catch (\PDOException $e) { - throw new DbException('SQL query error: [' . $this->errorCode() . '] ' . $this->errorInfo() . ' sql: ' . $this->getSql()); + if ($this->db->checkCodeIsOffline($e->errorInfo[0])) + { + $this->db->close(); + } + throw $e; } - $this->updateLastInsertId(); return $result; } diff --git a/src/Db/Drivers/Swoole/Driver.php b/src/Db/Drivers/Swoole/Driver.php index 8c7b2e7..d534e98 100644 --- a/src/Db/Drivers/Swoole/Driver.php +++ b/src/Db/Drivers/Swoole/Driver.php @@ -10,7 +10,6 @@ use Imi\Db\Exception\DbException; use Imi\Db\Statement\StatementManager; use Imi\Db\Transaction\Transaction; -use Imi\Pgsql\Db\Contract\IPgsqlDb; use Imi\Pgsql\Db\Contract\IPgsqlStatement; use Imi\Pgsql\Db\PgsqlBase; use Imi\Pgsql\Db\Util\SqlUtil; @@ -23,7 +22,7 @@ * * @Bean("SwoolePgsqlDriver") */ - class Driver extends PgsqlBase implements IPgsqlDb + class Driver extends PgsqlBase { /** * 连接对象 @@ -89,16 +88,26 @@ public function isConnected(): bool public function ping(): bool { $instance = $this->instance; + if (!$instance) + { + return false; + } + if ($instance->query('select 1')) + { + return true; + } + if ($this->checkCodeIsOffline($instance->errCode)) + { + $this->close(); + } - return $instance && $instance->query('select 1'); + return false; } /** * 构建DNS字符串. - * - * @return string */ - protected function buildDSN() + protected function buildDSN(): string { $option = $this->option; if (isset($option['dsn'])) @@ -151,12 +160,13 @@ public function close(): void { $this->instance = null; } + $this->transaction->init(); } /** * {@inheritDoc} */ - public function getInstance(): PostgreSQL + public function getInstance(): ?PostgreSQL { return $this->instance; } @@ -168,6 +178,11 @@ public function beginTransaction(): bool { if (!$this->inTransaction() && !$this->instance->query('begin')) { + if ($this->checkCodeIsOffline($this->instance->errCode)) + { + $this->close(); + } + return false; } $this->exec('SAVEPOINT P' . $this->getTransactionLevels()); @@ -181,7 +196,17 @@ public function beginTransaction(): bool */ public function commit(): bool { - return $this->instance->query('commit') && $this->transaction->commit(); + if (!$this->instance->query('commit')) + { + if ($this->checkCodeIsOffline($this->instance->errCode)) + { + $this->close(); + } + + return false; + } + + return $this->transaction->commit(); } /** @@ -202,6 +227,10 @@ public function rollBack(?int $levels = null): bool { $this->transaction->rollBack($levels); } + elseif ($this->checkCodeIsOffline($this->instance->errCode)) + { + $this->close(); + } return (bool) $result; } @@ -253,9 +282,15 @@ public function exec(string $sql): int { $this->lastSql = $sql; $instance = $this->instance; - $this->lastQueryResult = $instance->query($sql); + $this->lastQueryResult = $lastQueryResult = $instance->query($sql); + if (false === $lastQueryResult && $this->checkCodeIsOffline($this->instance->errCode)) + { + $this->close(); + + return 0; + } - return $instance->affectedRows($this->lastQueryResult); + return $instance->affectedRows($lastQueryResult); } /** @@ -332,7 +367,13 @@ public function prepare(string $sql, array $driverOptions = []): IPgsqlStatement $this->lastQueryResult = $queryResult = $this->instance->prepare($statementName, $parsedSql); if (false === $queryResult) { - throw new DbException('SQL prepare error: ' . $this->errorInfo() . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); + $errorCode = $this->errorCode(); + $errorInfo = $this->errorInfo(); + if ($this->checkCodeIsOffline($errorCode)) + { + $this->close(); + } + throw new DbException('SQL prepare error [' . $errorCode . '] ' . $errorInfo . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); } $stmt = App::getBean(Statement::class, $this, null, $sql, $statementName, $sqlParamsMap); if ($this->isCacheStatement && !isset($stmtCache)) @@ -353,7 +394,13 @@ public function query(string $sql): IPgsqlStatement $this->lastQueryResult = $queryResult = $this->instance->query($sql); if (false === $queryResult) { - throw new DbException('SQL query error: [' . $this->errorCode() . '] ' . $this->errorInfo() . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); + $errorCode = $this->errorCode(); + $errorInfo = $this->errorInfo(); + if ($this->checkCodeIsOffline($errorCode)) + { + $this->close(); + } + throw new DbException('SQL query error: [' . $errorCode . '] ' . $errorInfo . \PHP_EOL . 'sql: ' . $sql . \PHP_EOL); } return App::getBean(Statement::class, $this, $queryResult, $sql); diff --git a/src/Db/Drivers/Swoole/Statement.php b/src/Db/Drivers/Swoole/Statement.php index bb54889..1629565 100644 --- a/src/Db/Drivers/Swoole/Statement.php +++ b/src/Db/Drivers/Swoole/Statement.php @@ -169,7 +169,7 @@ public function execute(array $inputParameters = null): bool $this->queryResult = $queryResult = $pgDb->query($this->lastSql); if (false === $queryResult) { - throw new DbException('SQL query error: [' . $this->errorCode() . '] ' . $this->errorInfo() . ' sql: ' . $this->getSql()); + throw new DbException('SQL query error: [' . $this->errorCode() . '] ' . $this->errorInfo() . ' sql: ' . $this->getSql() . \PHP_EOL); } } else @@ -208,7 +208,13 @@ public function execute(array $inputParameters = null): bool $this->queryResult = $queryResult = $pgDb->execute($this->statementName, $bindValues); if (false === $queryResult) { - throw new DbException('SQL query error: [' . $this->errorCode() . '] ' . $this->errorInfo() . ' sql: ' . $this->getSql()); + $errorCode = $this->errorCode(); + $errorInfo = $this->errorInfo(); + if ($this->db->checkCodeIsOffline($errorCode)) + { + $this->close(); + } + throw new DbException('SQL query error: [' . $errorCode . '] ' . $errorInfo . \PHP_EOL . 'sql: ' . $this->getSql() . \PHP_EOL); } } $this->result = $pgDb->fetchAll($queryResult, \SW_PGSQL_ASSOC) ?: []; diff --git a/src/Db/PgsqlBase.php b/src/Db/PgsqlBase.php index 8a523b1..f1e5ef3 100644 --- a/src/Db/PgsqlBase.php +++ b/src/Db/PgsqlBase.php @@ -6,9 +6,10 @@ use Imi\Db\Drivers\Base; use Imi\Db\Query\Interfaces\IQuery; +use Imi\Pgsql\Db\Contract\IPgsqlDb; use Imi\Pgsql\Db\Query\PgsqlQuery; -abstract class PgsqlBase extends Base +abstract class PgsqlBase extends Base implements IPgsqlDb { /** * {@inheritDoc} @@ -19,10 +20,20 @@ public function createQuery(?string $modelClass = null): IQuery } /** - * 获取数据库种类. + * {@inheritDoc} */ public function getDbType(): string { return 'PostgreSQL'; } + + /** + * {@inheritDoc} + * + * @see http://www.postgres.cn/docs/13/errcodes-appendix.html + */ + public function checkCodeIsOffline(string $code): bool + { + return '57P01' === $code; + } } diff --git a/tests/TSwoolePgTest.php b/tests/TSwoolePgTest.php index 1b47de5..a55145e 100644 --- a/tests/TSwoolePgTest.php +++ b/tests/TSwoolePgTest.php @@ -8,7 +8,7 @@ trait TSwoolePgTest { protected function setUp(): void { - if (class_exists(\Swoole\Coroutine\PostgreSQL::class, false)) + if (!class_exists(\Swoole\Coroutine\PostgreSQL::class, false)) { $this->markTestSkipped(); }