implémentation de base de select

This commit is contained in:
Jephté Clain 2024-05-09 11:33:53 +04:00
parent ea2449b766
commit 5e760ce71b
5 changed files with 269 additions and 16 deletions

View File

@ -1,5 +0,0 @@
<?php
namespace nur\sery\db;
class filters {
}

View File

@ -141,6 +141,10 @@ class Sqlite {
} }
} }
function one($query, ?array $params=null): ?array {
return $this->get($query, $params, true);
}
protected function _fetchResult(SQLite3Result $result): Generator { protected function _fetchResult(SQLite3Result $result): Generator {
try { try {
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) { while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {

View File

@ -53,6 +53,120 @@ class _Query {
return $sql; 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_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_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 = [ const create_SCHEMA = [
"prefix" => "string", "prefix" => "string",
"table" => "string", "table" => "string",
@ -77,7 +191,7 @@ class _Query {
"having" => "?array", "having" => "?array",
]; ];
static function is_select(string $sql): bool { static function is_select(string $sql): bool {
return false; return preg_match("/^select\b/i", $sql);
} }
/** /**
@ -87,14 +201,14 @@ class _Query {
static function parse_select(array $query, ?array &$params=null): string { static function parse_select(array $query, ?array &$params=null): string {
# fusionner d'abord toutes les parties séquentielles # fusionner d'abord toutes les parties séquentielles
$usersql = $tmpsql = self::merge_seq($query); $usersql = $tmpsql = self::merge_seq($query);
## vérifier la présence des parties nécessaires ### vérifier la présence des parties nécessaires
$sql = []; $sql = [];
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix; if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
# select ## select
self::consume('select\s*', $tmpsql); $sql[] = "select"; self::consume('select\s*', $tmpsql); $sql[] = "select";
# cols ## cols
$usercols = []; $usercols = [];
if (self::consume('(.*)\s*(?<=$|\bfrom\b)', $tmpsql, $ms)) { if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
if ($ms[1]) $usercols[] = $ms[1]; if ($ms[1]) $usercols[] = $ms[1];
} }
$tmpcols = cl::withn($query["cols"] ?? null); $tmpcols = cl::withn($query["cols"] ?? null);
@ -119,24 +233,31 @@ class _Query {
$usercols = array_merge($usercols, $cols); $usercols = array_merge($usercols, $cols);
} }
} }
if (!$usercols && !$cols) $usercols = ["*"];
$sql[] = implode(" ", $usercols); $sql[] = implode(" ", $usercols);
# from ## from
$from = $query["from"] ?? null; $from = $query["from"] ?? null;
if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?<=;?\s*$|\bwhere\b)', $tmpsql, $ms)) { if (self::consume('from\s+([a-z_][a-z0-9_]*)\s*(?=;?\s*$|\bwhere\b)', $tmpsql, $ms)) {
if ($from === null) $from = $ms[1]; if ($from === null) $from = $ms[1];
$sql[] = "from";
$sql[] = $from; $sql[] = $from;
} elseif ($from !== null) { } elseif ($from !== null) {
$sql[] = "from";
$sql[] = $from; $sql[] = $from;
} else { } else {
throw new ValueException("expected table name: $usersql"); throw new ValueException("expected table name: $usersql");
} }
# where ## where
$userwhere = []; $userwhere = [];
if (self::consume('where\s*(.*)(?<=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) { if (self::consume('where\s*(.*?)(?=;?\s*$|\border\s+by\b)', $tmpsql, $ms)) {
if ($ms[1]) $userwhere[] = $ms[1]; if ($ms[1]) $userwhere[] = $ms[1];
} }
$where = cl::withn($query["where"] ?? null); $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 # order by
# group by # group by
# having # having

View File

@ -3,7 +3,6 @@ namespace nur\sery\db\sqlite;
use Exception; use Exception;
use nulib\tests\TestCase; use nulib\tests\TestCase;
use nur\sery\ValueException;
class SqliteTest extends TestCase { class SqliteTest extends TestCase {
const CREATE_PERSON = "create table person(nom varchar, prenom varchar, age integer)"; const CREATE_PERSON = "create table person(nom varchar, prenom varchar, age integer)";
@ -70,4 +69,52 @@ class SqliteTest extends TestCase {
["i" => 10, "s" => null/*"dix"*/], ["i" => 10, "s" => null/*"dix"*/],
], iterator_to_array($sqlite->all("select * from mapping"))); ], iterator_to_array($sqlite->all("select * from mapping")));
} }
function testSelect() {
$sqlite = new Sqlite(":memory:", [
"migrate" => "create table user (name varchar, amount integer)",
]);
$sqlite->exec(["insert into user", "values" => ["name" => "jclain1", "amount" => 1]]);
$sqlite->exec(["insert into user", "values" => ["name" => "jclain2", "amount" => 2]]);
$sqlite->exec(["insert into user", "values" => ["name" => "jclain5", "amount" => 5]]);
$sqlite->exec(["insert into user", "values" => ["name" => "fclain7", "amount" => 7]]);
$sqlite->exec(["insert into user", "values" => ["name" => "fclain9", "amount" => 9]]);
$sqlite->exec(["insert into user", "values" => ["name" => "fclain10", "amount" => 10]]);
self::assertSame([
"name" => "jclain1",
"amount" => 1,
], $sqlite->one("select * from user where name = 'jclain1'"));
self::assertSame([
"name" => "jclain1",
"amount" => 1,
], $sqlite->one(["select * from user where name = 'jclain1'"]));
self::assertSame([
"name" => "jclain1",
"amount" => 1,
], $sqlite->one(["select from user where name = 'jclain1'"]));
self::assertSame([
"name" => "jclain1",
"amount" => 1,
], $sqlite->one(["select from user", "where" => ["name = 'jclain1'"]]));
self::assertSame([
"name" => "jclain1",
"amount" => 1,
], $sqlite->one(["select", "from" => "user", "where" => ["name = 'jclain1'"]]));
self::assertSame([
"name" => "jclain1",
"amount" => 1,
], $sqlite->one(["select", "from" => "user", "where" => ["name" => "jclain1"]]));
self::assertSame([
"name" => "jclain1",
], $sqlite->one(["select name", "from" => "user", "where" => ["name" => "jclain1"]]));
self::assertSame([
"name" => "jclain1",
], $sqlite->one(["select", "cols" => "name", "from" => "user", "where" => ["name" => "jclain1"]]));
self::assertSame([
"name" => "jclain1",
], $sqlite->one(["select", "cols" => ["name"], "from" => "user", "where" => ["name" => "jclain1"]]));
self::assertSame([
"plouf" => "jclain1",
], $sqlite->one(["select", "cols" => ["plouf" => "name"], "from" => "user", "where" => ["name" => "jclain1"]]));
}
} }

