nur-sery/src/db/sqlite/_query.php

213 lines
6.1 KiB
PHP

<?php
namespace nur\sery\db\sqlite;
use nur\sery\cl;
use nur\sery\str;
use nur\sery\ValueException;
use SQLite3;
use SQLite3Stmt;
class _query {
static function verifix(&$query, ?array &$params=null): void {
if (is_array($query)) {
$prefix = $query[0] ?? null;
if ($prefix === null) {
throw new ValueException("requête invalide");
} elseif (_query_create::isa($prefix)) {
$query = _query_create::parse($query, $params);
} elseif (_query_select::isa($prefix)) {
$query = _query_select::parse($query, $params);
} elseif (_query_insert::isa($prefix)) {
$query = _query_insert::parse($query, $params);
} elseif (_query_update::isa($prefix)) {
$query = _query_update::parse($query, $params);
} elseif (_query_delete::isa($prefix)) {
$query = _query_delete::parse($query, $params);
} elseif (_query_generic::isa($prefix)) {
$query = _query_generic::parse($query, $params);
} else {
throw SqliteException::wrap(ValueException::invalid_kind($query, "query"));
}
} elseif (!is_string($query)) {
$query = strval($query);
}
}
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
$string = substr($string, strlen($ms[0]));
return true;
}
/** fusionner toutes les parties séquentielles d'une requête */
protected static function merge_seq(array $query): string {
$index = 0;
$sql = "";
foreach ($query as $key => $value) {
if ($key === $index) {
$index++;
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
$sql .= " ";
}
$sql .= $value;
}
}
return $sql;
}
protected static function is_sep(&$cond): bool {
if (!is_string($cond)) return false;
if (!preg_match('/^\s*(and|or|not)\s*$/i', $cond, $ms)) return false;
$cond = $ms[1];
return true;
}
static function parse_conds(?array $conds, ?array &$sql, ?array &$params): void {
if (!$conds) return;
$sep = null;
$index = 0;
$condsql = [];
foreach ($conds as $key => $cond) {
if ($key === $index) {
## séquentiel
if ($index === 0 && self::is_sep($cond)) {
$sep = $cond;
} elseif (is_array($cond)) {
# condition récursive
self::parse_conds($cond, $condsql, $params);
} else {
# condition litérale
$condsql[] = strval($cond);
}
$index++;
} else {
## associatif
# paramètre
$param = $key;
if ($params !== null && array_key_exists($param, $params)) {
$i = 1;
while (array_key_exists("$key$i", $params)) {
$i++;
}
$param = "$key$i";
}
# value ou [operator, value]
if (is_array($cond)) {
#XXX implémenter le support de ["between", lower, upper]
# et aussi ["in", values]
$op = null;
$value = null;
$condkeys = array_keys($cond);
if (array_key_exists("op", $cond)) $op = $cond["op"];
if (array_key_exists("value", $cond)) $value = $cond["value"];
$condkey = 0;
if ($op === null && array_key_exists($condkey, $condkeys)) {
$op = $cond[$condkeys[$condkey]];
$condkey++;
}
if ($value === null && array_key_exists($condkey, $condkeys)) {
$value = $cond[$condkeys[$condkey]];
$condkey++;
}
} else {
$op = "=";
$value = $cond;
}
$cond = [$key, $op];
if ($value !== null) {
$cond[] = ":$param";
$params[$param] = $value;
}
$condsql[] = implode(" ", $cond);
}
}
if ($sep === null) $sep = "and";
$count = count($condsql);
if ($count > 1) {
$sql[] = "(" . implode(" $sep ", $condsql) . ")";
} elseif ($count == 1) {
$sql[] = $condsql[0];
}
}
static function parse_set_values(?array $values, ?array &$sql, ?array &$params): void {
if (!$values) return;
$index = 0;
$parts = [];
foreach ($values as $key => $part) {
if ($key === $index) {
## séquentiel
if (is_array($part)) {
# paramètres récursifs
self::parse_set_values($part, $parts, $params);
} else {
# paramètre litéral
$parts[] = strval($part);
}
$index++;
} else {
## associatif
# paramètre
$param = $key;
if ($params !== null && array_key_exists($param, $params)) {
$i = 1;
while (array_key_exists("$key$i", $params)) {
$i++;
}
$param = "$key$i";
}
# value
$value = $part;
$part = [$key, "="];
if ($value === null) {
$part[] = "null";
} else {
$part[] = ":$param";
$params[$param] = $value;
}
$parts[] = implode(" ", $part);
}
}
$sql = cl::merge($sql, $parts);
}
protected static function check_eof(string $tmpsql, string $usersql): void {
self::consume(';\s*', $tmpsql);
if ($tmpsql) {
throw new ValueException("unexpected value at end: $usersql");
}
}
function __construct($sql, ?array $params=null) {
self::verifix($sql, $params);
$this->sql = $sql;
$this->params = $params;
}
/** @var string */
protected $sql;
/** @var ?array */
protected $params;
function useStmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
if ($this->params !== null) {
/** @var SQLite3Stmt $stmt */
$stmt = SqliteException::check($db, $db->prepare($this->sql));
$close = true;
try {
foreach ($this->params as $param => $value) {
SqliteException::check($db, $stmt->bindValue($param, $value));
}
$close = false;
return true;
} finally {
if ($close) $stmt->close();
}
} else {
$sql = $this->sql;
return false;
}
}
}