180 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nur\m\pgsql;
 | |
| 
 | |
| 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);
 | |
|   }
 | |
| }
 |