View File

@ -0,0 +1,86 @@
<?php
namespace nur\sery\db\sqlite;
use PHPUnit\Framework\TestCase;
class _QueryTest extends TestCase {
function testParseConds(): void {
$sql = $params = null;
_Query::parse_conds(null, $sql, $params);
self::assertNull($sql);
self::assertNull($params);
$sql = $params = null;
_Query::parse_conds([], $sql, $params);
self::assertNull($sql);
self::assertNull($params);
$sql = $params = null;
_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);
self::assertSame(["col = 'value'"], $sql);
self::assertNull($params);
$sql = $params = null;
_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);
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);
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);
self::assertSame(["(int is null and string <> :string)"], $sql);
self::assertSame(["string" => "value"], $params);
}
function testParseValues(): void {
$sql = $params = null;
_Query::parse_values(null, $sql, $params);
self::assertNull($sql);
self::assertNull($params);
$sql = $params = null;
_Query::parse_values([], $sql, $params);
self::assertNull($sql);
self::assertNull($params);
$sql = $params = null;
_Query::parse_values(["col = 'value'"], $sql, $params);
self::assertSame(["col = 'value'"], $sql);
self::assertNull($params);
$sql = $params = null;
_Query::parse_values([["col = 'value'"]], $sql, $params);
self::assertSame(["col = 'value'"], $sql);
self::assertNull($params);
$sql = $params = null;
_Query::parse_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_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_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);
}
}