295 lines
7.8 KiB
PHP
295 lines
7.8 KiB
PHP
<?php
|
|
namespace nulib\db\pgsql;
|
|
|
|
use nulib\cl;
|
|
use nulib\db\_private\_config;
|
|
use nulib\db\_private\Tvalues;
|
|
use nulib\db\IDatabase;
|
|
use nulib\db\ITransactor;
|
|
use nulib\php\func;
|
|
use nulib\ValueException;
|
|
|
|
class Pgsql implements IDatabase {
|
|
use Tvalues;
|
|
|
|
static function with($pgsql, ?array $params=null): self {
|
|
if ($pgsql instanceof static) {
|
|
return $pgsql;
|
|
} elseif ($pgsql instanceof self) {
|
|
# recréer avec les mêmes paramètres
|
|
return new static(null, cl::merge([
|
|
"dbconn" => $pgsql->dbconn,
|
|
"options" => $pgsql->options,
|
|
"config" => $pgsql->config,
|
|
"migration" => $pgsql->migration,
|
|
], $params));
|
|
} else {
|
|
return new static($pgsql, $params);
|
|
}
|
|
}
|
|
|
|
|
|
protected const OPTIONS = [
|
|
"persistent" => true,
|
|
"force_new" => false,
|
|
"serial_support" => true,
|
|
];
|
|
|
|
const CONFIG = null;
|
|
|
|
const MIGRATION = null;
|
|
|
|
const params_SCHEMA = [
|
|
"dbconn" => ["array"],
|
|
"options" => ["?array|callable"],
|
|
"replace_config" => ["?array|callable"],
|
|
"config" => ["?array|callable"],
|
|
"migration" => ["?array|string|callable"],
|
|
"auto_open" => ["bool", true],
|
|
];
|
|
|
|
const dbconn_SCHEMA = [
|
|
"" => "?string",
|
|
"host" => "string",
|
|
"hostaddr" => "?string",
|
|
"port" => "?int",
|
|
"dbname" => "string",
|
|
"user" => "string",
|
|
"password" => "string",
|
|
"connect_timeout" => "?int",
|
|
"options" => "?string",
|
|
"sslmode" => "?string",
|
|
"service" => "?string",
|
|
];
|
|
|
|
protected const dbconn_MAP = [
|
|
"name" => "dbname",
|
|
"pass" => "password",
|
|
];
|
|
|
|
const options_SCHEMA = [
|
|
"persistent" => ["bool", self::OPTIONS["persistent"]],
|
|
"force_new" => ["bool", self::OPTIONS["force_new"]],
|
|
];
|
|
|
|
function __construct($dbconn=null, ?array $params=null) {
|
|
if ($dbconn !== null) {
|
|
if (!is_array($dbconn)) {
|
|
$dbconn = ["" => $dbconn];
|
|
#XXX à terme, il faudra interroger config
|
|
#$tmp = config::db($dbconn);
|
|
#if ($tmp !== null) $dbconn = $tmp;
|
|
#else $dbconn = ["" => $dbconn];
|
|
}
|
|
$params["dbconn"] = $dbconn;
|
|
}
|
|
# dbconn
|
|
$this->dbconn = $params["dbconn"] ?? null;
|
|
# options
|
|
$this->options = $params["options"] ?? static::OPTIONS;
|
|
# configuration
|
|
$config = $params["replace_config"] ?? null;
|
|
if ($config === null) {
|
|
$config = $params["config"] ?? static::CONFIG;
|
|
if (is_callable($config)) $config = [$config];
|
|
}
|
|
$this->config = $config;
|
|
# migrations
|
|
$this->migration = $params["migration"] ?? static::MIGRATION;
|
|
#
|
|
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
|
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
|
$this->open();
|
|
}
|
|
}
|
|
|
|
protected ?array $dbconn;
|
|
|
|
/** @var array|callable|null */
|
|
protected $options;
|
|
|
|
/** @var array|string|callable */
|
|
protected $config;
|
|
|
|
/** @var array|string|callable */
|
|
protected $migration;
|
|
|
|
/** @var resource */
|
|
protected $db = null;
|
|
|
|
function open(): self {
|
|
if ($this->db === null) {
|
|
$dbconn = $this->dbconn;
|
|
$connection_string = [$dbconn[""] ?? null];
|
|
unset($dbconn[""]);
|
|
foreach ($dbconn as $key => $value) {
|
|
if ($value === null) continue;
|
|
$value = strval($value);
|
|
if ($value === "" || preg_match("/[ '\\\\]/", $value)) {
|
|
$value = str_replace("\\", "\\\\", $value);
|
|
$value = str_replace("'", "\\'", $value);
|
|
$value = "'$value'";
|
|
}
|
|
$key = cl::get(self::dbconn_MAP, $key, $key);
|
|
$connection_string[] = "$key=$value";
|
|
}
|
|
$connection_string = implode(" ", array_filter($connection_string));
|
|
$options = $this->options;
|
|
if (is_callable($options)) {
|
|
$options = func::with($options)->bind($this, true)->invoke();
|
|
}
|
|
$forceNew = $options["force_new"] ?? false;
|
|
$flags = $forceNew? PGSQL_CONNECT_FORCE_NEW: 0;
|
|
|
|
if ($options["persistent"] ?? true) $db = pg_pconnect($connection_string, $flags);
|
|
else $db = pg_connect($connection_string, $flags);
|
|
if ($db === false) throw new PgsqlException("unable to connect");
|
|
$this->db = $db;
|
|
|
|
_config::with($this->config)->configure($this);
|
|
//_migration::with($this->migration)->migrate($this);
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
function close(): self {
|
|
if ($this->db !== null) {
|
|
pg_close($this->db);
|
|
$this->db = null;
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
protected function db() {
|
|
$this->open();
|
|
return $this->db;
|
|
}
|
|
|
|
function _exec(string $query): bool {
|
|
$result = pg_query($this->db(), $query);
|
|
if ($result === false) return false;
|
|
pg_free_result($result);
|
|
return true;
|
|
}
|
|
|
|
function getLastSerial() {
|
|
$db = $this->db();
|
|
$result = @pg_query($db, "select lastval()");
|
|
if ($result === false) return false;
|
|
$lastSerial = pg_fetch_row($result)[0];
|
|
pg_free_result($result);
|
|
return $lastSerial;
|
|
}
|
|
|
|
function exec($query, ?array $params=null) {
|
|
$db = $this->db();
|
|
$query = new _pgsqlQuery($query, $params);
|
|
$result = $query->_exec($db);
|
|
$serialSupport = $this->options["serial_support"] ?? true;
|
|
if ($serialSupport && $query->isInsert()) return $this->getLastSerial();
|
|
$affected_rows = pg_affected_rows($result);
|
|
pg_free_result($result);
|
|
return $affected_rows;
|
|
}
|
|
|
|
/** @var ITransactor[] */
|
|
protected ?array $transactors = null;
|
|
|
|
function willUpdate(...$transactors): self {
|
|
foreach ($transactors as $transactor) {
|
|
if ($transactor instanceof ITransactor) {
|
|
$this->transactors[] = $transactor;
|
|
$transactor->willUpdate();
|
|
} else {
|
|
throw ValueException::invalid_type($transactor, ITransactor::class);
|
|
}
|
|
}
|
|
return $this;
|
|
}
|
|
|
|
function inTransaction(?bool &$inerror=null): bool {
|
|
$status = pg_transaction_status($this->db());
|
|
if ($status === PGSQL_TRANSACTION_ACTIVE || $status === PGSQL_TRANSACTION_INTRANS) {
|
|
$inerror = false;
|
|
return true;
|
|
} elseif ($status === PGSQL_TRANSACTION_INERROR) {
|
|
$inerror = true;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
|
$this->_exec("begin");
|
|
if ($this->transactors !== null) {
|
|
foreach ($this->transactors as $transactor) {
|
|
$transactor->beginTransaction();
|
|
}
|
|
}
|
|
if ($func !== null) {
|
|
$commited = false;
|
|
try {
|
|
func::call($func, $this);
|
|
if ($commit) {
|
|
$this->commit();
|
|
$commited = true;
|
|
}
|
|
} finally {
|
|
if ($commit && !$commited) $this->rollback();
|
|
}
|
|
}
|
|
}
|
|
|
|
function commit(): void {
|
|
$this->_exec("commit");
|
|
if ($this->transactors !== null) {
|
|
foreach ($this->transactors as $transactor) {
|
|
$transactor->commit();
|
|
}
|
|
}
|
|
}
|
|
|
|
function rollback(): void {
|
|
$this->_exec("rollback");
|
|
if ($this->transactors !== null) {
|
|
foreach ($this->transactors as $transactor) {
|
|
$transactor->rollback();
|
|
}
|
|
}
|
|
}
|
|
|
|
function get($query, ?array $params=null, bool $entireRow=false) {
|
|
$db = $this->db();
|
|
$query = new _pgsqlQuery($query, $params);
|
|
$result = $query->_exec($db);
|
|
$row = pg_fetch_assoc($result);
|
|
pg_free_result($result);
|
|
if ($row === false) return null;
|
|
$this->verifixRow($row);
|
|
if ($entireRow) return $row;
|
|
else return cl::first($row);
|
|
}
|
|
|
|
function one($query, ?array $params=null): ?array {
|
|
return $this->get($query, $params, true);
|
|
}
|
|
|
|
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
|
$db = $this->db();
|
|
$query = new _pgsqlQuery($query, $params);
|
|
$result = $query->_exec($db);
|
|
$primaryKeys = cl::withn($primaryKeys);
|
|
while (($row = pg_fetch_assoc($result)) !== false) {
|
|
$this->verifixRow($row);
|
|
if ($primaryKeys !== null) {
|
|
$key = implode("-", cl::select($row, $primaryKeys));
|
|
yield $key => $row;
|
|
} else {
|
|
yield $row;
|
|
}
|
|
}
|
|
pg_free_result($result);
|
|
}
|
|
}
|