$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; } } }