<?php
namespace nur\m\pgsql;

use nur\b\IllegalAccessException;
use nur\m\base\OneRowIterator;
use nur\m\base\Query;
use nur\m\IQuery;
use nur\m\IRowIncarnation;
use nur\m\IRowIterator;

class PgsqlQuery extends Query {
  /** @var PgsqlConn */
  protected $conn;

  protected function newRowIncarnation(): IRowIncarnation {
    return new PgsqlRowIncarnation();
  }

  protected function newRowIterator(?string $sql, ?array &$bindings=null, ?IRowIncarnation $incarnation=null): IRowIterator {
    return new PgsqlRowIterator($this->conn, $sql, $bindings, $incarnation);
  }

  function __construct(PgsqlConn $conn, ?string $sql=null, ?array $filter=null, ?IRowIncarnation $incarnation=null) {
    $this->conn = $conn;
    if ($incarnation === null) $incarnation = $this->newRowIncarnation();
    $this->setIncarnation($incarnation);
    $this->select($sql, $filter);
  }

  protected function _execute(bool $commit): IRowIterator {
    $sql = $this->validateFilter();
    if ($sql === null) $sql = $this->sql;
    $incarnation = $this->incarnation;
    switch ($this->type) {
    case self::TYPE_SELECT:
      $incarnation->createBindings($bindings, $this->filter);
      return $this->newRowIterator($sql, $bindings, $incarnation);
    case self::TYPE_UPDATE;
      $incarnation->createBindings($bindings, $this->filter, $this->row, $this->results);
      $incarnation->prepareBindings($bindings);
      ["num_rows" => $numRows
      ] = $this->conn->_execute($sql, $bindings, PgsqlConn::EXECUTE_PARAMS_DML_UPDATE);
      $incarnation->loadResults($this->results, $bindings);
      if ($commit) $this->commit();
      return new OneRowIterator([
        "num_rows" => $numRows,
      ]);
    case self::TYPE_INSERT:
      $incarnation->createBindings($bindings, $this->filter, $this->row, $this->results);
      $incarnation->prepareBindings($bindings);
      ["insert_id" => $insertId
      ] = $this->conn->_execute($sql, $bindings, PgsqlConn::EXECUTE_PARAMS_DML_INSERT);
      $incarnation->loadResults($this->results, $bindings);
      if ($commit) $this->commit();
      return new OneRowIterator([
        "insert_id" => $insertId,
      ]);
    default:
      throw IllegalAccessException::unexpected_state();
    }
  }

  function beginTransaction(): IQuery {
    $this->conn->beginTransaction();
    return $this;
  }

  function commit(): IQuery {
    $this->conn->commit();
    return $this;
  }

  function rollback(): IQuery {
    $this->conn->rollback();
    return $this;
  }

  const SELECT_TABLE_NAME = <<<EOT
select to_regclass(:name) as name
EOT;

  function tableExists(string $tableName): bool {
    return $this->select(self::SELECT_TABLE_NAME, [
      "name" => $tableName,
    ])->firstVal("name") !== null;
  }
}