modifs.mineures sans commentaires
This commit is contained in:
parent
21c1d33085
commit
3b5055b91a
|
@ -72,8 +72,8 @@ class Sqlite {
|
|||
function open(): self {
|
||||
if ($this->db === null) {
|
||||
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
||||
_Config::with($this->config)->configure($this);
|
||||
_Migration::with($this->migration)->migrate($this);
|
||||
_config::with($this->config)->configure($this);
|
||||
_migration::with($this->migration)->migrate($this);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ class Sqlite {
|
|||
|
||||
function exec($query, ?array $params=null): bool {
|
||||
$this->open();
|
||||
$query = new _Query($query, $params);
|
||||
$query = new _query($query, $params);
|
||||
$db = $this->db;
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
try {
|
||||
|
@ -120,7 +120,7 @@ class Sqlite {
|
|||
|
||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||
$this->open();
|
||||
$query = new _Query($query, $params);
|
||||
$query = new _query($query, $params);
|
||||
$db = $this->db;
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
try {
|
||||
|
@ -157,7 +157,7 @@ class Sqlite {
|
|||
|
||||
function all($query, ?array $params=null): iterable {
|
||||
$this->open();
|
||||
$query = new _Query($query, $params);
|
||||
$query = new _query($query, $params);
|
||||
$db = $this->db;
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
try {
|
||||
|
|
|
@ -1,474 +0,0 @@
|
|||
<?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 (self::is_create($prefix)) {
|
||||
$query = self::parse_create($query, $params);
|
||||
} elseif (self::is_select($prefix)) {
|
||||
$query = self::parse_select($query, $params);
|
||||
} elseif (self::is_insert($prefix)) {
|
||||
$query = self::parse_insert($query, $params);
|
||||
} elseif (self::is_update($prefix)) {
|
||||
$query = self::parse_update($query, $params);
|
||||
} elseif (self::is_delete($prefix)) {
|
||||
$query = self::parse_delete($query, $params);
|
||||
} else {
|
||||
throw SqliteException::wrap(ValueException::invalid_kind($query, "query"));
|
||||
}
|
||||
} elseif (!is_string($query)) {
|
||||
$query = strval($query);
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
|
||||
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++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$userorderby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$userorderby[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$usergroupby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$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;
|
||||
|
||||
## insert
|
||||
self::consume('insert\s*', $tmpsql); $sql[] = "insert";
|
||||
|
||||
## into
|
||||
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");
|
||||
}
|
||||
|
||||
## cols & values
|
||||
$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];
|
||||
}
|
||||
|
||||
## fin de la requête
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,9 +3,9 @@ namespace nur\sery\db\sqlite;
|
|||
|
||||
use nur\sery\php\func;
|
||||
|
||||
class _Config {
|
||||
static function with($configs): _Config {
|
||||
if ($configs instanceof self) return $configs;
|
||||
class _config {
|
||||
static function with($configs): self {
|
||||
if ($configs instanceof static) return $configs;
|
||||
return new static($configs);
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@ namespace nur\sery\db\sqlite;
|
|||
|
||||
use nur\sery\php\func;
|
||||
|
||||
class _Migration {
|
||||
static function with($migrations): _Migration {
|
||||
if ($migrations instanceof self) return $migrations;
|
||||
class _migration {
|
||||
static function with($migrations): self {
|
||||
if ($migrations instanceof static) return $migrations;
|
||||
return new static($migrations);
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
<?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);
|
||||
} 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;
|
||||
}
|
||||
|
||||
protected 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];
|
||||
}
|
||||
}
|
||||
|
||||
protected 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
class _query_create extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"table" => "string",
|
||||
"schema" => "?array",
|
||||
"cols" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
class _query_delete extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"from" => "?string",
|
||||
"where" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
class _query_insert extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"into" => "?string",
|
||||
"schema" => "?array",
|
||||
"cols" => "?array",
|
||||
"values" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(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(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;
|
||||
|
||||
## insert
|
||||
self::consume('insert\s*', $tmpsql);
|
||||
$sql[] = "insert";
|
||||
|
||||
## into
|
||||
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");
|
||||
}
|
||||
|
||||
## cols & values
|
||||
$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];
|
||||
}
|
||||
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) . ")";
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
self::check_eof($tmpsql, $usersql);
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
class _query_select extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"schema" => "?array",
|
||||
"cols" => "?array",
|
||||
"from" => "?string",
|
||||
"where" => "?array",
|
||||
"order by" => "?array",
|
||||
"group by" => "?array",
|
||||
"having" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(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(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 = [];
|
||||
|
||||
## préfixe
|
||||
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++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$userorderby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$userorderby[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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++;
|
||||
} else {
|
||||
if ($value === null) $value = false;
|
||||
if (!is_bool($value)) {
|
||||
$usergroupby[] = "$key $value";
|
||||
} elseif ($value) {
|
||||
$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);
|
||||
}
|
||||
|
||||
## suffixe
|
||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
||||
|
||||
## fin de la requête
|
||||
self::check_eof($tmpsql, $usersql);
|
||||
return implode(" ", $sql);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
namespace nur\sery\db\sqlite;
|
||||
|
||||
class _query_update extends _query {
|
||||
const SCHEMA = [
|
||||
"prefix" => "?string",
|
||||
"table" => "?string",
|
||||
"schema" => "?array",
|
||||
"cols" => "?array",
|
||||
"values" => "?array",
|
||||
"where" => "?array",
|
||||
"suffix" => "?string",
|
||||
];
|
||||
|
||||
static function isa(string $sql): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
}
|
||||
}
|
|
@ -6,79 +6,79 @@ use PHPUnit\Framework\TestCase;
|
|||
class _QueryTest extends TestCase {
|
||||
function testParseConds(): void {
|
||||
$sql = $params = null;
|
||||
_Query::parse_conds(null, $sql, $params);
|
||||
_query::parse_conds(null, $sql, $params);
|
||||
self::assertNull($sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_conds([], $sql, $params);
|
||||
_query::parse_conds([], $sql, $params);
|
||||
self::assertNull($sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_conds(["col = 'value'"], $sql, $params);
|
||||
_query::parse_conds(["col = 'value'"], $sql, $params);
|
||||
self::assertSame(["col = 'value'"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_conds([["col = 'value'"]], $sql, $params);
|
||||
_query::parse_conds([["col = 'value'"]], $sql, $params);
|
||||
self::assertSame(["col = 'value'"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_conds(["int" => 42, "string" => "value"], $sql, $params);
|
||||
_query::parse_conds(["int" => 42, "string" => "value"], $sql, $params);
|
||||
self::assertSame(["(int = :int and string = :string)"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_conds(["or", "int" => 42, "string" => "value"], $sql, $params);
|
||||
_query::parse_conds(["or", "int" => 42, "string" => "value"], $sql, $params);
|
||||
self::assertSame(["(int = :int or string = :string)"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_conds([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
|
||||
_query::parse_conds([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
|
||||
self::assertSame(["((int = :int and string = :string) and (int = :int1 and string = :string1))"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value", "int1" => 24, "string1" => "eulav"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_conds(["int" => ["is null"], "string" => ["<>", "value"]], $sql, $params);
|
||||
_query::parse_conds(["int" => ["is null"], "string" => ["<>", "value"]], $sql, $params);
|
||||
self::assertSame(["(int is null and string <> :string)"], $sql);
|
||||
self::assertSame(["string" => "value"], $params);
|
||||
}
|
||||
|
||||
function testParseValues(): void {
|
||||
$sql = $params = null;
|
||||
_Query::parse_set_values(null, $sql, $params);
|
||||
_query::parse_set_values(null, $sql, $params);
|
||||
self::assertNull($sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_set_values([], $sql, $params);
|
||||
_query::parse_set_values([], $sql, $params);
|
||||
self::assertNull($sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_set_values(["col = 'value'"], $sql, $params);
|
||||
_query::parse_set_values(["col = 'value'"], $sql, $params);
|
||||
self::assertSame(["col = 'value'"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_set_values([["col = 'value'"]], $sql, $params);
|
||||
_query::parse_set_values([["col = 'value'"]], $sql, $params);
|
||||
self::assertSame(["col = 'value'"], $sql);
|
||||
self::assertNull($params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
|
||||
_query::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
|
||||
self::assertSame(["int = :int", "string = :string"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
|
||||
_query::parse_set_values(["int" => 42, "string" => "value"], $sql, $params);
|
||||
self::assertSame(["int = :int", "string = :string"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value"], $params);
|
||||
|
||||
$sql = $params = null;
|
||||
_Query::parse_set_values([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
|
||||
_query::parse_set_values([["int" => 42, "string" => "value"], ["int" => 24, "string" => "eulav"]], $sql, $params);
|
||||
self::assertSame(["int = :int", "string = :string", "int = :int1", "string = :string1"], $sql);
|
||||
self::assertSame(["int" => 42, "string" => "value", "int1" => 24, "string1" => "eulav"], $params);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue