nur-sery/nur_src/m/base/Query.php

224 lines
7.1 KiB
PHP

<?php
namespace nur\m\base;
use Iterator;
use nur\A;
use nur\m\IQuery;
use nur\m\IRowIncarnation;
use nur\m\IRowIterator;
abstract class Query implements IQuery {
/**
* retourner une chaine quotée pour SQL ou "null" si
* ($value === "" && !$allow_empty)
*/
static function qv(string $value, bool $allowEmpty=false): string {
if ($value) return "'".str_replace("'", "''", $value)."'";
elseif ($allowEmpty) return "''";
else return "null";
}
# nb de caractères minimum pour une recherche approximative
const LIKE_THRESHOLD = 2;
static function likev($value, bool $partial=false, ?int $likeThreshold=null) {
if ($value === false || $value === null) return $value;
if ($likeThreshold === null) $likeThreshold = static::LIKE_THRESHOLD;
if ($partial && strlen($value) >= $likeThreshold) {
// Rajouter un espace qui sera remplacé par %
if (!preg_match('/[- %]$/', $value)) $value .= " ";
}
return preg_replace('/[- ]+/', '%', $value);
}
static function build_actual_query(string $query, ?array $bindings) {
if ($bindings !== null) {
AbstractConn::fix_sql_with_seq_bindings($query, $bindings);
foreach ($bindings as $name => $value) {
if (A::is_seq($value) && count($value) == 0) {
$value = null;
}
if ($value === null) {
$value = "null";
$query = str_replace(":$name", $value, $query);
} elseif (A::is_seq($value)) {
$values = $value;
$count = count($values);
for ($i = $count - 1; $i >= 0; $i--) {
$value = $values[$i];
if (is_array($value)) {
# tableau associatif avec les informations sur le type de la valeur
#XXX pour le moment, le type est ignoré
$value = $value["value"];
}
if (!is_string($value)) $value = strval($value);
// chaine
if (!preg_match('/^[1-9][0-9]*$/', $value)) {
$value = self::qv($value, true);
}
$query = str_replace(":${name}_$i", $value, $query);
}
} else {
if (is_array($value)) {
# tableau associatif avec les informations sur le type de la valeur
#XXX pour le moment, le type est ignoré
$value = $value["value"];
}
if (!is_string($value)) $value = strval($value);
// chaine
if (!preg_match('/^[1-9][0-9]*$/', $value)) {
$value = self::qv($value, true);
}
$query = str_replace(":$name", $value, $query);
}
}
}
return str_replace("\n", " ", $query);
}
const TYPE_SELECT = 1, TYPE_UPDATE = 2, TYPE_INSERT = 3;
/** @var int type de requête */
protected $type;
/** @var string|null la requête SQL effective */
protected $sql;
/** @var array|null le filtre permettant de sélectionner les lignes */
protected $filter;
/** @var mixed une ligne de données, pour mise à jour ou insertion */
protected $row;
/** @var array|null le tableau retour */
protected $results;
/** @var IRowIncarnation */
protected $incarnation;
abstract protected function newRowIncarnation(): IRowIncarnation;
abstract protected function newRowIterator(?string $sql, ?array &$bindings=null, ?IRowIncarnation $incarnation=null): IRowIterator;
function setIncarnation(IRowIncarnation $incarnation): IQuery {
$this->incarnation = $incarnation;
return $this;
}
#############################################################################
/** @var string requête SQL de sélection par défaut */
const SQL_SELECT = null;
function select(?string $sql=null, ?array $filter=null): IQuery {
if ($sql === null) $sql = static::SQL_SELECT;
$this->type = self::TYPE_SELECT;
$this->sql = $sql;
$this->filter = $filter;
$this->row = null;
$result = null;
$this->results =& $result;
return $this;
}
/** @var string requête SQL de mise à jour par défaut */
const SQL_UPDATE = null;
function update(?string $sql=null, ?array $filter=null, $row=null, ?array &$results=null): IQuery {
if ($sql === null) $sql = static::SQL_UPDATE;
$this->type = self::TYPE_UPDATE;
$this->sql = $sql;
$this->filter = $filter;
$this->row = $row;
$this->results =& $results;
return $this;
}
/** @var string requête SQL d'insertion par défaut */
const SQL_INSERT = null;
function insert(?string $sql=null, $row=null, ?array &$results=null): IQuery {
if ($sql === null) $sql = static::SQL_INSERT;
$this->type = self::TYPE_INSERT;
$this->sql = $sql;
$this->filter = null;
$this->row = $row;
$this->results =& $results;
return $this;
}
/** valider les filtres utilisés et retourner le cas échéant une requête sql mise à jour */
protected function validateFilter(): ?string {
return null;
}
abstract protected function _execute(bool $commit): IRowIterator;
function execute(bool $commit=false): IRowIterator {
return $this->_execute($commit);
}
function execute2(?array $filter=null, $row=null, ?array &$results=null): IRowIterator {
if ($filter !== null) $this->filter = $filter;
if ($row !== null) $this->row = $row;
if ($results !== null) $this->results =& $results;
return $this->_execute(false);
}
function all(): array { return $this->execute()->all(); }
function allVals(?string $name=null): array {
return rows::vals($this->all(), $name);
}
function first($default=null) { return $this->execute()->first($default); }
function firstVal(?string $name=null, $default=null) {
return rows::val($this->first(), $name, $default);
}
function numRows(): int { return $this->execute()->numRows(); }
function insertId() { return $this->execute()->insertId(); }
function one($default=null, ?bool $rewind=null): array { return $this->execute()->one($default, $rewind); }
function peek($default=null, ?bool $rewind=false): array { return $this->execute()->peek($default, $rewind); }
/** @var Iterator */
protected $rowIterator;
protected $firstRow;
function getRow(?string $sql=null) {
if ($sql !== null) $this->search(null, $sql);
return $this->firstRow;
}
function search(?array $filter, ?string $sql=null): bool {
if ($sql !== null) $this->sql = $sql;
$this->firstRow = null;
$this->rowIterator = null;
[$first, $second, $iterator] = $this->execute2($filter)->peek();
if ($second !== null) {
$this->rowIterator = $iterator;
} elseif ($first !== null) {
$this->firstRow = $first;
}
return $this->firstRow !== null;
}
function isClosed(): bool {
$rowIterator = $this->rowIterator;
if ($rowIterator instanceof IRowIterator) return $rowIterator->isClosed();
else return true;
}
function rewind(): void {
if ($this->isClosed()) $this->rowIterator = $this->execute();
$this->rowIterator->rewind();
}
function valid(): bool {
$valid = $this->rowIterator->valid();
if (!$valid) $this->rowIterator = null;
return $valid;
}
function key() { return $this->rowIterator->key(); }
function current() { return $this->rowIterator->current(); }
function next() { $this->rowIterator->next(); }
}