$value) { if ($key === $index) { $index++; if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) { $sql .= " "; } $sql .= $value; } } return $sql; } 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)) { $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); } const create_SCHEMA = [ "prefix" => "string", "table" => "string", "schema" => "?array", "cols" => "?array", "suffix" => "?string", ]; static function is_create(string $sql): bool { return false; } static function parse_create(array $query, ?array &$params=null): string { } const select_SCHEMA = [ "prefix" => "string", "schema" => "?array", "cols" => "?array", "from" => "?string", "where" => "?array", "order by" => "?array", "group by" => "?array", "having" => "?array", ]; static function is_select(string $sql): bool { return preg_match("/^select\b/i", $sql); } /** * parser une chaine de la forme * "select [COLS] [from TABLE] [where CONDS] [order by ORDERS] [group by GROUPS] [having CONDS]" */ static function parse_select(array $query, ?array &$params=null): string { # fusionner d'abord toutes les parties séquentielles $usersql = $tmpsql = self::merge_seq($query); ### vérifier la présence des parties nécessaires $sql = []; if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix; ## select self::consume('select\s*', $tmpsql); $sql[] = "select"; ## cols $usercols = []; if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) { if ($ms[1]) $usercols[] = $ms[1]; } $tmpcols = cl::withn($query["cols"] ?? null); $schema = $query["schema"] ?? null; if ($tmpcols !== null) { $cols = []; $index = 0; foreach ($tmpcols as $key => $col) { if ($key === $index) { $index++; $cols[] = $col; $usercols[] = $col; } else { $cols[] = $key; $usercols[] = "$col as $key"; } } } else { $cols = null; if ($schema && is_array($schema) && !in_array("*", $usercols)) { $cols = array_keys($schema); $usercols = array_merge($usercols, $cols); } } if (!$usercols && !$cols) $usercols = ["*"]; $sql[] = implode(" ", $usercols); ## from $from = $query["from"] ?? null; if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) { if ($from === null) $from = $ms[1]; $sql[] = "from"; $sql[] = $from; } elseif ($from !== null) { $sql[] = "from"; $sql[] = $from; } else { throw new ValueException("expected table name: $usersql"); } ## where $userwhere = []; if (self::consume('where\b\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) { if ($ms[1]) $userwhere[] = $ms[1]; } $where = cl::withn($query["where"] ?? null); if ($where !== null) self::parse_conds($where, $userwhere, $params); if ($userwhere) { $sql[] = "where"; $sql[] = implode(" and ", $userwhere); } ## order by $userorderby = []; if (self::consume('order\s+by\b\s*(.*?)(?=;?\s*$|\bgroup\s+by\b)', $tmpsql, $ms)) { if ($ms[1]) $userorderby[] = $ms[1]; } $orderby = cl::withn($query["order by"] ?? null); if ($orderby !== null) { $index = 0; foreach ($orderby as $key => $value) { if ($key === $index) { $userorderby[] = $value; $index++; } elseif ($value !== null) { if (is_bool($value)) $value = $value? "asc": "desc"; $userorderby[] = "$key $value"; } } } if ($userorderby) { $sql[] = "order by"; $sql[] = implode(", ", $userorderby); } ## group by $usergroupby = []; if (self::consume('group\s+by\b\s*(.*?)(?=;?\s*$|\bhaving\b)', $tmpsql, $ms)) { if ($ms[1]) $usergroupby[] = $ms[1]; } $groupby = cl::withn($query["group by"] ?? null); if ($groupby !== null) { $index = 0; foreach ($groupby as $key => $value) { if ($key === $index) { $usergroupby[] = $value; $index++; } elseif ($value !== null) { $usergroupby[] = $key; } } } if ($usergroupby) { $sql[] = "group by"; $sql[] = implode(", ", $usergroupby); } ## having $userhaving = []; if (self::consume('having\b\s*(.*?)(?=;?\s*$)', $tmpsql, $ms)) { if ($ms[1]) $userhaving[] = $ms[1]; } $having = cl::withn($query["having"] ?? null); if ($having !== null) self::parse_conds($having, $userhaving, $params); if ($userhaving) { $sql[] = "having"; $sql[] = implode(" and ", $userhaving); } ## fin de la requête self::consume(';\s*', $tmpsql); if ($tmpsql) { throw new ValueException("unexpected value at end: $usersql"); } return implode(" ", $sql); } const insert_SCHEMA = [ "prefix" => "string", "into" => "?string", "schema" => "?array", "cols" => "?array", "values" => "?array", ]; static function is_insert(string $sql): bool { return preg_match("/^insert\b/i", $sql); } /** * parser une chaine de la forme * "insert [into] [TABLE] [(COLS)] [values (VALUES)]" */ static function parse_insert(array $query, ?array &$params=null): string { # fusionner d'abord toutes les parties séquentielles $usersql = $tmpsql = self::merge_seq($query); # vérifier la présence des parties nécessaires $sql = []; if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix; self::consume('insert\s*', $tmpsql); $sql[] = "insert"; self::consume('into\s*', $tmpsql); $sql[] = "into"; $into = $query["into"] ?? null; if (self::consume('([a-z_][a-z0-9_]*)\s*', $tmpsql, $ms)) { if ($into === null) $into = $ms[1]; $sql[] = $into; } elseif ($into !== null) { $sql[] = $into; } else { throw new ValueException("expected table name: $usersql"); } $usercols = []; $uservalues = []; if (self::consume('\(([^)]*)\)\s*', $tmpsql, $ms)) { $usercols = array_merge($usercols, preg_split("/\s*,\s*/", $ms[1])); } $cols = cl::withn($query["cols"] ?? null); $values = cl::withn($query["values"] ?? null); $schema = $query["schema"] ?? null; if ($cols === null) { if ($usercols) { $cols = $usercols; } elseif ($values) { $cols = array_keys($values); $usercols = array_merge($usercols, $cols); } elseif ($schema && is_array($schema)) { #XXX implémenter support AssocSchema $cols = array_keys($schema); $usercols = array_merge($usercols, $cols); } } if (self::consume('values\s+\(\s*(.*)\s*\)\s*', $tmpsql, $ms)) { if ($ms[1]) $uservalues[] = $ms[1]; } self::consume(';\s*', $tmpsql); if ($tmpsql) { throw new ValueException("unexpected value at end: $usersql"); } if ($cols !== null && !$uservalues) { if (!$usercols) $usercols = $cols; foreach ($cols as $col) { $uservalues[] = ":$col"; $params[$col] = $values[$col] ?? null; } } $sql[] = "(".implode(", ", $usercols).")"; $sql[] = "values (".implode(", ", $uservalues).")"; return implode(" ", $sql); } const update_SCHEMA = [ "prefix" => "string", "table" => "?string", "schema" => "?array", "cols" => "?array", "values" => "?array", "where" => "?array", ]; static function is_update(string $sql): bool { return false; } static function parse_update(array $query, ?array &$params=null): string { } const delete_SCHEMA = [ "prefix" => "string", "from" => "?string", "where" => "?array", ]; static function is_delete(string $sql): bool { return false; } static function parse_delete(array $query, ?array &$params=null): string { } 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; } } }