181 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace nur\m\pgsql;
 | 
						|
 | 
						|
use nur\base;
 | 
						|
use nur\config;
 | 
						|
use nur\debug;
 | 
						|
use nur\m\base\AbstractConn;
 | 
						|
use nur\m\base\QueryException;
 | 
						|
use nur\m\IQuery;
 | 
						|
use nur\m\IRowIncarnation;
 | 
						|
use nur\md;
 | 
						|
use nur\SV;
 | 
						|
 | 
						|
class PgsqlConn extends AbstractConn {
 | 
						|
  static function query_exception($res=null, ?string $cause=null): QueryException {
 | 
						|
    $parts = [];
 | 
						|
    if ($cause) $parts[] = $cause;
 | 
						|
    if ($res !== null) $parts[] = pg_last_error($res);
 | 
						|
    elseif (!$parts) $parts[] = "unknown error";
 | 
						|
    $message = implode(": ", $parts);
 | 
						|
    return new QueryException($message);
 | 
						|
  }
 | 
						|
 | 
						|
  protected $dbname, $dbuser, $dbpass, $options;
 | 
						|
  protected $conn;
 | 
						|
 | 
						|
  protected $inTransaction = false;
 | 
						|
 | 
						|
  function __construct($dbname, ?string $dbuser=null, ?string $dbpass=null) {
 | 
						|
    if (is_array($dbname)) {
 | 
						|
      if ($dbuser === null) $dbuser = $dbname["user"];
 | 
						|
      if ($dbpass === null) $dbpass = $dbname["pass"];
 | 
						|
      $dbname = $dbname["name"];
 | 
						|
    }
 | 
						|
    if (strpos($dbname, "=") === false) {
 | 
						|
      $connString = "dbname=$dbname";
 | 
						|
    } else {
 | 
						|
      $connString = $dbname;
 | 
						|
    }
 | 
						|
    if ($dbuser !== null) $connString .= " user=$dbuser";
 | 
						|
    if ($dbpass !== null) $connString .= " password=$dbpass";
 | 
						|
    $this->dbname = $dbname;
 | 
						|
    $this->dbuser = $dbuser;
 | 
						|
    $this->dbpass = $dbpass;
 | 
						|
    $conn = pg_connect($connString);
 | 
						|
    if ($conn === false) {
 | 
						|
      throw self::query_exception(null, "unable to connect");
 | 
						|
    }
 | 
						|
    $this->conn = $conn;
 | 
						|
  }
 | 
						|
 | 
						|
  function __destruct() {
 | 
						|
    $this->rollback();
 | 
						|
    if ($this->conn) pg_close($this->conn);
 | 
						|
  }
 | 
						|
 | 
						|
  function getInfos(): array {
 | 
						|
    return [$this->dbname, $this->dbuser, $this->dbpass];
 | 
						|
  }
 | 
						|
 | 
						|
  function beginTransaction(): void {
 | 
						|
    if (!$this->inTransaction) {
 | 
						|
      if (pg_query($this->conn, "begin") === false) {
 | 
						|
        throw self::query_exception(null, "unable to begin transaction");
 | 
						|
      }
 | 
						|
      $this->inTransaction = true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function commit(): void {
 | 
						|
    if ($this->inTransaction) {
 | 
						|
      $this->inTransaction = false;
 | 
						|
      if (pg_query($this->conn, "commit") === false) {
 | 
						|
        throw self::query_exception($this->conn, "error on commit");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function rollback(): void {
 | 
						|
    if ($this->inTransaction) {
 | 
						|
      $this->inTransaction = false;
 | 
						|
      if (pg_query($this->conn, "rollback") === false) {
 | 
						|
        throw self::query_exception($this->conn, "error on rollback");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function _execute0(string $sql, ?array &$bindings, array $params): array {
 | 
						|
    if ($params["transaction"]) $this->beginTransaction();
 | 
						|
 | 
						|
    if (!$bindings) {
 | 
						|
      $stmt = pg_query($this->conn, $sql);
 | 
						|
    } else {
 | 
						|
      # trier d'abord les champ par ordre de longueur, pour éviter les overlaps
 | 
						|
      $names = array_keys($bindings);
 | 
						|
      usort($names, function ($a, $b) {
 | 
						|
        return -SV::compare(strlen(strval($a)), strlen(strval($b)));
 | 
						|
      });
 | 
						|
      $bparams = [];
 | 
						|
      $number = 1;
 | 
						|
      foreach ($names as $name) {
 | 
						|
        $sql = str_replace(":$name", "\$$number", $sql);
 | 
						|
        $bparams[] = $bindings[$name];
 | 
						|
        $number++;
 | 
						|
      }
 | 
						|
      $stmt = pg_query_params($this->conn, $sql, $bparams);
 | 
						|
    }
 | 
						|
    if ($stmt === false) {
 | 
						|
      throw self::query_exception($this->conn, "execute error");
 | 
						|
    }
 | 
						|
 | 
						|
    $r = [];
 | 
						|
    if ($params["num_rows"]) {
 | 
						|
      $numRows = pg_affected_rows($stmt);
 | 
						|
      if ($numRows === false) throw self::query_exception($stmt, "error getting num_rows");
 | 
						|
      $r["num_rows"] = $numRows;
 | 
						|
    }
 | 
						|
    if ($params["last_insert_id"]) {
 | 
						|
      $r["insert_id"] = null;
 | 
						|
    }
 | 
						|
    if ($params["stmt"]) {
 | 
						|
      $r["stmt"] = $stmt;
 | 
						|
    } else {
 | 
						|
      pg_free_result($stmt);
 | 
						|
    }
 | 
						|
    return $r;
 | 
						|
  }
 | 
						|
 | 
						|
  function _prepareLogger(string $sql, ?array $bindings): array {
 | 
						|
    $queryLogger = $this->queryLogger;
 | 
						|
    $actualQuery = null;
 | 
						|
    $traceSql = config::k("trace_sql", false);
 | 
						|
    if ($traceSql || $queryLogger !== null) {
 | 
						|
      $actualQuery = PgsqlQuery::build_actual_query($sql, $bindings);
 | 
						|
    }
 | 
						|
    if ($traceSql) debug::log("SQL TRACE --", $actualQuery);
 | 
						|
    return [$queryLogger, $actualQuery];
 | 
						|
  }
 | 
						|
 | 
						|
  function _execute1(string $sql, ?array &$bindings, array $params): array {
 | 
						|
    [$queryLogger, $actualQuery] = $this->_prepareLogger($sql, $bindings);
 | 
						|
    $r = $this->_execute0($sql, $bindings, $params);
 | 
						|
    if ($queryLogger !== null) $queryLogger->logQuery($actualQuery);
 | 
						|
    return $r;
 | 
						|
  }
 | 
						|
 | 
						|
  function _execute(string $sql, ?array &$bindings=null, ?array $params=null): array {
 | 
						|
    md::ensure_schema($params, self::EXECUTE_PARAMS_SCHEMA);
 | 
						|
    return $this->_execute1($sql, $bindings, $params);
 | 
						|
  }
 | 
						|
 | 
						|
  function _fetchAll(string $sql, ?array &$bindings=null): array {
 | 
						|
    ["stmt" => $stmt
 | 
						|
    ] = $this->_execute1($sql, $bindings, self::EXECUTE_PARAMS_DQL);
 | 
						|
    try {
 | 
						|
      $rows = pg_fetch_all($stmt, PGSQL_ASSOC);
 | 
						|
      return $rows;
 | 
						|
    } finally {
 | 
						|
      pg_free_result($stmt);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function _fetchFirst(string $sql, ?array &$bindings=null): ?array {
 | 
						|
    ["stmt" => $stmt
 | 
						|
    ] = $this->_execute1($sql, $bindings, self::EXECUTE_PARAMS_DQL);
 | 
						|
    $row = pg_fetch_assoc($stmt);
 | 
						|
    pg_free_result($stmt);
 | 
						|
    return $row !== false? $row: null;
 | 
						|
  }
 | 
						|
 | 
						|
  function _update(string $sql, ?array &$bindings=null): int {
 | 
						|
    ["num_rows" => $numRows
 | 
						|
    ] = $this->_execute1($sql, $bindings, self::EXECUTE_PARAMS_DML_UPDATE);
 | 
						|
    return $numRows;
 | 
						|
  }
 | 
						|
 | 
						|
  function query(?string $sql=null, ?array $filter=null, ?IRowIncarnation $incarnation=null): IQuery {
 | 
						|
    return new PgsqlQuery($this, $sql, $filter, $incarnation);
 | 
						|
  }
 | 
						|
}
 |