275 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			275 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace nur\m\oracle;
 | 
						|
 | 
						|
use nur\A;
 | 
						|
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;
 | 
						|
 | 
						|
class OracleConn extends AbstractConn {
 | 
						|
  static function query_exception($res=null, ?string $cause=null): QueryException {
 | 
						|
    $parts = array();
 | 
						|
    if ($cause) $parts[] = $cause;
 | 
						|
    $error = $res !== null? oci_error($res): oci_error();
 | 
						|
    $parts[] = $error["code"];
 | 
						|
    $parts[] = $error["message"];
 | 
						|
    # trouver un moyen de logguer les informations suivantes
 | 
						|
    #$parts[] = $error["offset"];
 | 
						|
    #$parts[] = $error["sqltext"];
 | 
						|
    $message = implode(": ", $parts);
 | 
						|
    return new QueryException($message);
 | 
						|
 | 
						|
  }
 | 
						|
 | 
						|
  const NLS_CHARSET = "AL32UTF8";
 | 
						|
 | 
						|
  const NLS_DATE_FORMAT_DATETIME = "YYYY-MM-DD HH24:MI:SS";
 | 
						|
  const NLS_DATE_FORMAT_DATEONLY = "YYYY-MM-DD";
 | 
						|
 | 
						|
  const NLS_SORT_BINARY = "BINARY";
 | 
						|
  const NLS_COMP_BINARY = "BINARY";
 | 
						|
 | 
						|
  protected $dbuser, $dbpass, $dbname;
 | 
						|
  protected $conn;
 | 
						|
 | 
						|
  protected $inSession = false;
 | 
						|
  private $nlsDateFormat = self::NLS_DATE_FORMAT_DATETIME;
 | 
						|
  private $nlsSort = self::NLS_SORT_BINARY;
 | 
						|
  private $nlsComp = self::NLS_COMP_BINARY;
 | 
						|
 | 
						|
  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"];
 | 
						|
    }
 | 
						|
    $this->dbuser = $dbuser;
 | 
						|
    $this->dbpass = $dbpass;
 | 
						|
    $this->dbname = $dbname;
 | 
						|
    $this->connect();
 | 
						|
  }
 | 
						|
 | 
						|
  function __destruct() {
 | 
						|
    $this->rollback();
 | 
						|
    $this->close();
 | 
						|
  }
 | 
						|
 | 
						|
  function getInfos(): array {
 | 
						|
    return [$this->dbname, $this->dbuser, $this->dbpass];
 | 
						|
  }
 | 
						|
 | 
						|
  private function connect() {
 | 
						|
    if ($this->conn === null) {
 | 
						|
      $conn = oci_pconnect($this->dbuser, $this->dbpass, $this->dbname, static::NLS_CHARSET);
 | 
						|
      if (!$conn) throw self::query_exception(null, "unable to connect");
 | 
						|
      $this->conn = $conn;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function setDateFormat($datetime=true) {
 | 
						|
    if ($datetime) $this->nls_date_format = self::NLS_DATE_FORMAT_DATETIME;
 | 
						|
    else $this->nls_date_format = self::NLS_DATE_FORMAT_DATEONLY;
 | 
						|
  }
 | 
						|
 | 
						|
  function setSortcomp($nlsSort="BINARY_CI", $nlsComp="LINGUISTIC") {
 | 
						|
    $this->nlsSort = $nlsSort;
 | 
						|
    $this->nlsComp = $nlsComp;
 | 
						|
  }
 | 
						|
 | 
						|
  private function initSession() {
 | 
						|
    if (!$this->inSession) {
 | 
						|
      # format des dates
 | 
						|
      $req = oci_parse($this->conn, "alter session set nls_date_format = '$this->nlsDateFormat'");
 | 
						|
      if (!$req) throw self::query_exception($req, "error setting nls_date_format");
 | 
						|
      oci_execute($req);
 | 
						|
      oci_free_statement($req);
 | 
						|
      # tri et comparaison
 | 
						|
      if ($this->nlsSort) {
 | 
						|
        $req = oci_parse($this->conn, "alter session set nls_sort = $this->nlsSort");
 | 
						|
        if (!$req) throw self::query_exception($req, "error setting nls_sort");
 | 
						|
        oci_execute($req);
 | 
						|
        oci_free_statement($req);
 | 
						|
      }
 | 
						|
      if ($this->nlsComp) {
 | 
						|
        $req = oci_parse($this->conn, "alter session set nls_comp = $this->nlsComp");
 | 
						|
        if (!$req) throw self::query_exception($req, "error setting nls_comp");
 | 
						|
        oci_execute($req);
 | 
						|
        oci_free_statement($req);
 | 
						|
      }
 | 
						|
      $this->inSession = true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function beginTransaction(): void {
 | 
						|
    $this->inTransaction = true;
 | 
						|
  }
 | 
						|
 | 
						|
  function commit(): void {
 | 
						|
    if ($this->inTransaction) {
 | 
						|
      $this->inTransaction = false;
 | 
						|
      $this->inSession = false;
 | 
						|
      if ($this->conn && oci_commit($this->conn) === false) {
 | 
						|
        throw self::query_exception(null, "error on commit");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function rollback(): void {
 | 
						|
    if ($this->inTransaction) {
 | 
						|
      $this->inTransaction = false;
 | 
						|
      $this->inSession = false;
 | 
						|
      if ($this->conn && oci_rollback($this->conn) === false) {
 | 
						|
        throw self::query_exception(null, "error on rollback");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private function bindParam($stmt, string $name, &$values): void {
 | 
						|
    if (A::is_assoc($values)) {
 | 
						|
      # tableau associatif: on peut spécifier la longueur et le type de la valeur
 | 
						|
      if (is_array($values["value"])) {
 | 
						|
        # tableau de valeurs
 | 
						|
        $max_array_length = A::get($values, "max_array_length", null);
 | 
						|
        if ($max_array_length === null) $max_array_length = count($values["value"]);
 | 
						|
        $max_item_length = A::get($values, "max_item_length", -1);
 | 
						|
        $type = A::get($values, "type", SQLT_CHR);
 | 
						|
        $r = oci_bind_array_by_name($stmt, $name, $values["value"], $max_array_length, $max_item_length, $type);
 | 
						|
      } else {
 | 
						|
        # valeur simple
 | 
						|
        $max_length = A::get($values, "max_length", -1);
 | 
						|
        $type = A::get($values, "type", SQLT_CHR);
 | 
						|
        $r = oci_bind_by_name($stmt, $name, $values["value"], $max_length, $type);
 | 
						|
      }
 | 
						|
    } elseif (is_array($values)) {
 | 
						|
      # valeur tableau, binder sur chaque élément individuellement
 | 
						|
      foreach ($values as $i => &$value) {
 | 
						|
        if (A::is_assoc($value)) {
 | 
						|
          # tableau associatif: on peut spécifier la longueur et le type de la valeur
 | 
						|
          $max_length = A::get($values, "max_length", -1);
 | 
						|
          $type = A::get($values, "type", SQLT_CHR);
 | 
						|
          $r = oci_bind_by_name($stmt, "${name}_${i}", $value["value"], $max_length, $type);
 | 
						|
        } else {
 | 
						|
          $r = oci_bind_by_name($stmt, "${name}_${i}", $value);
 | 
						|
        }
 | 
						|
        if ($r === false) break;
 | 
						|
      }; unset($value);
 | 
						|
    } else {
 | 
						|
      # valeur simple
 | 
						|
      $r = oci_bind_by_name($stmt, $name, $values);
 | 
						|
    }
 | 
						|
    if ($r === false) {
 | 
						|
      oci_free_statement($stmt);
 | 
						|
      throw self::query_exception($stmt, "error binding with $name");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function _execute0(string $sql, ?array &$bindings, array $params): array {
 | 
						|
    self::fix_sql_with_seq_bindings($sql, $bindings);
 | 
						|
    $this->connect();
 | 
						|
    $this->initSession();
 | 
						|
 | 
						|
    if ($params["transaction"]) $this->beginTransaction();
 | 
						|
 | 
						|
    $stmt = oci_parse($this->conn, $sql);
 | 
						|
    if ($stmt === false) throw self::query_exception($stmt, "preparation error");
 | 
						|
 | 
						|
    if ($bindings !== null) {
 | 
						|
      foreach ($bindings as $name => &$values) {
 | 
						|
        # IMPORTANT: il faut binder sur l'adresse de $values
 | 
						|
        $this->bindParam($stmt, $name, $values);
 | 
						|
      }; unset($values);
 | 
						|
    }
 | 
						|
 | 
						|
    if ($this->inTransaction) $mode = OCI_NO_AUTO_COMMIT;
 | 
						|
    else $mode = OCI_COMMIT_ON_SUCCESS;
 | 
						|
    $r = oci_execute($stmt, $mode);
 | 
						|
    if ($r === false) throw self::query_exception($stmt, "execute error");
 | 
						|
 | 
						|
    $r = [];
 | 
						|
    if ($params["num_rows"]) {
 | 
						|
      $numRows = oci_num_rows($stmt);
 | 
						|
      if ($numRows === false) throw self::query_exception(null, "error getting num_rows");
 | 
						|
      $r["num_rows"] = $numRows;
 | 
						|
    }
 | 
						|
    if ($params["last_insert_id"]) {
 | 
						|
      $r["insert_id"] = null;
 | 
						|
    }
 | 
						|
    if ($params["stmt"]) {
 | 
						|
      $r["stmt"] = $stmt;
 | 
						|
    } else {
 | 
						|
      oci_free_statement($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 = OracleQuery::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 = [];
 | 
						|
      $num_rows = oci_fetch_all($stmt, $rows, 0, -1, OCI_FETCHSTATEMENT_BY_ROW + OCI_ASSOC);
 | 
						|
      if ($num_rows === false) throw self::query_exception(null, "fetch error");
 | 
						|
      return $rows;
 | 
						|
    } finally {
 | 
						|
      oci_free_statement($stmt);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function _fetchFirst(string $sql, ?array &$bindings=null): ?array {
 | 
						|
    ["stmt" => $stmt
 | 
						|
    ] = $this->_execute1($sql, $bindings, self::EXECUTE_PARAMS_DQL);
 | 
						|
    $row = oci_fetch_assoc($stmt);
 | 
						|
    oci_free_statement($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 OracleQuery($this, $sql, $filter, $incarnation);
 | 
						|
  }
 | 
						|
 | 
						|
  function close(): void {
 | 
						|
    if ($this->conn !== null) {
 | 
						|
      $r = oci_close($this->conn);
 | 
						|
      $this->conn = null;
 | 
						|
      if ($r === false) {
 | 
						|
        throw self::query_exception(null, "unable to close");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |