modifs.mineures sans commentaires
This commit is contained in:
parent
a1314b7fa4
commit
ba4a67658f
|
@ -8,7 +8,7 @@ trait Tcreate {
|
|||
return preg_match("/^create\s+table\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ trait Tdelete {
|
|||
return preg_match("/^delete(?:\s+from)?\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$tmpsql = self::merge_seq($query);
|
||||
self::consume('delete(?:\s+from)?\b', $tmpsql);
|
||||
|
@ -22,7 +22,7 @@ trait Tdelete {
|
|||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
self::parse_conds($where, $wheresql, $params);
|
||||
self::parse_conds($where, $wheresql, $bindings);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
|
|
|
@ -9,7 +9,7 @@ trait Tgeneric {
|
|||
return preg_match('/^(?:drop\s+table)\b/i', $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
if (!cl::is_list($query)) {
|
||||
throw new ValueException("Seuls les tableaux séquentiels sont supportés");
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ trait Tinsert {
|
|||
* parser une chaine de la forme
|
||||
* "insert [into] [TABLE] [(COLS)] [values (VALUES)]"
|
||||
*/
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
# fusionner d'abord toutes les parties séquentielles
|
||||
$usersql = $tmpsql = self::merge_seq($query);
|
||||
|
||||
|
@ -66,7 +66,7 @@ trait Tinsert {
|
|||
if (!$usercols) $usercols = $cols;
|
||||
foreach ($cols as $col) {
|
||||
$uservalues[] = ":$col";
|
||||
$params[$col] = $values[$col] ?? null;
|
||||
$bindings[$col] = $values[$col] ?? null;
|
||||
}
|
||||
}
|
||||
$sql[] = "(" . implode(", ", $usercols) . ")";
|
||||
|
|
|
@ -13,7 +13,7 @@ trait Tselect {
|
|||
* 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 {
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
# fusionner d'abord toutes les parties séquentielles
|
||||
$usersql = $tmpsql = self::merge_seq($query);
|
||||
|
||||
|
@ -76,7 +76,7 @@ trait Tselect {
|
|||
if ($ms[1]) $userwhere[] = $ms[1];
|
||||
}
|
||||
$where = cl::withn($query["where"] ?? null);
|
||||
if ($where !== null) self::parse_conds($where, $userwhere, $params);
|
||||
if ($where !== null) self::parse_conds($where, $userwhere, $bindings);
|
||||
if ($userwhere) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $userwhere);
|
||||
|
@ -141,7 +141,7 @@ trait Tselect {
|
|||
if ($ms[1]) $userhaving[] = $ms[1];
|
||||
}
|
||||
$having = cl::withn($query["having"] ?? null);
|
||||
if ($having !== null) self::parse_conds($having, $userhaving, $params);
|
||||
if ($having !== null) self::parse_conds($having, $userhaving, $bindings);
|
||||
if ($userhaving) {
|
||||
$sql[] = "having";
|
||||
$sql[] = implode(" and ", $userhaving);
|
||||
|
|
|
@ -6,7 +6,7 @@ trait Tupdate {
|
|||
return preg_match("/^update\b/i", $sql);
|
||||
}
|
||||
|
||||
static function parse(array $query, ?array &$params=null): string {
|
||||
static function parse(array $query, ?array &$bindings=null): string {
|
||||
#XXX implémentation minimale
|
||||
$sql = [self::merge_seq($query)];
|
||||
|
||||
|
@ -17,14 +17,14 @@ trait Tupdate {
|
|||
$sql[] = $query["table"];
|
||||
|
||||
## set
|
||||
self::parse_set_values($query["values"], $setsql, $params);
|
||||
self::parse_set_values($query["values"], $setsql, $bindings);
|
||||
$sql[] = "set";
|
||||
$sql[] = implode(", ", $setsql);
|
||||
|
||||
## where
|
||||
$where = $query["where"] ?? null;
|
||||
if ($where !== null) {
|
||||
self::parse_conds($where, $wheresql, $params);
|
||||
self::parse_conds($where, $wheresql, $bindings);
|
||||
if ($wheresql) {
|
||||
$sql[] = "where";
|
||||
$sql[] = implode(" and ", $wheresql);
|
||||
|
|
|
@ -35,7 +35,7 @@ abstract class _base {
|
|||
return true;
|
||||
}
|
||||
|
||||
static function parse_conds(?array $conds, ?array &$sql, ?array &$params): void {
|
||||
static function parse_conds(?array $conds, ?array &$sql, ?array &$bindings): void {
|
||||
if (!$conds) return;
|
||||
$sep = null;
|
||||
$index = 0;
|
||||
|
@ -49,7 +49,7 @@ abstract class _base {
|
|||
# ignorer les valeurs true et false
|
||||
} elseif (is_array($cond)) {
|
||||
# condition récursive
|
||||
self::parse_conds($cond, $condsql, $params);
|
||||
self::parse_conds($cond, $condsql, $bindings);
|
||||
} else {
|
||||
# condition litérale
|
||||
$condsql[] = strval($cond);
|
||||
|
@ -65,9 +65,9 @@ abstract class _base {
|
|||
## associatif
|
||||
# paramètre
|
||||
$i = false;
|
||||
if ($params !== null && array_key_exists($key, $params)) {
|
||||
if ($bindings !== null && array_key_exists($key, $bindings)) {
|
||||
$i = 2;
|
||||
while (array_key_exists("$key$i", $params)) {
|
||||
while (array_key_exists("$key$i", $bindings)) {
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ abstract class _base {
|
|||
foreach ($values as $value) {
|
||||
$param = "$key$i";
|
||||
$parts[] = ":$param";
|
||||
$params[$param] = $value;
|
||||
$bindings[$param] = $value;
|
||||
if ($i === false) $i = 2;
|
||||
else $i++;
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ abstract class _base {
|
|||
}
|
||||
}
|
||||
|
||||
static function parse_set_values(?array $values, ?array &$sql, ?array &$params): void {
|
||||
static function parse_set_values(?array $values, ?array &$sql, ?array &$bindings): void {
|
||||
if (!$values) return;
|
||||
$index = 0;
|
||||
$parts = [];
|
||||
|
@ -172,7 +172,7 @@ abstract class _base {
|
|||
## séquentiel
|
||||
if (is_array($part)) {
|
||||
# paramètres récursifs
|
||||
self::parse_set_values($part, $parts, $params);
|
||||
self::parse_set_values($part, $parts, $bindings);
|
||||
} else {
|
||||
# paramètre litéral
|
||||
$parts[] = strval($part);
|
||||
|
@ -182,9 +182,9 @@ abstract class _base {
|
|||
## associatif
|
||||
# paramètre
|
||||
$param = $key;
|
||||
if ($params !== null && array_key_exists($param, $params)) {
|
||||
if ($bindings !== null && array_key_exists($param, $bindings)) {
|
||||
$i = 1;
|
||||
while (array_key_exists("$key$i", $params)) {
|
||||
while (array_key_exists("$key$i", $bindings)) {
|
||||
$i++;
|
||||
}
|
||||
$param = "$key$i";
|
||||
|
@ -196,7 +196,7 @@ abstract class _base {
|
|||
$part[] = "null";
|
||||
} else {
|
||||
$part[] = ":$param";
|
||||
$params[$param] = $value;
|
||||
$bindings[$param] = $value;
|
||||
}
|
||||
$parts[] = implode(" ", $part);
|
||||
}
|
||||
|
@ -211,17 +211,21 @@ abstract class _base {
|
|||
}
|
||||
}
|
||||
|
||||
abstract protected static function verifix(&$sql, ?array &$params=null): void;
|
||||
abstract protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void;
|
||||
|
||||
function __construct($sql, ?array $params=null) {
|
||||
static::verifix($sql, $params);
|
||||
function __construct($sql, ?array $bindings=null) {
|
||||
static::verifix($sql, $bindings, $meta);
|
||||
$this->sql = $sql;
|
||||
$this->params = $params;
|
||||
$this->bindings = $bindings;
|
||||
$this->meta = $meta;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $sql;
|
||||
|
||||
/** @var ?array */
|
||||
protected $params;
|
||||
protected $bindings;
|
||||
|
||||
/** @var ?array */
|
||||
protected $meta;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
namespace nur\sery\db\mysql;
|
||||
|
||||
use nur\sery\db\pdo\Pdo;
|
||||
|
||||
class Mysql extends Pdo {
|
||||
}
|
|
@ -0,0 +1,310 @@
|
|||
<?php
|
||||
namespace nur\sery\db\mysql;
|
||||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\db\CapacitorChannel;
|
||||
use nur\sery\db\CapacitorStorage;
|
||||
use nur\sery\php\func;
|
||||
use nur\sery\str;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
/**
|
||||
* Class MysqlStorage
|
||||
*/
|
||||
class MysqlStorage extends CapacitorStorage {
|
||||
function __construct($mysql) {
|
||||
$this->mysql = Mysql::with($mysql);
|
||||
}
|
||||
|
||||
/** @var Mysql */
|
||||
protected $mysql;
|
||||
|
||||
function mysql(): Mysql {
|
||||
return $this->mysql;
|
||||
}
|
||||
|
||||
const KEY_DEFINITIONS = [
|
||||
"id_" => "integer primary key autoincrement",
|
||||
"item__" => "text",
|
||||
"sum_" => "varchar(40)",
|
||||
"created_" => "datetime",
|
||||
"modified_" => "datetime",
|
||||
];
|
||||
|
||||
/** sérialiser les valeurs qui doivent l'être dans $values */
|
||||
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
||||
if ($values === null) return null;
|
||||
$columns = cl::merge(self::KEY_DEFINITIONS, $channel->getKeyDefinitions());
|
||||
$index = 0;
|
||||
$row = [];
|
||||
foreach (array_keys($columns) as $column) {
|
||||
$key = $column;
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
continue;
|
||||
} elseif (str::del_suffix($key, "__")) {
|
||||
if (!array_key_exists($key, $values)) continue;
|
||||
$value = $values[$key];
|
||||
if ($value !== null) $value = serialize($value);
|
||||
} else {
|
||||
if (!array_key_exists($key, $values)) continue;
|
||||
$value = $values[$key];
|
||||
}
|
||||
$row[$column] = $value;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/** désérialiser les valeurs qui doivent l'être dans $values */
|
||||
protected function unserialize(CapacitorChannel $channel, ?array $row): ?array {
|
||||
if ($row === null) return null;
|
||||
$columns = cl::merge(self::KEY_DEFINITIONS, $channel->getKeyDefinitions());
|
||||
$index = 0;
|
||||
$values = [];
|
||||
foreach (array_keys($columns) as $column) {
|
||||
$key = $column;
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
continue;
|
||||
} elseif (!array_key_exists($column, $row)) {
|
||||
continue;
|
||||
} elseif (str::del_suffix($key, "__")) {
|
||||
$value = $row[$column];
|
||||
if ($value !== null) $value = unserialize($value);
|
||||
} else {
|
||||
$value = $row[$column];
|
||||
}
|
||||
$values[$key] = $value;
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
protected function _create(CapacitorChannel $channel): void {
|
||||
if (!$channel->isCreated()) {
|
||||
$columns = cl::merge(self::KEY_DEFINITIONS, $channel->getKeyDefinitions());
|
||||
$this->mysql->exec([
|
||||
"create table if not exists",
|
||||
"table" => $channel->getTableName(),
|
||||
"cols" => $columns,
|
||||
]);
|
||||
$channel->setCreated();
|
||||
}
|
||||
}
|
||||
|
||||
/** @var CapacitorChannel[] */
|
||||
protected $channels;
|
||||
|
||||
function addChannel(CapacitorChannel $channel): CapacitorChannel {
|
||||
$this->_create($channel);
|
||||
$this->channels[$channel->getName()] = $channel;
|
||||
return $channel;
|
||||
}
|
||||
|
||||
protected function getChannel(?string $name): CapacitorChannel {
|
||||
$name = CapacitorChannel::verifix_name($name);
|
||||
$channel = $this->channels[$name] ?? null;
|
||||
if ($channel === null) {
|
||||
$channel = $this->addChannel(new CapacitorChannel($name));
|
||||
}
|
||||
return $channel;
|
||||
}
|
||||
|
||||
function _exists(CapacitorChannel $channel): bool {
|
||||
$tableName = $this->mysql->get([
|
||||
"select table_name from information_schema.tables",
|
||||
"where" => [
|
||||
#"table_schema" => database_name #XXX
|
||||
"table_name" => $channel->getTableName(),
|
||||
],
|
||||
]);
|
||||
return $tableName !== null;
|
||||
}
|
||||
|
||||
function _ensureExists(CapacitorChannel $channel): void {
|
||||
$this->_create($channel);
|
||||
}
|
||||
|
||||
function _reset(CapacitorChannel $channel): void {
|
||||
$this->mysql->exec([
|
||||
"drop table if exists",
|
||||
$channel->getTableName(),
|
||||
]);
|
||||
$channel->setCreated(false);
|
||||
}
|
||||
|
||||
function _charge(CapacitorChannel $channel, $item, ?callable $func, ?array $args): int {
|
||||
$this->_create($channel);
|
||||
$now = date("Y-m-d H:i:s");
|
||||
$item__ = serialize($item);
|
||||
$sum_ = sha1($item__);
|
||||
$row = cl::merge([
|
||||
"item__" => $item__,
|
||||
"sum_" => $sum_,
|
||||
], $this->unserialize($channel, $channel->getKeyValues($item)));
|
||||
$prow = null;
|
||||
$id_ = $row["id_"] ?? null;
|
||||
if ($id_ !== null) {
|
||||
# modification
|
||||
$prow = $this->mysql->one([
|
||||
"select id_, item__, sum_, created_, modified_",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => ["id_" => $id_],
|
||||
]);
|
||||
}
|
||||
$insert = null;
|
||||
if ($prow === null) {
|
||||
# création
|
||||
$row = cl::merge($row, [
|
||||
"created_" => $now,
|
||||
"modified_" => $now,
|
||||
]);
|
||||
$insert = true;
|
||||
} elseif ($sum_ !== $prow["sum_"]) {
|
||||
# modification
|
||||
$row = cl::merge($row, [
|
||||
"modified_" => $now,
|
||||
]);
|
||||
$insert = false;
|
||||
}
|
||||
|
||||
if ($func === null) $func = [$channel, "onCharge"];
|
||||
$onCharge = func::_prepare($func);
|
||||
$args ??= [];
|
||||
$values = $this->unserialize($channel, $row);
|
||||
$pvalues = $this->unserialize($channel, $prow);
|
||||
$updates = func::_call($onCharge, [$item, $values, $pvalues, ...$args]);
|
||||
if (is_array($updates)) {
|
||||
$updates = $this->serialize($channel, $updates);
|
||||
if (array_key_exists("item__", $updates)) {
|
||||
# si item a été mis à jour, il faut mettre à jour sum_
|
||||
$updates["sum_"] = sha1($updates["item__"]);
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = $now;
|
||||
}
|
||||
}
|
||||
$row = cl::merge($row, $updates);
|
||||
}
|
||||
|
||||
if ($insert === null) {
|
||||
# aucune modification
|
||||
return 0;
|
||||
} elseif ($insert) {
|
||||
$this->mysql->exec([
|
||||
"insert",
|
||||
"into" => $channel->getTableName(),
|
||||
"values" => $row,
|
||||
]);
|
||||
} else {
|
||||
$this->mysql->exec([
|
||||
"update",
|
||||
"table" => $channel->getTableName(),
|
||||
"values" => $row,
|
||||
"where" => ["id_" => $id_],
|
||||
]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function _discharge(CapacitorChannel $channel, bool $reset=true): iterable {
|
||||
$rows = $this->mysql->all([
|
||||
"select item__",
|
||||
"from" => $channel->getTableName(),
|
||||
]);
|
||||
foreach ($rows as $row) {
|
||||
yield unserialize($row['item__']);
|
||||
}
|
||||
if ($reset) $this->_reset($channel);
|
||||
}
|
||||
|
||||
protected function verifixFilter(CapacitorChannel $channel, &$filter): void {
|
||||
if ($filter !== null && !is_array($filter)) {
|
||||
$id = $filter;
|
||||
$channel->verifixId($id);
|
||||
$filter = ["id_" => $id];
|
||||
}
|
||||
$filter = $this->serialize($channel, $filter);
|
||||
}
|
||||
|
||||
function _count(CapacitorChannel $channel, $filter): int {
|
||||
$this->verifixFilter($channel, $filter);
|
||||
return $this->mysql->get([
|
||||
"select count(*)",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
]);
|
||||
}
|
||||
|
||||
function _one(CapacitorChannel $channel, $filter): ?array {
|
||||
if ($filter === null) throw ValueException::null("filter");
|
||||
$this->verifixFilter($channel, $filter);
|
||||
$row = $this->mysql->one([
|
||||
"select",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
]);
|
||||
return $this->unserialize($channel, $row);
|
||||
}
|
||||
|
||||
function _all(CapacitorChannel $channel, $filter): iterable {
|
||||
$this->verifixFilter($channel, $filter);
|
||||
$rows = $this->mysql->all([
|
||||
"select",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
], null, "id_");
|
||||
foreach ($rows as $key => $row) {
|
||||
yield $key => $this->unserialize($channel, $row);
|
||||
}
|
||||
}
|
||||
|
||||
function _each(CapacitorChannel $channel, $filter, ?callable $func, ?array $args): int {
|
||||
if ($func === null) $func = [$channel, "onEach"];
|
||||
$onEach = func::_prepare($func);
|
||||
$mysql = $this->mysql;
|
||||
$tableName = $channel->getTableName();
|
||||
$commited = false;
|
||||
$count = 0;
|
||||
$mysql->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
try {
|
||||
$args ??= [];
|
||||
foreach ($this->_all($channel, $filter) as $row) {
|
||||
$updates = func::_call($onEach, [$row["item"], $row, ...$args]);
|
||||
if (is_array($updates)) {
|
||||
$updates = $this->serialize($channel, $updates);
|
||||
if (array_key_exists("item__", $updates)) {
|
||||
# si item a été mis à jour, il faut mettre à jour sum_
|
||||
$updates["sum_"] = sha1($updates["item__"]);
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||
}
|
||||
}
|
||||
$mysql->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $updates,
|
||||
"where" => ["id_" => $row["id_"]],
|
||||
]);
|
||||
if ($commitThreshold !== null) {
|
||||
$commitThreshold--;
|
||||
if ($commitThreshold == 0) {
|
||||
$mysql->commit();
|
||||
$mysql->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
}
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
$mysql->commit();
|
||||
$commited = true;
|
||||
return $count;
|
||||
} finally {
|
||||
if (!$commited) $mysql->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->mysql->close();
|
||||
}
|
||||
}
|
|
@ -1,32 +1,52 @@
|
|||
<?php
|
||||
namespace nur\sery\db\mysql;
|
||||
|
||||
use nur\sery\db\_private\_base;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
class _query_base extends _base {
|
||||
protected static function verifix(&$sql, ?array &$params=null): void {
|
||||
class _query_base extends \nur\sery\db\pdo\_query_base {
|
||||
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
if ($prefix === null) {
|
||||
throw new ValueException("requête invalide");
|
||||
} elseif (_query_create::isa($prefix)) {
|
||||
$sql = _query_create::parse($sql, $params);
|
||||
$sql = _query_create::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_query_select::isa($prefix)) {
|
||||
$sql = _query_select::parse($sql, $params);
|
||||
$sql = _query_select::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_query_insert::isa($prefix)) {
|
||||
$sql = _query_insert::parse($sql, $params);
|
||||
$sql = _query_insert::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_query_update::isa($prefix)) {
|
||||
$sql = _query_update::parse($sql, $params);
|
||||
$sql = _query_update::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_query_delete::isa($prefix)) {
|
||||
$sql = _query_delete::parse($sql, $params);
|
||||
$sql = _query_delete::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_query_generic::isa($prefix)) {
|
||||
$sql = _query_generic::parse($sql, $params);
|
||||
$sql = _query_generic::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
throw ValueException::invalid_kind($sql, "query");
|
||||
}
|
||||
} elseif (!is_string($sql)) {
|
||||
$sql = strval($sql);
|
||||
} else {
|
||||
if (!is_string($sql)) $sql = strval($sql);
|
||||
if (_query_create::isa($sql)) {
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_query_select::isa($sql)) {
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_query_insert::isa($sql)) {
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_query_update::isa($sql)) {
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_query_delete::isa($sql)) {
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_query_generic::isa($sql)) {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
namespace nur\sery\db\pdo;
|
||||
|
||||
use Generator;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\php\func;
|
||||
use nur\sery\php\time\Date;
|
||||
use nur\sery\php\time\DateTime;
|
||||
|
||||
class Pdo {
|
||||
static function with($pdo, ?array $params=null): self {
|
||||
if ($pdo instanceof static) {
|
||||
return $pdo;
|
||||
} elseif ($pdo instanceof self) {
|
||||
# recréer avec les mêmes paramètres
|
||||
return new static(null, cl::merge([
|
||||
"dbconn" => $pdo->dbconn,
|
||||
"options" => $pdo->options,
|
||||
"config" => $pdo->config,
|
||||
"migrate" => $pdo->migration,
|
||||
], $params));
|
||||
} elseif (is_array($pdo)) {
|
||||
return new static(null, cl::merge($pdo, $params));
|
||||
} else {
|
||||
return new static($pdo, $params);
|
||||
}
|
||||
}
|
||||
|
||||
static function config_errmodeException_lowerCase(self $pdo) {
|
||||
$pdo->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->db->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER);
|
||||
}
|
||||
|
||||
const OPTIONS = [
|
||||
\PDO::ATTR_PERSISTENT => true,
|
||||
];
|
||||
|
||||
const CONFIG = [
|
||||
[self::class, "config_errmodeException_lowerCase"],
|
||||
];
|
||||
|
||||
const MIGRATE = null;
|
||||
|
||||
const dbconn_SCHEMA = [
|
||||
"name" => "string",
|
||||
"user" => "?string",
|
||||
"pass" => "?string",
|
||||
];
|
||||
|
||||
const params_SCHEMA = [
|
||||
"dbconn" => ["array"],
|
||||
"options" => ["?array|callable"],
|
||||
"config" => ["?array|callable"],
|
||||
"migrate" => ["?array|string|callable"],
|
||||
"auto_open" => ["bool", true],
|
||||
];
|
||||
|
||||
function __construct($dbconn=null, ?array $params=null) {
|
||||
if ($dbconn !== null) {
|
||||
if (!is_array($dbconn)) $dbconn = ["name" => $dbconn];
|
||||
$params["dbconn"] = $dbconn;
|
||||
}
|
||||
# dbconn
|
||||
$this->dbconn = $params["dbconn"] ?? null;
|
||||
$this->dbconn["name"] ??= null;
|
||||
$this->dbconn["user"] ??= null;
|
||||
$this->dbconn["pass"] ??= null;
|
||||
# options
|
||||
$this->options = $params["options"] ?? static::OPTIONS;
|
||||
# configuration
|
||||
$this->config = $params["config"] ?? static::CONFIG;
|
||||
# migrations
|
||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
||||
#
|
||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||
$this->open();
|
||||
}
|
||||
}
|
||||
|
||||
protected array $dbconn;
|
||||
|
||||
/** @var array|callable */
|
||||
protected array $options;
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $config;
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $migration;
|
||||
|
||||
protected ?\PDO $db = null;
|
||||
|
||||
function open(): self {
|
||||
if ($this->db === null) {
|
||||
$dbconn = $this->dbconn;
|
||||
$options = $this->options;
|
||||
if (is_callable($options)) {
|
||||
func::ensure_func($options, $this, $args);
|
||||
$options = func::call($options, ...$args);
|
||||
}
|
||||
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
||||
_config::with($this->config)->configure($this);
|
||||
//_migration::with($this->migration)->migrate($this);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->db = null;
|
||||
}
|
||||
|
||||
protected function db(): \PDO {
|
||||
$this->open();
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
/** @return int|false */
|
||||
function _exec(string $query) {
|
||||
return $this->db()->exec($query);
|
||||
}
|
||||
|
||||
/** @return int|false */
|
||||
function exec($query, ?array $params=null) {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
if ($stmt->execute() === false) return false;
|
||||
if ($query->isInsert()) return $db->lastInsertId();
|
||||
else return $stmt->rowCount();
|
||||
} else {
|
||||
return $db->exec($sql);
|
||||
}
|
||||
}
|
||||
|
||||
function beginTransaction(): void {
|
||||
$this->db()->beginTransaction();
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
$this->db()->commit();
|
||||
}
|
||||
|
||||
function rollback(): void {
|
||||
$this->db()->rollBack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tester si $date est une date/heure valide de la forme "YYYY-mm-dd HH:MM:SS"
|
||||
*
|
||||
* Si oui, $ms obtient les 6 éléments de la chaine
|
||||
*/
|
||||
static function is_datetime($date, ?array &$ms=null): bool {
|
||||
return is_string($date) && preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $date, $ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tester si $date est une date valide de la forme "YYYY-mm-dd [00:00:00]"
|
||||
*
|
||||
* Si oui, $ms obtient les 3 éléments de la chaine
|
||||
*/
|
||||
static function is_date($date, ?array &$ms=null): bool {
|
||||
return is_string($date) && preg_match('/^(\d{4})-(\d{2})-(\d{2})(?: 00:00:00)?$/', $date, $ms);
|
||||
}
|
||||
|
||||
function verifixRow(array &$row) {
|
||||
foreach ($row as &$value) {
|
||||
if (self::is_date($value)) {
|
||||
$value = new Date($value);
|
||||
} elseif (self::is_date($value)) {
|
||||
$value = new DateTime($value);
|
||||
}
|
||||
}; unset($value);
|
||||
}
|
||||
|
||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
$stmt = null;
|
||||
try {
|
||||
/** @var \PDOStatement $stmt */
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
if ($stmt->execute() === false) return null;
|
||||
} else {
|
||||
$stmt = $db->query($sql);
|
||||
}
|
||||
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
if ($row === false) return null;
|
||||
$this->verifixRow($row);
|
||||
if ($entireRow) return $row;
|
||||
else return cl::first($row);
|
||||
} finally {
|
||||
if ($stmt instanceof \PDOStatement) $stmt->closeCursor();
|
||||
}
|
||||
}
|
||||
|
||||
function one($query, ?array $params=null): ?array {
|
||||
return $this->get($query, $params, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* si $primaryKeys est fourni, le résultat est indexé sur la(es) colonne(s)
|
||||
* spécifiée(s)
|
||||
*/
|
||||
function all($query, ?array $params=null, $primaryKeys=null): Generator {
|
||||
$db = $this->db();
|
||||
$query = new _query_base($query, $params);
|
||||
$stmt = null;
|
||||
try {
|
||||
/** @var \PDOStatement $stmt */
|
||||
if ($query->useStmt($db, $stmt, $sql)) {
|
||||
if ($stmt->execute() === false) return;
|
||||
} else {
|
||||
$stmt = $db->query($sql);
|
||||
}
|
||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
||||
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
||||
$this->verifixRow($row);
|
||||
if ($primaryKeys !== null) {
|
||||
$key = implode("-", cl::select($row, $primaryKeys));
|
||||
yield $key => $row;
|
||||
} else {
|
||||
yield $row;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if ($stmt instanceof \PDOStatement) $stmt->closeCursor();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\db\pdo;
|
||||
namespace nur\sery\db\pdo;
|
||||
|
||||
use nur\sery\php\func;
|
||||
|
|
@ -1,32 +1,101 @@
|
|||
<?php
|
||||
namespace nur\sery\db\pdo;
|
||||
|
||||
use DateTimeInterface;
|
||||
use nur\sery\db\_private\_base;
|
||||
use nur\sery\db\sqlite\SqliteException;
|
||||
use nur\sery\php\time\Date;
|
||||
use nur\sery\php\time\DateTime;
|
||||
use nur\sery\str;
|
||||
use nur\sery\ValueException;
|
||||
|
||||
class _query_base extends _base {
|
||||
protected static function verifix(&$sql, ?array &$params=null): void {
|
||||
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
if ($prefix === null) {
|
||||
throw new ValueException("requête invalide");
|
||||
} elseif (_query_create::isa($prefix)) {
|
||||
$sql = _query_create::parse($sql, $params);
|
||||
$sql = _query_create::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_query_select::isa($prefix)) {
|
||||
$sql = _query_select::parse($sql, $params);
|
||||
$sql = _query_select::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_query_insert::isa($prefix)) {
|
||||
$sql = _query_insert::parse($sql, $params);
|
||||
$sql = _query_insert::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_query_update::isa($prefix)) {
|
||||
$sql = _query_update::parse($sql, $params);
|
||||
$sql = _query_update::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_query_delete::isa($prefix)) {
|
||||
$sql = _query_delete::parse($sql, $params);
|
||||
$sql = _query_delete::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_query_generic::isa($prefix)) {
|
||||
$sql = _query_generic::parse($sql, $params);
|
||||
$sql = _query_generic::parse($sql, $bindinds);
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
throw ValueException::invalid_kind($sql, "query");
|
||||
}
|
||||
} elseif (!is_string($sql)) {
|
||||
$sql = strval($sql);
|
||||
} else {
|
||||
if (!is_string($sql)) $sql = strval($sql);
|
||||
if (_query_create::isa($sql)) {
|
||||
$meta = ["isa" => "create", "type" => "ddl"];
|
||||
} elseif (_query_select::isa($sql)) {
|
||||
$meta = ["isa" => "select", "type" => "dql"];
|
||||
} elseif (_query_insert::isa($sql)) {
|
||||
$meta = ["isa" => "insert", "type" => "dml"];
|
||||
} elseif (_query_update::isa($sql)) {
|
||||
$meta = ["isa" => "update", "type" => "dml"];
|
||||
} elseif (_query_delete::isa($sql)) {
|
||||
$meta = ["isa" => "delete", "type" => "dml"];
|
||||
} elseif (_query_generic::isa($sql)) {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
} else {
|
||||
$meta = ["isa" => "generic", "type" => null];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function is_sqldate(string $date): bool {
|
||||
return preg_match('/^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2})?$/', $date);
|
||||
}
|
||||
|
||||
protected function verifixBindings(&$value): void {
|
||||
if ($value instanceof Date) {
|
||||
$value = $value->format('Y-m-d');
|
||||
} elseif ($value instanceof DateTime) {
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
} elseif ($value instanceof DateTimeInterface) {
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
str::del_suffix($value, " 00:00:00");
|
||||
} elseif (is_string($value)) {
|
||||
if (self::is_sqldate($value)) {
|
||||
# déjà dans le bon format
|
||||
} elseif (Date::isa_date($value)) {
|
||||
$value = new Date($value);
|
||||
$value = $value->format('Y-m-d');
|
||||
} elseif (DateTime::isa_datetime($value)) {
|
||||
$value = new DateTime($value);
|
||||
$value = $value->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useStmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
|
||||
if ($this->bindings !== null) {
|
||||
$stmt = $db->prepare($this->sql);
|
||||
foreach ($this->bindings as $name => $value) {
|
||||
$this->verifixBindings($value);
|
||||
$stmt->bindValue($name, $value);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
$sql = $this->sql;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isInsert(): bool {
|
||||
return ($this->meta["isa"] ?? null) === "insert";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,23 +7,23 @@ use SQLite3;
|
|||
use SQLite3Stmt;
|
||||
|
||||
class _query_base extends _base {
|
||||
protected static function verifix(&$sql, ?array &$params=null): void {
|
||||
protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void {
|
||||
if (is_array($sql)) {
|
||||
$prefix = $sql[0] ?? null;
|
||||
if ($prefix === null) {
|
||||
throw new ValueException("requête invalide");
|
||||
} elseif (_query_create::isa($prefix)) {
|
||||
$sql = _query_create::parse($sql, $params);
|
||||
$sql = _query_create::parse($sql, $bindinds);
|
||||
} elseif (_query_select::isa($prefix)) {
|
||||
$sql = _query_select::parse($sql, $params);
|
||||
$sql = _query_select::parse($sql, $bindinds);
|
||||
} elseif (_query_insert::isa($prefix)) {
|
||||
$sql = _query_insert::parse($sql, $params);
|
||||
$sql = _query_insert::parse($sql, $bindinds);
|
||||
} elseif (_query_update::isa($prefix)) {
|
||||
$sql = _query_update::parse($sql, $params);
|
||||
$sql = _query_update::parse($sql, $bindinds);
|
||||
} elseif (_query_delete::isa($prefix)) {
|
||||
$sql = _query_delete::parse($sql, $params);
|
||||
$sql = _query_delete::parse($sql, $bindinds);
|
||||
} elseif (_query_generic::isa($prefix)) {
|
||||
$sql = _query_generic::parse($sql, $params);
|
||||
$sql = _query_generic::parse($sql, $bindinds);
|
||||
} else {
|
||||
throw SqliteException::wrap(ValueException::invalid_kind($sql, "query"));
|
||||
}
|
||||
|
@ -33,12 +33,12 @@ class _query_base extends _base {
|
|||
}
|
||||
|
||||
function useStmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
|
||||
if ($this->params !== null) {
|
||||
if ($this->bindings !== null) {
|
||||
/** @var SQLite3Stmt $stmt */
|
||||
$stmt = SqliteException::check($db, $db->prepare($this->sql));
|
||||
$close = true;
|
||||
try {
|
||||
foreach ($this->params as $param => $value) {
|
||||
foreach ($this->bindings as $param => $value) {
|
||||
SqliteException::check($db, $stmt->bindValue($param, $value));
|
||||
}
|
||||
$close = false;
|
||||
|
|
|
@ -34,6 +34,29 @@ class DateTime extends \DateTime {
|
|||
const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/';
|
||||
const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})[tT](\d{2})(\d{2})(\d{2})?([zZ])?$/';
|
||||
|
||||
static function isa($datetime): bool {
|
||||
if ($datetime instanceof DateTimeInterface) return true;
|
||||
if (!is_int($datetime) && !is_string($datetime)) return false;
|
||||
return preg_match(self::DMY_PATTERN, $datetime) ||
|
||||
preg_match(self::YMD_PATTERN, $datetime) ||
|
||||
preg_match(self::DMYHIS_PATTERN, $datetime) ||
|
||||
preg_match(self::YMDHISZ_PATTERN, $datetime);
|
||||
}
|
||||
|
||||
static function isa_datetime($datetime): bool {
|
||||
if ($datetime instanceof DateTimeInterface) return true;
|
||||
if (!is_int($datetime) && !is_string($datetime)) return false;
|
||||
return preg_match(self::DMYHIS_PATTERN, $datetime) ||
|
||||
preg_match(self::YMDHISZ_PATTERN, $datetime);
|
||||
}
|
||||
|
||||
static function isa_date($datetime): bool {
|
||||
if ($datetime instanceof DateTimeInterface) return true;
|
||||
if (!is_int($datetime) && !is_string($datetime)) return false;
|
||||
return preg_match(self::DMY_PATTERN, $datetime) ||
|
||||
preg_match(self::YMD_PATTERN, $datetime);
|
||||
}
|
||||
|
||||
static function _YmdHMSZ_format(\DateTime $datetime): string {
|
||||
$YmdHMS = $datetime->format("Ymd\\THis");
|
||||
$Z = $datetime->format("P");
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
<?php
|
||||
namespace nur\sery\wip\db\pdo;
|
||||
|
||||
use nur\sery\cl;
|
||||
|
||||
class Pdo {
|
||||
static function config_errmodeException_lowerCase(self $pdo) {
|
||||
$pdo->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->db->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER);
|
||||
}
|
||||
|
||||
const OPTIONS = [
|
||||
\PDO::ATTR_PERSISTENT => true,
|
||||
];
|
||||
|
||||
const CONFIG = [
|
||||
[self::class, "config_errmodeException_lowerCase"],
|
||||
];
|
||||
|
||||
const MIGRATE = null;
|
||||
|
||||
const dbconn_SCHEMA = [
|
||||
"name" => "string",
|
||||
"user" => "?string",
|
||||
"pass" => "?string",
|
||||
];
|
||||
|
||||
const params_SCHEMA = [
|
||||
"options" => ["?array"],
|
||||
"config" => ["?array|callable"],
|
||||
"migrate" => ["?array|string|callable"],
|
||||
"auto_open" => ["bool", true],
|
||||
];
|
||||
|
||||
function __construct($dbconn, ?array $params=null) {
|
||||
if (!is_array($dbconn)) $dbconn = ["name" => $dbconn];
|
||||
$this->dbname = $dbconn["name"];
|
||||
$this->dbuser = $dbconn["user"] ?? null;
|
||||
$this->dbpass = $dbconn["pass"] ?? null;
|
||||
# options
|
||||
$this->options = cl::with($params["options"] ?? static::OPTIONS);
|
||||
# configuration
|
||||
$this->config = $params["config"] ?? static::CONFIG;
|
||||
# migrations
|
||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
||||
#
|
||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||
$this->open();
|
||||
}
|
||||
}
|
||||
|
||||
protected ?string $dbconn;
|
||||
|
||||
protected ?string $dbuser;
|
||||
|
||||
protected ?string $dbpass;
|
||||
|
||||
protected array $options;
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $config;
|
||||
|
||||
/** @var array|string|callable */
|
||||
protected $migration;
|
||||
|
||||
protected ?\PDO $db;
|
||||
|
||||
function open(): self {
|
||||
if ($this->db === null) {
|
||||
$this->db = new \PDO($this->dbname, $this->dbuser, $this->dbpass, $this->options);
|
||||
_config::with($this->config)->configure($this);
|
||||
_migration::with($this->migration)->migrate($this);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->db = null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue