-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement PostgreSQL support with PDO
- Loading branch information
1 parent
14a161f
commit 2ac71e1
Showing
11 changed files
with
386 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace PeachySQL; | ||
|
||
use PDO; | ||
use PeachySQL\Pgsql\Options; | ||
use PeachySQL\Pgsql\Statement; | ||
use PeachySQL\QueryBuilder\Insert; | ||
|
||
/** | ||
* Implements the standard PeachySQL features for PostgreSQL (using PDO) | ||
*/ | ||
class Pgsql extends PeachySql | ||
{ | ||
private PDO $conn; | ||
private bool $usedPrepare; | ||
|
||
public function __construct(PDO $connection, ?Options $options = null) | ||
{ | ||
$this->conn = $connection; | ||
$this->usedPrepare = true; | ||
|
||
if ($options === null) { | ||
$options = new Options(); | ||
} | ||
|
||
$this->options = $options; | ||
} | ||
|
||
/** | ||
* Begins a transaction | ||
* @throws SqlException if an error occurs | ||
*/ | ||
public function begin(): void | ||
{ | ||
if (!$this->conn->beginTransaction()) { | ||
throw $this->getError('Failed to begin transaction', $this->conn->errorInfo()); | ||
} | ||
} | ||
|
||
/** | ||
* Commits a transaction begun with begin() | ||
* @throws SqlException if an error occurs | ||
*/ | ||
public function commit(): void | ||
{ | ||
if (!$this->conn->commit()) { | ||
throw $this->getError('Failed to commit transaction', $this->conn->errorInfo()); | ||
} | ||
} | ||
|
||
/** | ||
* Rolls back a transaction begun with begin() | ||
* @throws SqlException if an error occurs | ||
*/ | ||
public function rollback(): void | ||
{ | ||
if (!$this->conn->rollback()) { | ||
throw $this->getError('Failed to roll back transaction', $this->conn->errorInfo()); | ||
} | ||
} | ||
|
||
final public function makeBinaryParam(?string $binaryStr, ?int $length = null): array | ||
{ | ||
return [$binaryStr, $length]; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public static function getError(string $message, array $error): SqlException | ||
{ | ||
/** @var array{0: string, 1: int|null, 2: string|null} $error */ | ||
$code = $error[1] ?? 0; | ||
$details = $error[2] ?? ''; | ||
$sqlState = $error[0]; | ||
|
||
return new SqlException($message, $code, $details, $sqlState); | ||
} | ||
|
||
/** | ||
* Returns a prepared statement which can be executed multiple times | ||
* @throws SqlException if an error occurs | ||
*/ | ||
public function prepare(string $sql, array $params = []): Statement | ||
{ | ||
try { | ||
if (!$stmt = $this->conn->prepare($sql)) { | ||
throw $this->getError('Failed to prepare statement', $this->conn->errorInfo()); | ||
} | ||
|
||
$i = 0; | ||
/** @psalm-suppress MixedAssignment */ | ||
foreach ($params as &$param) { | ||
$i++; | ||
|
||
if (is_bool($param)) { | ||
$stmt->bindParam($i, $param, PDO::PARAM_BOOL); | ||
} elseif (is_int($param)) { | ||
$stmt->bindParam($i, $param, PDO::PARAM_INT); | ||
} elseif (is_array($param)) { | ||
$stmt->bindParam($i, $param[0], PDO::PARAM_LOB); | ||
} else { | ||
$stmt->bindParam($i, $param, PDO::PARAM_STR); | ||
} | ||
} | ||
} catch (\PDOException $e) { | ||
throw $this->getError('Failed to prepare statement', $this->conn->errorInfo()); | ||
} | ||
|
||
return new Statement($stmt, $this->usedPrepare); | ||
} | ||
|
||
/** | ||
* Prepares and executes a single query with bound parameters | ||
*/ | ||
public function query(string $sql, array $params = []): Statement | ||
{ | ||
$this->usedPrepare = false; | ||
$stmt = $this->prepare($sql, $params); | ||
$this->usedPrepare = true; | ||
$stmt->execute(); | ||
return $stmt; | ||
} | ||
|
||
/** | ||
* Performs a single bulk insert query | ||
*/ | ||
protected function insertBatch(string $table, array $colVals, int $identityIncrement = 1): BulkInsertResult | ||
{ | ||
$sqlParams = (new Insert($this->options))->buildQuery($table, $colVals); | ||
$result = $this->query($sqlParams->sql, $sqlParams->params); | ||
|
||
try { | ||
$lastId = (int) $this->conn->lastInsertId(); | ||
} catch (\PDOException $e) { | ||
$lastId = 0; | ||
} | ||
|
||
if ($lastId) { | ||
$firstId = $lastId - $identityIncrement * (count($colVals) -1); | ||
$ids = range($firstId, $lastId, $identityIncrement); | ||
} else { | ||
$ids = []; | ||
} | ||
|
||
return new BulkInsertResult($ids, $result->getAffected()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace PeachySQL\Pgsql; | ||
|
||
use PeachySQL\BaseOptions; | ||
|
||
/** | ||
* Handles PostgreSQL-specific options | ||
*/ | ||
class Options extends BaseOptions | ||
{ | ||
// https://stackoverflow.com/questions/6581573/what-are-the-max-number-of-allowable-parameters-per-database-provider-type | ||
public int $maxBoundParams = 65_535; // 2 ** 16 - 1; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace PeachySQL\Pgsql; | ||
|
||
use PeachySQL\BaseStatement; | ||
use PeachySQL\Pgsql; | ||
use PDO; | ||
use PDOException; | ||
use PDOStatement; | ||
|
||
class Statement extends BaseStatement | ||
{ | ||
private ?PDOStatement $stmt; | ||
|
||
public function __construct(PDOStatement $stmt, bool $usedPrepare) | ||
{ | ||
parent::__construct($usedPrepare); | ||
$this->stmt = $stmt; | ||
} | ||
|
||
public function execute(): void | ||
{ | ||
if ($this->stmt === null) { | ||
throw new \Exception('Cannot execute closed statement'); | ||
} | ||
|
||
try { | ||
if (!$this->stmt->execute()) { | ||
throw Pgsql::getError('Failed to execute prepared statement', $this->stmt->errorInfo()); | ||
} | ||
} catch (PDOException $e) { | ||
throw Pgsql::getError('Failed to execute prepared statement', $this->stmt->errorInfo()); | ||
} | ||
|
||
$this->affected = $this->stmt->rowCount(); | ||
|
||
if (!$this->usedPrepare && $this->stmt->columnCount() === 0) { | ||
$this->close(); // no results, so statement can be closed | ||
} | ||
} | ||
|
||
public function getIterator(): \Generator | ||
{ | ||
if ($this->stmt !== null) { | ||
while ( | ||
/** @var array|false $row */ | ||
$row = $this->stmt->fetch(PDO::FETCH_ASSOC) | ||
) { | ||
yield $row; | ||
} | ||
|
||
if (!$this->usedPrepare) { | ||
$this->close(); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Closes the prepared statement and deallocates the statement handle. | ||
* @throws \Exception if the statement has already been closed | ||
*/ | ||
public function close(): void | ||
{ | ||
if ($this->stmt === null) { | ||
throw new \Exception('Statement has already been closed'); | ||
} | ||
|
||
$this->stmt->closeCursor(); | ||
$this->stmt = null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.