Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
ec978b9d00 | |||
3aec24ddb3 | |||
c5b9812c82 | |||
43f84843ae | |||
17fa5f9db5 | |||
599475043c | |||
d25fd92ffd | |||
b1fc572fd8 | |||
43c53b286a | |||
1f68c4bac4 | |||
687493fa1e | |||
c8dcc6fe27 | |||
de34b16ffd | |||
c2b1d23d36 | |||
23358180ac | |||
d274a6528a | |||
2e026daeda | |||
8e7e59cc42 | |||
dd3006e05d | |||
bbbe101918 | |||
b8a3d7ae77 | |||
c78196450e | |||
a587f99e42 | |||
00effe4ee8 | |||
50aedea0e6 | |||
cc56dc9834 | |||
a418a87ac9 | |||
0a73ba371f | |||
24ecb913dc | |||
aef0533d9b | |||
87e262cfe2 | |||
aabdb3f765 | |||
a371a68ee2 | |||
d70612261d | |||
1671354fd8 | |||
714baef2fe | |||
045f97eb96 | |||
3ee92ef338 | |||
737e9d17b6 | |||
37354525ec | |||
9767028da6 | |||
299b90c85e | |||
cae38dae95 | |||
b6cc62e010 | |||
b4e28ade02 | |||
d63a0cb704 | |||
91beea9c29 | |||
0e9be5f221 | |||
5e141b575e | |||
8ee13a85c0 | |||
2af417a193 | |||
d4cc8bfa42 | |||
ca129dfda4 | |||
2a50167241 | |||
146461a184 | |||
2a46c12e08 | |||
d241ce6561 | |||
86136e75a5 | |||
64c872cf3f | |||
ebbd9e06c0 | |||
5c6d55ed46 | |||
bab9ba81fe | |||
ecd01777c1 | |||
bd1f901b70 | |||
1536e091fb | |||
a8d55d329a | |||
60ab13ff84 | |||
f2614385fe |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
/test_*
|
||||||
|
|
||||||
/.composer.lock.runphp
|
/.composer.lock.runphp
|
||||||
|
|
||||||
.~lock*#
|
.~lock*#
|
||||||
|
35
CHANGES.md
35
CHANGES.md
@ -1,3 +1,38 @@
|
|||||||
|
## Release 0.5.1p82 du 12/05/2025-15:31
|
||||||
|
|
||||||
|
## Release 0.5.1p74 du 12/05/2025-15:28
|
||||||
|
|
||||||
|
* `d274a65` améliorer status
|
||||||
|
* `2e026da` l'option -ww affiche la différence
|
||||||
|
* `8e7e59c` intégrer les méthodes de Cursor et KeyAccess
|
||||||
|
* `a587f99` installer completion pour pman
|
||||||
|
* `cc56dc9` renommer pdev en pmer
|
||||||
|
* `0a73ba3` améliorer ergonomie de p
|
||||||
|
* `aef0533` ajout str::split
|
||||||
|
* `87e262c` ajout cl::delv
|
||||||
|
* `a371a68` maj doc
|
||||||
|
* `d706122` ajout infos release
|
||||||
|
|
||||||
|
## Release 0.5.0p82 du 30/04/2025-04:33
|
||||||
|
|
||||||
|
## Release 0.5.0p74 du 30/04/2025-04:31
|
||||||
|
|
||||||
|
* `3ee92ef` ajout str::replace
|
||||||
|
* `3735452` améliorer le support des migrations dans les canaux
|
||||||
|
* `9767028` pas de rebind par défaut
|
||||||
|
* `cae38da` auto-migration des canaux
|
||||||
|
* `b6cc62e` ajouter les méthodes déléguées pour Capacitor
|
||||||
|
* `5e141b5` pman: ajout des clés match_require et match_require-dev
|
||||||
|
* `d4cc8bf` config pman composer
|
||||||
|
* `d241ce6` ajout PgsqlStorage
|
||||||
|
* `5c6d55e` maj ordre func
|
||||||
|
* `bab9ba8` début pgsql
|
||||||
|
* `ecd0177` migration de nur_func à func
|
||||||
|
* `bd1f901` réorganiser le code de génération sql
|
||||||
|
* `1536e09` améliorations func
|
||||||
|
|
||||||
|
## Release 0.4.1p82 du 25/03/2025-08:47
|
||||||
|
|
||||||
## Release 0.4.1p74 du 25/03/2025-08:47
|
## Release 0.4.1p74 du 25/03/2025-08:47
|
||||||
|
|
||||||
* `5beb5e6` corriger la prise en compte du proxy
|
* `5beb5e6` corriger la prise en compte du proxy
|
||||||
|
31
README.md
Normal file
31
README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# nulib
|
||||||
|
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
Exemple: release de la version 0.6.0
|
||||||
|
~~~sh
|
||||||
|
version=0.6.0
|
||||||
|
|
||||||
|
## branche dev74
|
||||||
|
git checkout dev74
|
||||||
|
|
||||||
|
prel -v$version
|
||||||
|
|
||||||
|
_merge82
|
||||||
|
|
||||||
|
## branche dev82
|
||||||
|
git checkout dev82
|
||||||
|
|
||||||
|
prel -C
|
||||||
|
|
||||||
|
commit="$(git log --grep="Init changelog . version ${version}p82" --format=%H)" &&
|
||||||
|
echo "commit=$commit"
|
||||||
|
|
||||||
|
git checkout dev74
|
||||||
|
|
||||||
|
git cherry-pick "$commit"
|
||||||
|
pp -a
|
||||||
|
~~~
|
||||||
|
|
||||||
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
@ -1 +1 @@
|
|||||||
0.4.1
|
0.5.1
|
||||||
|
@ -84,6 +84,12 @@ function _list_commits() {
|
|||||||
_filter_rel
|
_filter_rel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _show_diff() {
|
||||||
|
local source="${1:-$SrcBranch}" dest="${2:-$DestBranch}" mergebase
|
||||||
|
setx mergebase=git merge-base "$dest" "$source"
|
||||||
|
git diff ${_sd_COLOR:+--color=$_sd_COLOR} "$mergebase..$source"
|
||||||
|
}
|
||||||
|
|
||||||
function _scripte() {
|
function _scripte() {
|
||||||
echo >>"$script"
|
echo >>"$script"
|
||||||
echo "$comment$(qvals "$@")" >>"$script"
|
echo "$comment$(qvals "$@")" >>"$script"
|
||||||
@ -420,7 +426,7 @@ EOF
|
|||||||
$(qvals echo "$(awk <"$changelog" '
|
$(qvals echo "$(awk <"$changelog" '
|
||||||
BEGIN { p = 0 }
|
BEGIN { p = 0 }
|
||||||
p == 0 && $0 == "" { p = 1; next }
|
p == 0 && $0 == "" { p = 1; next }
|
||||||
p == 1 { gsub(/\$/, "\\$", $0); print }
|
p == 1 { print }
|
||||||
')") >CHANGES.md
|
')") >CHANGES.md
|
||||||
git add CHANGES.md
|
git add CHANGES.md
|
||||||
EOF
|
EOF
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
|
|
||||||
exec "$(dirname -- "$0")/pdev" --tech-merge -Bdev82 dev74 -a "git checkout dev74" "$@"
|
exec "$(dirname -- "$0")/pmer" --tech-merge -Bdev82 dev74 -a "git checkout dev74" "$@"
|
||||||
|
10
bin/p
10
bin/p
@ -29,6 +29,9 @@ function git_statuses() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# sans arguments, il y a un comportement spécial
|
||||||
|
[ $# -eq 0 ] && NoArgs=1 || NoArgs=
|
||||||
|
|
||||||
chdir=
|
chdir=
|
||||||
all=
|
all=
|
||||||
composer=
|
composer=
|
||||||
@ -43,6 +46,13 @@ Si l'option -a est utilisée, ce script accepte comme arguments une liste de pat
|
|||||||
)
|
)
|
||||||
parse_args "$@"; set -- "${args[@]}"
|
parse_args "$@"; set -- "${args[@]}"
|
||||||
|
|
||||||
|
if [ -n "$NoArgs" ]; then
|
||||||
|
# si aucun argument n'est spécifié et si on n'est pas dans un projet git,
|
||||||
|
# afficher le status de tous les sous répertoires
|
||||||
|
setx toplevel=git_get_toplevel
|
||||||
|
[ -z "$toplevel" ] && all=1
|
||||||
|
fi
|
||||||
|
|
||||||
setx OrigCwd=pwd
|
setx OrigCwd=pwd
|
||||||
if [ -n "$chdir" ]; then
|
if [ -n "$chdir" ]; then
|
||||||
cd "$chdir" || die
|
cd "$chdir" || die
|
||||||
|
61
bin/pman
61
bin/pman
@ -78,7 +78,7 @@ function init_repo_action() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init_config_action() {
|
function init_config_action() {
|
||||||
local -a push_branches; config
|
local -a push_branches; local config
|
||||||
|
|
||||||
[ -f .pman.conf -a -z "$ForceCreate" ] && die "La configuration pman a déjà été initialisée"
|
[ -f .pman.conf -a -z "$ForceCreate" ] && die "La configuration pman a déjà été initialisée"
|
||||||
|
|
||||||
@ -91,6 +91,48 @@ function init_config_action() {
|
|||||||
_push_branches
|
_push_branches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _init_composer() {
|
||||||
|
if [ ! -f .composer.pman.yml -o -n "$ForceCreate" ]; then
|
||||||
|
ac_set_tmpfile config
|
||||||
|
cat >"$config" <<EOF
|
||||||
|
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
|
||||||
|
|
||||||
|
composer:
|
||||||
|
match_prefix:
|
||||||
|
match_prefix-dev:
|
||||||
|
profiles: [ dev, dist ]
|
||||||
|
dev:
|
||||||
|
link: true
|
||||||
|
require:
|
||||||
|
reqire-dev:
|
||||||
|
dist:
|
||||||
|
link: false
|
||||||
|
require:
|
||||||
|
reqire-dev:
|
||||||
|
EOF
|
||||||
|
"${EDITOR:-nano}" "$config"
|
||||||
|
[ -s "$config" ] || return 1
|
||||||
|
|
||||||
|
cp "$config" .composer.pman.yml
|
||||||
|
git add .composer.pman.yml
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_composer_action() {
|
||||||
|
local -a push_branches; local config
|
||||||
|
|
||||||
|
[ -f .composer.pman.yml -a -z "$ForceCreate" ] && die "La configuration pman composer a déjà été initialisée"
|
||||||
|
|
||||||
|
resolve_should_push
|
||||||
|
|
||||||
|
_init_composer || exit_with ewarn "Initialisation de la configuration annulée"
|
||||||
|
git commit -m "configuration pman composer"
|
||||||
|
push_branches+=("$CurrentBranch")
|
||||||
|
|
||||||
|
_push_branches
|
||||||
|
}
|
||||||
|
|
||||||
function _ensure_main_branch() {
|
function _ensure_main_branch() {
|
||||||
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
|
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
|
||||||
[ -n "$MainBranch" ] || die "$MAIN: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
[ -n "$MainBranch" ] || die "$MAIN: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||||
@ -108,7 +150,7 @@ $MAIN: une branche du même nom existe dans l'origine
|
|||||||
|
|
||||||
function _ensure_develop_branch() {
|
function _ensure_develop_branch() {
|
||||||
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
|
||||||
[ -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
[ "$1" == init -o -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_develop_action() {
|
function init_develop_action() {
|
||||||
@ -119,7 +161,7 @@ function init_develop_action() {
|
|||||||
$DEVELOP: une branche du même nom existe dans l'origine
|
$DEVELOP: une branche du même nom existe dans l'origine
|
||||||
git checkout $DEVELOP"
|
git checkout $DEVELOP"
|
||||||
_ensure_main_branch
|
_ensure_main_branch
|
||||||
_ensure_develop_branch
|
_ensure_develop_branch init
|
||||||
|
|
||||||
resolve_should_push
|
resolve_should_push
|
||||||
|
|
||||||
@ -137,7 +179,7 @@ $DEVELOP: une branche du même nom existe dans l'origine
|
|||||||
|
|
||||||
function _ensure_upstream_branch() {
|
function _ensure_upstream_branch() {
|
||||||
[ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
|
[ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
|
||||||
[ -n "$UpstreamBranch" ] || die "$UPSTREAM: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
[ "$1" == init -o -n "$UpstreamBranch" ] || die "$UPSTREAM: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_upstream_action() {
|
function init_upstream_action() {
|
||||||
@ -148,7 +190,7 @@ function init_upstream_action() {
|
|||||||
$UPSTREAM: une branche du même nom existe dans l'origine
|
$UPSTREAM: une branche du même nom existe dans l'origine
|
||||||
git checkout $UPSTREAM"
|
git checkout $UPSTREAM"
|
||||||
_ensure_develop_branch
|
_ensure_develop_branch
|
||||||
_ensure_upstream_branch
|
_ensure_upstream_branch init
|
||||||
|
|
||||||
resolve_should_push
|
resolve_should_push
|
||||||
|
|
||||||
@ -182,7 +224,7 @@ $UPSTREAM: une branche du même nom existe dans l'origine
|
|||||||
|
|
||||||
function _ensure_dist_branch() {
|
function _ensure_dist_branch() {
|
||||||
[ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
|
[ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
|
||||||
[ -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
[ "$1" == init -o -n "$DistBranch" ] || die "$DIST: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_dist_action() {
|
function init_dist_action() {
|
||||||
@ -193,7 +235,7 @@ function init_dist_action() {
|
|||||||
$DIST: une branche du même nom existe dans l'origine
|
$DIST: une branche du même nom existe dans l'origine
|
||||||
git checkout $DIST"
|
git checkout $DIST"
|
||||||
_ensure_main_branch
|
_ensure_main_branch
|
||||||
_ensure_dist_branch
|
_ensure_dist_branch init
|
||||||
|
|
||||||
resolve_should_push
|
resolve_should_push
|
||||||
|
|
||||||
@ -242,6 +284,7 @@ function init_action() {
|
|||||||
case "$what" in
|
case "$what" in
|
||||||
init|repo|r) init_repo_action "$@";;
|
init|repo|r) init_repo_action "$@";;
|
||||||
config) init_config_action "$@";;
|
config) init_config_action "$@";;
|
||||||
|
composer) init_composer_action "$@";;
|
||||||
main|m) checkout_main_action;;
|
main|m) checkout_main_action;;
|
||||||
develop|dev|d) init_develop_action "$@";;
|
develop|dev|d) init_develop_action "$@";;
|
||||||
upstream|up|u) init_upstream_action "$@";;
|
upstream|up|u) init_upstream_action "$@";;
|
||||||
@ -263,7 +306,8 @@ Origin=
|
|||||||
ForceCreate=
|
ForceCreate=
|
||||||
args=(
|
args=(
|
||||||
"gérer un projet git"
|
"gérer un projet git"
|
||||||
"repo|config|develop|upstream|dist
|
"repo|config|composer
|
||||||
|
develop|upstream|dist
|
||||||
|
|
||||||
INITIALISATION
|
INITIALISATION
|
||||||
|
|
||||||
@ -272,6 +316,7 @@ configurer certaines branches du dépôt si elles n'existent pas déjà
|
|||||||
|
|
||||||
repo
|
repo
|
||||||
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
|
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
|
||||||
|
|
||||||
develop
|
develop
|
||||||
créer la branche $DEVELOP
|
créer la branche $DEVELOP
|
||||||
upstream
|
upstream
|
||||||
|
@ -11,9 +11,20 @@ function show_action() {
|
|||||||
local commits
|
local commits
|
||||||
setx commits=_list_commits
|
setx commits=_list_commits
|
||||||
if [ -n "$commits" ]; then
|
if [ -n "$commits" ]; then
|
||||||
|
if [ $ShowLevel -ge 2 ]; then
|
||||||
|
{
|
||||||
|
echo "\
|
||||||
|
# Commits à fusionner $SrcBranch --> $DestBranch
|
||||||
|
|
||||||
|
$commits
|
||||||
|
"
|
||||||
|
_sd_COLOR=always _show_diff
|
||||||
|
} | less -eRF
|
||||||
|
else
|
||||||
einfo "Commits à fusionner $SrcBranch --> $DestBranch"
|
einfo "Commits à fusionner $SrcBranch --> $DestBranch"
|
||||||
eecho "$commits"
|
eecho "$commits"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensure_branches() {
|
function ensure_branches() {
|
||||||
@ -148,6 +159,7 @@ ConfigFile=
|
|||||||
_Fake=
|
_Fake=
|
||||||
_KeepScript=
|
_KeepScript=
|
||||||
action=merge
|
action=merge
|
||||||
|
ShowLevel=0
|
||||||
TechMerge=
|
TechMerge=
|
||||||
SquashMsg=
|
SquashMsg=
|
||||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||||
@ -158,6 +170,7 @@ args=(
|
|||||||
" [source]
|
" [source]
|
||||||
|
|
||||||
CONFIGURATION
|
CONFIGURATION
|
||||||
|
|
||||||
Le fichier .pman.conf contient la configuration des branches. Les variables
|
Le fichier .pman.conf contient la configuration des branches. Les variables
|
||||||
supplémentaires suivantes peuvent être définies:
|
supplémentaires suivantes peuvent être définies:
|
||||||
BEFORE_MERGE_<srcType>
|
BEFORE_MERGE_<srcType>
|
||||||
@ -176,7 +189,7 @@ fichier de configuration des branches. cette option est prioritaire sur --config
|
|||||||
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||||
--fake _Fake=1 "++option non documentée"
|
--fake _Fake=1 "++option non documentée"
|
||||||
--keep-script _KeepScript=1 "++option non documentée"
|
--keep-script _KeepScript=1 "++option non documentée"
|
||||||
-w,--show action=show "\
|
-w,--show '$action=show; inc@ ShowLevel' "\
|
||||||
lister les modifications qui seraient fusionnées dans la branche destination"
|
lister les modifications qui seraient fusionnées dans la branche destination"
|
||||||
-b,--rebase action=rebase "\
|
-b,--rebase action=rebase "\
|
||||||
lancer git rebase -i sur la branche source. cela permet de réordonner les
|
lancer git rebase -i sur la branche source. cela permet de réordonner les
|
14
bin/prel
14
bin/prel
@ -11,9 +11,20 @@ function show_action() {
|
|||||||
local commits
|
local commits
|
||||||
setx commits=_list_commits
|
setx commits=_list_commits
|
||||||
if [ -n "$commits" ]; then
|
if [ -n "$commits" ]; then
|
||||||
|
if [ $ShowLevel -ge 2 ]; then
|
||||||
|
{
|
||||||
|
echo "\
|
||||||
|
# Commits à fusionner $SrcBranch --> $DestBranch
|
||||||
|
|
||||||
|
$commits
|
||||||
|
"
|
||||||
|
_sd_COLOR=always _show_diff
|
||||||
|
} | less -eRF
|
||||||
|
else
|
||||||
einfo "Commits à fusionner $SrcBranch --> $DestBranch"
|
einfo "Commits à fusionner $SrcBranch --> $DestBranch"
|
||||||
eecho "$commits"
|
eecho "$commits"
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensure_branches() {
|
function ensure_branches() {
|
||||||
@ -194,6 +205,7 @@ ConfigFile=
|
|||||||
_Fake=
|
_Fake=
|
||||||
_KeepScript=
|
_KeepScript=
|
||||||
action=release
|
action=release
|
||||||
|
ShowLevel=0
|
||||||
[ -z "$PMAN_NO_MERGE" ] && Merge=1 || Merge=
|
[ -z "$PMAN_NO_MERGE" ] && Merge=1 || Merge=
|
||||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||||
Version=
|
Version=
|
||||||
@ -222,7 +234,7 @@ fichier de configuration des branches. cette option est prioritaire sur --config
|
|||||||
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||||
--fake _Fake=1 "++option non documentée"
|
--fake _Fake=1 "++option non documentée"
|
||||||
--keep-script _KeepScript=1 "++option non documentée"
|
--keep-script _KeepScript=1 "++option non documentée"
|
||||||
-w,--show action=show "\
|
-w,--show '$action=show; inc@ ShowLevel' "\
|
||||||
lister les modifications qui seraient intégrées dans la release"
|
lister les modifications qui seraient intégrées dans la release"
|
||||||
--release action=release "++\
|
--release action=release "++\
|
||||||
créer la release.
|
créer la release.
|
||||||
|
@ -29,7 +29,7 @@ for file in "$@"; do
|
|||||||
estep "Création de $file"
|
estep "Création de $file"
|
||||||
|
|
||||||
cat >"$file" <<EOF
|
cat >"$file" <<EOF
|
||||||
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=$encoding
|
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
-- @database xxx
|
-- @database xxx
|
||||||
|
|
||||||
start transaction;
|
start transaction;
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-pcntl": "*",
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"ext-pgsql": "*",
|
||||||
"ext-sqlite3": "*"
|
"ext-sqlite3": "*"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
4
composer.lock
generated
4
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "266a079e97f3ceecc2cc0a84d6b9743b",
|
"content-hash": "a8b9dc80255663640bda855729ef2d47",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "symfony/deprecation-contracts",
|
"name": "symfony/deprecation-contracts",
|
||||||
@ -2022,6 +2022,8 @@
|
|||||||
"ext-posix": "*",
|
"ext-posix": "*",
|
||||||
"ext-pcntl": "*",
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"ext-pgsql": "*",
|
||||||
"ext-sqlite3": "*"
|
"ext-sqlite3": "*"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.2.0"
|
"plugin-api-version": "2.2.0"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
|
|
||||||
function __pman_pdev_branches() {
|
function __pman_pmer_branches() {
|
||||||
local toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
|
local toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
|
||||||
[ -n "$toplevel" ] || return 0
|
[ -n "$toplevel" ] || return 0
|
||||||
|
|
||||||
@ -29,9 +29,9 @@ function __pman_pdev_branches() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function __pdev_completion() {
|
function __pmer_completion() {
|
||||||
local cur
|
local cur
|
||||||
_get_comp_words_by_ref cur
|
_get_comp_words_by_ref cur
|
||||||
COMPREPLY=($(compgen -W "$(__pman_pdev_branches)" "$cur"))
|
COMPREPLY=($(compgen -W "$(__pman_pmer_branches)" "$cur"))
|
||||||
}
|
}
|
||||||
complete -F __pdev_completion pdev
|
complete -F __pmer_completion pmer
|
||||||
|
@ -1,6 +1,20 @@
|
|||||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
|
|
||||||
source "$@" || exit 1
|
source "$@" || exit 1
|
||||||
|
cd "$srcdir"
|
||||||
|
|
||||||
# supprimer les fichiers de VCS
|
# supprimer les fichiers de VCS
|
||||||
rm -rf "$srcdir/.git"
|
rm -rf .git
|
||||||
|
|
||||||
|
# completion
|
||||||
|
fromdir=lib/completion.d
|
||||||
|
todir="$HOME/etc/completion.d"
|
||||||
|
mkdir -p "$todir"
|
||||||
|
for file in pman; do
|
||||||
|
from="$fromdir/$file"
|
||||||
|
to="$todir/$file"
|
||||||
|
if [ -f "$to" ]; then
|
||||||
|
diff -q "$from" "$to" && continue
|
||||||
|
fi
|
||||||
|
cp "$from" "$to"
|
||||||
|
done
|
||||||
|
120
php/src/A.php
120
php/src/A.php
@ -1,13 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib;
|
namespace nulib;
|
||||||
|
|
||||||
|
use nulib\php\func;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class A: gestion de tableaux ou d'instances de {@link IArrayWrapper}
|
* Class A: gestion de tableaux ou d'instances de {@link IArrayWrapper}
|
||||||
*
|
*
|
||||||
* contrairement à {@link cl}, les méthodes de cette classes sont plutôt conçues
|
* cette classe reprend les méthodes de {@link cl} avec la différence que la
|
||||||
* pour modifier le tableau en place
|
* modification est faite en place
|
||||||
*/
|
*/
|
||||||
class A {
|
class A {
|
||||||
/**
|
/**
|
||||||
@ -176,6 +177,14 @@ class A {
|
|||||||
return $pvalue;
|
return $pvalue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static final function shift(?array &$dest, int $count=1, $default=null) {
|
||||||
|
if ($dest === null) return null;
|
||||||
|
$values = array_slice($dest, 0, $count);
|
||||||
|
$dest = array_slice($dest, $count);
|
||||||
|
if ($values === []) return $default;
|
||||||
|
else return $count == 1? $values[0]: $values;
|
||||||
|
}
|
||||||
|
|
||||||
static final function pop(&$dest, $key, $default=null) {
|
static final function pop(&$dest, $key, $default=null) {
|
||||||
if ($dest === null) return $default;
|
if ($dest === null) return $default;
|
||||||
self::ensure_narray($dest);
|
self::ensure_narray($dest);
|
||||||
@ -232,4 +241,111 @@ class A {
|
|||||||
if ($assoc) uasort($array, cl::compare($keys));
|
if ($assoc) uasort($array, cl::compare($keys));
|
||||||
else usort($array, cl::compare($keys));
|
else usort($array, cl::compare($keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
/**
|
||||||
|
* s'assurer que $array est un tableau associatif, en remplaçant toutes les
|
||||||
|
* clés numériques par la clé correspondante dans $key
|
||||||
|
* <code>
|
||||||
|
* $array = ["first", "second"]
|
||||||
|
* A::ensure_assoc($array, ["a", "b"]);
|
||||||
|
* // returns ["a" => "first", "b" => "second"]
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
static final function ensure_assoc(?array &$array, array $keys, ?array $params=null): void {
|
||||||
|
$prefix = $params["key_prefix"] ?? null;
|
||||||
|
$suffix = $params["key_suffix"] ?? null;
|
||||||
|
$index = 0;
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
if ($prefix !== null || $suffix !== null) {
|
||||||
|
$destKey = "$prefix$key$suffix";
|
||||||
|
} else {
|
||||||
|
# préserver les clés numériques
|
||||||
|
$destKey = $key;
|
||||||
|
}
|
||||||
|
if ($array !== null && array_key_exists($destKey, $array)) continue;
|
||||||
|
while (in_array($index, $keys, true)) {
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
if ($array !== null && array_key_exists($index, $array)) {
|
||||||
|
$array[$destKey] = $array[$index];
|
||||||
|
unset($array[$index]);
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* s'assurer que $array contient toutes les clés de $defaults, avec la valeur
|
||||||
|
* par défaut le cas échéant
|
||||||
|
*
|
||||||
|
* $missings est un tableau indiquant des valeurs qui si elles sont dans
|
||||||
|
* $array, signifie que la clé correspondante doit être considérée comme
|
||||||
|
* inexistante (et donc remplacée par la valeur de $defaults)
|
||||||
|
*/
|
||||||
|
static final function ensure_keys(?array &$array, array $defaults, ?array $missings=null, ?array $params=null): void {
|
||||||
|
$keys = array_keys($defaults);
|
||||||
|
$prefix = $params["key_prefix"] ?? null;
|
||||||
|
$suffix = $params["key_suffix"] ?? null;
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$destKey = "$prefix$key$suffix";
|
||||||
|
$haveMissing = $missings !== null && array_key_exists($key, $missings);
|
||||||
|
if ($array === null || !array_key_exists($destKey, $array)) {
|
||||||
|
$array[$destKey] = $defaults[$key];
|
||||||
|
} elseif ($haveMissing && $array[$destKey] === $missings[$key]) {
|
||||||
|
$array[$destKey] = $defaults[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* supprimer dans $array les clés dont les valeurs correspondent au tableau
|
||||||
|
* $missings
|
||||||
|
*/
|
||||||
|
static final function delete_missings(?array &$array, array $missings, ?array $params=null): void {
|
||||||
|
$prefix = $params["key_prefix"] ?? null;
|
||||||
|
$suffix = $params["key_suffix"] ?? null;
|
||||||
|
foreach ($missings as $key => $missing) {
|
||||||
|
$destKey = "$prefix$key$suffix";
|
||||||
|
if (array_key_exists($destKey, $array) && $array[$destKey] === $missing) {
|
||||||
|
unset($array[$destKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* s'assurer que les clés dans $array sont dans le même ordre que dans $keys
|
||||||
|
*
|
||||||
|
* les clés supplémentaires sont poussées à la fin du tableau
|
||||||
|
*/
|
||||||
|
static final function ensure_order(?array &$array, array $keys, ?array $params=null): void {
|
||||||
|
if ($array === null) return;
|
||||||
|
|
||||||
|
$prefix = $params["key_prefix"] ?? null;
|
||||||
|
$suffix = $params["key_suffix"] ?? null;
|
||||||
|
if ($prefix !== null || $suffix !== null) {
|
||||||
|
foreach ($keys as &$key) {
|
||||||
|
$key = "$prefix$key$suffix";
|
||||||
|
}; unset($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
$destKeys = array_keys($array);
|
||||||
|
$keyCount = count($keys);
|
||||||
|
if (array_slice($destKeys, 0, $keyCount) === $keys) {
|
||||||
|
# si le tableau a déjà les bonnes clés dans le bon ordre, rien à faire
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ordered = [];
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
if (array_key_exists($key, $array)) {
|
||||||
|
$ordered[$key] = $array[$key];
|
||||||
|
unset($array[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$preserveKeys = $params["preserve_keys"] ?? false;
|
||||||
|
if ($preserveKeys) $array = cl::merge2($ordered, $array);
|
||||||
|
else $array = array_merge($ordered, $array);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -460,7 +460,7 @@ class RunFile {
|
|||||||
$exitcode = null;
|
$exitcode = null;
|
||||||
$message = [
|
$message = [
|
||||||
"status" => "$desc: EN COURS pid $data[pid]",
|
"status" => "$desc: EN COURS pid $data[pid]",
|
||||||
"started" => "Démarrée depuis $dateStart ($sinceStart)",
|
"started" => "Démarrée $sinceStart le $dateStart",
|
||||||
"action" => $action,
|
"action" => $action,
|
||||||
];
|
];
|
||||||
} elseif ($this->isStopped($data)) {
|
} elseif ($this->isStopped($data)) {
|
||||||
@ -471,8 +471,9 @@ class RunFile {
|
|||||||
elseif ($exitcode === 0) $type = "success";
|
elseif ($exitcode === 0) $type = "success";
|
||||||
else $type = "danger";
|
else $type = "danger";
|
||||||
$message = [
|
$message = [
|
||||||
"status" => "$desc: TERMINEE$duration",
|
"status" => "$desc: TERMINEE",
|
||||||
"stopped" => "Arrêtée $sinceStop le $dateStop",
|
"stopped" => "Arrêtée $sinceStop le $dateStop",
|
||||||
|
"duration" => $duration,
|
||||||
"result" => $result,
|
"result" => $result,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
141
php/src/cl.php
141
php/src/cl.php
@ -2,7 +2,7 @@
|
|||||||
namespace nulib;
|
namespace nulib;
|
||||||
|
|
||||||
use ArrayAccess;
|
use ArrayAccess;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +60,35 @@ class cl {
|
|||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner la valeur à l'index $index, ou $default si le tableau est null
|
||||||
|
* ou vide, ou si l'index n'existe pas
|
||||||
|
*
|
||||||
|
* ici, l'index est le rang de la clé: 0 pour la première clé du tableau, 1
|
||||||
|
* pour la deuxième, etc.
|
||||||
|
*
|
||||||
|
* si $index est négatif, il est compté à partir de la fin du tableau
|
||||||
|
*/
|
||||||
|
static final function nth(?iterable $iterable, int $index, $default=null) {
|
||||||
|
if ($iterable === null) return $default;
|
||||||
|
if ($index < 0 && !is_array($iterable)) {
|
||||||
|
$iterable = iterator_to_array($iterable, false);
|
||||||
|
}
|
||||||
|
if (is_array($iterable)) {
|
||||||
|
$keys = array_keys($iterable);
|
||||||
|
$count = count($keys);
|
||||||
|
while ($index < 0) $index += $count;
|
||||||
|
$key = $keys[$index] ?? null;
|
||||||
|
if ($key === null) return $default;
|
||||||
|
return $iterable[$key];
|
||||||
|
}
|
||||||
|
foreach ($iterable as $value) {
|
||||||
|
if ($index === 0) return $value;
|
||||||
|
$index--;
|
||||||
|
}
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* retourner la dernière valeur de $array ou $default si le tableau est null
|
* retourner la dernière valeur de $array ou $default si le tableau est null
|
||||||
* ou vide
|
* ou vide
|
||||||
@ -212,7 +241,7 @@ class cl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* obtenir la liste des clés finalement obtenues après l'appel à
|
* obtenir la liste des clés qui seraient finalement obtenues après l'appel à
|
||||||
* {@link self::select()} avec le mapping spécifié
|
* {@link self::select()} avec le mapping spécifié
|
||||||
*/
|
*/
|
||||||
static final function selected_keys(?array $mappings): array {
|
static final function selected_keys(?array $mappings): array {
|
||||||
@ -255,7 +284,7 @@ class cl {
|
|||||||
* $includes qui ne sont pas mentionnées dans $excludes.
|
* $includes qui ne sont pas mentionnées dans $excludes.
|
||||||
*
|
*
|
||||||
* - si $includes===null && $excludes===null, retourner le tableau inchangé
|
* - si $includes===null && $excludes===null, retourner le tableau inchangé
|
||||||
* - si $includes vaut null, prendre toutes les clés
|
* - si $includes vaut null, c'est comme si toutes les clés étaient incluses
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static final function xselect($array, ?array $includes, ?array $excludes=null): ?array {
|
static final function xselect($array, ?array $includes, ?array $excludes=null): ?array {
|
||||||
@ -302,6 +331,27 @@ class cl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* si $array est un array ou une instance de ArrayAccess&Traversable,
|
||||||
|
* supprimer le premier élément dont la valeur est $value
|
||||||
|
*
|
||||||
|
* @param array|ArrayAccess $array
|
||||||
|
*/
|
||||||
|
static final function delv(&$array, $value, bool $strict=false): void {
|
||||||
|
if (is_array($array)) {
|
||||||
|
$key = array_search($value, $array, $strict);
|
||||||
|
if ($key !== false) unset($array[$key]);
|
||||||
|
} elseif ($array instanceof ArrayAccess && $array instanceof Traversable) {
|
||||||
|
$found = false;
|
||||||
|
foreach ($array as $key => $val) {
|
||||||
|
if ($strict) $found = $val === $value;
|
||||||
|
else $found = $val == $value;
|
||||||
|
if ($found) break;
|
||||||
|
}
|
||||||
|
if ($found) $array->offsetUnset($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** retourner le nombre d'éléments de $array */
|
/** retourner le nombre d'éléments de $array */
|
||||||
static final function count(?array $array): int {
|
static final function count(?array $array): int {
|
||||||
return $array !== null? count($array): 0;
|
return $array !== null? count($array): 0;
|
||||||
@ -348,15 +398,86 @@ class cl {
|
|||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
static final function map(callable $callback, ?iterable $array): array {
|
/**
|
||||||
$result = [];
|
* tester si $array satisfait les conditions de $filter
|
||||||
if ($array !== null) {
|
* - $filter est un scalaire, le transformer en [$filter]
|
||||||
$ctx = nur_func::_prepare($callback);
|
* - sinon $filter doit être un tableau de scalaires
|
||||||
foreach ($array as $key => $value) {
|
*
|
||||||
$result[$key] = nur_func::_call($ctx, [$value, $key]);
|
* les règles des conditions sont les suivantes:
|
||||||
|
* - une valeur séquentielle $key est équivalente à la valeur associative
|
||||||
|
* $key => true
|
||||||
|
* - une valeur associative $key => bool indique que la clé correspondante ne
|
||||||
|
* doit pas (resp. doit) exister selon que bool vaut false (resp. true)
|
||||||
|
* - une valeur associative $key => $value indique que la clé correspondante
|
||||||
|
* doit exiter avec la valeur spécifiée
|
||||||
|
*/
|
||||||
|
static final function filter(?array $array, $filter): bool {
|
||||||
|
if ($filter === null) return false;
|
||||||
|
if (!is_array($filter)) $filter = [$filter];
|
||||||
|
if (!$filter) return false;
|
||||||
|
|
||||||
|
$index = 0;
|
||||||
|
foreach ($filter as $key => $value) {
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
if ($array === null) return false;
|
||||||
|
if (!array_key_exists($value, $array)) return false;
|
||||||
|
} elseif (is_bool($value)) {
|
||||||
|
if ($value) {
|
||||||
|
if ($array === null || !array_key_exists($key, $array)) return false;
|
||||||
|
} else {
|
||||||
|
if ($array !== null && array_key_exists($key, $array)) return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($array === null) return false;
|
||||||
|
if (!array_key_exists($key, $array)) return false;
|
||||||
|
if ($array[$key] !== $value) return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $result;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mapper le tableau source $array selon les règles suivantes illustrées dans
|
||||||
|
* l'exemple suivant:
|
||||||
|
* si
|
||||||
|
* $map = ["a", "b" => "x", "c" => function() { return "y"; }, "d" => null]
|
||||||
|
* alors retourner le tableau
|
||||||
|
* ["a" => $array["a"], "b" => $array["x"], "c" => "y", "d" => null]
|
||||||
|
*
|
||||||
|
* si une fonction est utilisée, sa signature est
|
||||||
|
* <code>function(mixed $value, string|int $key, ?array $array)</code>
|
||||||
|
*/
|
||||||
|
static function map(?array $array, ?array $map): ?array {
|
||||||
|
if ($map === null) return $array;
|
||||||
|
$index = 0;
|
||||||
|
$mapped = [];
|
||||||
|
foreach ($map as $key => $value) {
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
if ($value === null) $mapped[] = null;
|
||||||
|
else $mapped[$value] = cl::get($array, $value);
|
||||||
|
} elseif (is_callable($value)) {
|
||||||
|
$func = func::with($value);
|
||||||
|
$value = cl::get($array, $key);
|
||||||
|
$mapped[$key] = $func->invoke([$value, $key, $array]);
|
||||||
|
} else {
|
||||||
|
if ($value === null) $mapped[$key] = null;
|
||||||
|
else $mapped[$key] = cl::get($array, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function mapf(?iterable $items, $func): array {
|
||||||
|
$mapped = [];
|
||||||
|
if ($items !== null) {
|
||||||
|
$func = func::with($func);
|
||||||
|
foreach ($items as $key => $item) {
|
||||||
|
$mapped[$key] = $func->invoke([$item, $key, $items]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -86,6 +86,12 @@ class cv {
|
|||||||
$b = $tmp;
|
$b = $tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** cloner une valeur */
|
||||||
|
static final function clone($value) {
|
||||||
|
if (is_object($value)) $value = clone $value;
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
/** mettre à jour $dest avec $value si $cond($value) est vrai */
|
/** mettre à jour $dest avec $value si $cond($value) est vrai */
|
||||||
@ -197,19 +203,29 @@ class cv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* retourner [$bool, $string, $array] initialisés chacun en fonction du type
|
* retourner [$bool, $scalar, $array] initialisés chacun en fonction du type
|
||||||
* de $value.
|
* de $value.
|
||||||
*
|
*
|
||||||
* @throws ValueException si $value n'est d'aucun de ces types
|
* @throws ValueException si $value n'est d'aucun de ces types
|
||||||
*/
|
*/
|
||||||
static final function check_bsa($value, ?string $prefix=null, bool $throw_exception=true): array {
|
static final function check_bsa($value, ?string $prefix=null, bool $throw_exception=true): array {
|
||||||
$bool = is_bool($value)? $value : null;
|
$bool = is_bool($value)? $value : null;
|
||||||
$string = is_string($value)? $value : null;
|
$scalar = !is_bool($value) && is_scalar($value)? $value : null;
|
||||||
$array = is_array($value)? $value : null;
|
$array = is_array($value)? $value : null;
|
||||||
if ($bool === null && $string === null && $array === null && $throw_exception) {
|
if ($bool === null && $scalar === null && $array === null && $throw_exception) {
|
||||||
throw ValueException::invalid_kind($value, "value", $prefix);
|
throw ValueException::invalid_kind($value, "value", $prefix);
|
||||||
} else {
|
} else {
|
||||||
return [$bool, $string, $array];
|
return [$bool, $scalar, $array];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function subclass_of($value, $class): bool {
|
||||||
|
if (is_string($value)) {
|
||||||
|
return $value === $class || is_subclass_of($value, $class);
|
||||||
|
} elseif (is_object($value)) {
|
||||||
|
return $value instanceof $class;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
use nulib\php\nur_func;
|
use nulib\cl;
|
||||||
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
@ -39,6 +40,11 @@ class Capacitor implements ITransactor {
|
|||||||
return $this->getChannel()->getTableName();
|
return $this->getChannel()->getTableName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCreateSql(): string {
|
||||||
|
$channel = $this->channel;
|
||||||
|
return $this->storage->_getMigration($channel)->getSql(get_class($channel), $this->db());
|
||||||
|
}
|
||||||
|
|
||||||
/** @var CapacitorChannel[] */
|
/** @var CapacitorChannel[] */
|
||||||
protected ?array $subChannels = null;
|
protected ?array $subChannels = null;
|
||||||
|
|
||||||
@ -87,7 +93,7 @@ class Capacitor implements ITransactor {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -120,10 +126,6 @@ class Capacitor implements ITransactor {
|
|||||||
if ($db->inTransaction()) $db->rollback();
|
if ($db->inTransaction()) $db->rollback();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCreateSql(): string {
|
|
||||||
return $this->storage->_getCreateSql($this->channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exists(): bool {
|
function exists(): bool {
|
||||||
return $this->storage->_exists($this->channel);
|
return $this->storage->_exists($this->channel);
|
||||||
}
|
}
|
||||||
@ -167,6 +169,13 @@ class Capacitor implements ITransactor {
|
|||||||
return $this->storage->_delete($this->channel, $filter, $func, $args);
|
return $this->storage->_delete($this->channel, $filter, $func, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dbUpdate(array $update) {
|
||||||
|
return $this->storage->db()->exec(cl::merge([
|
||||||
|
"update",
|
||||||
|
"table" => $this->getTableName(),
|
||||||
|
], $update));
|
||||||
|
}
|
||||||
|
|
||||||
function close(): void {
|
function close(): void {
|
||||||
$this->storage->close();
|
$this->storage->close();
|
||||||
}
|
}
|
||||||
|
@ -8,15 +8,19 @@ use Traversable;
|
|||||||
/**
|
/**
|
||||||
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
|
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
|
||||||
*/
|
*/
|
||||||
class CapacitorChannel {
|
class CapacitorChannel implements ITransactor {
|
||||||
const NAME = null;
|
const NAME = null;
|
||||||
|
|
||||||
const TABLE_NAME = null;
|
const TABLE_NAME = null;
|
||||||
|
|
||||||
const COLUMN_DEFINITIONS = null;
|
protected function COLUMN_DEFINITIONS(): ?array {
|
||||||
|
return static::COLUMN_DEFINITIONS;
|
||||||
|
} const COLUMN_DEFINITIONS = null;
|
||||||
|
|
||||||
const PRIMARY_KEYS = null;
|
const PRIMARY_KEYS = null;
|
||||||
|
|
||||||
|
const MIGRATION = null;
|
||||||
|
|
||||||
const MANAGE_TRANSACTIONS = true;
|
const MANAGE_TRANSACTIONS = true;
|
||||||
|
|
||||||
const EACH_COMMIT_THRESHOLD = 100;
|
const EACH_COMMIT_THRESHOLD = 100;
|
||||||
@ -26,19 +30,17 @@ class CapacitorChannel {
|
|||||||
static function verifix_name(?string &$name, ?string &$tableName=null): void {
|
static function verifix_name(?string &$name, ?string &$tableName=null): void {
|
||||||
if ($name !== null) {
|
if ($name !== null) {
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
if ($tableName === null) {
|
$tableName ??= str_replace("-", "_", $name) . "_channel";
|
||||||
$tableName = str_replace("-", "_", $name) . "_channel";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$name = static::class;
|
$name = static::class;
|
||||||
if ($name === self::class) {
|
if ($name === self::class) {
|
||||||
$name = "default";
|
$name = "default";
|
||||||
if ($tableName === null) $tableName = "default_channel";
|
$tableName ??= "default_channel";
|
||||||
} else {
|
} else {
|
||||||
$name = preg_replace('/^.*\\\\/', "", $name);
|
$name = preg_replace('/^.*\\\\/', "", $name);
|
||||||
$name = preg_replace('/Channel$/', "", $name);
|
$name = preg_replace('/Channel$/', "", $name);
|
||||||
$name = lcfirst($name);
|
$name = lcfirst($name);
|
||||||
if ($tableName === null) $tableName = str::camel2us($name);
|
$tableName ??= str::camel2us($name);
|
||||||
$name = strtolower($name);
|
$name = strtolower($name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,17 +63,52 @@ class CapacitorChannel {
|
|||||||
$this->useCache = static::USE_CACHE;
|
$this->useCache = static::USE_CACHE;
|
||||||
$this->setup = false;
|
$this->setup = false;
|
||||||
$this->created = false;
|
$this->created = false;
|
||||||
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
|
$columnDefinitions = $this->COLUMN_DEFINITIONS();
|
||||||
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
|
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
|
||||||
if ($primaryKeys === null && $columnDefinitions !== null) {
|
$migration = cl::withn(static::MIGRATION);
|
||||||
|
$lastMkey = 1;
|
||||||
|
if ($columnDefinitions !== null) {
|
||||||
|
# mettre à jour la liste des clés primaires et des migrations
|
||||||
$index = 0;
|
$index = 0;
|
||||||
foreach ($columnDefinitions as $col => $def) {
|
foreach ($columnDefinitions as $col => $def) {
|
||||||
if ($col === $index) {
|
if ($col === $index) {
|
||||||
$index++;
|
$index++;
|
||||||
|
if (is_array($def)) {
|
||||||
|
# tableau: c'est une migration
|
||||||
|
$mkey = null;
|
||||||
|
$mvalues = null;
|
||||||
|
$mdefs = $def;
|
||||||
|
$mindex = 0;
|
||||||
|
foreach ($mdefs as $mcol => $mdef) {
|
||||||
|
if ($mindex === 0 && $mcol === 0) {
|
||||||
|
$mindex++;
|
||||||
|
$mkey = $mdef;
|
||||||
|
} elseif ($mcol === $mindex) {
|
||||||
|
# si définition séquentielle, prendre la migration telle quelle
|
||||||
|
$mindex++;
|
||||||
|
$mvalues[] = $mdef;
|
||||||
|
} elseif ($mdef) {
|
||||||
|
# mise à jour d'une colonne
|
||||||
|
$mvalues[] = "alter table $tableName add column $mcol $mdef";
|
||||||
|
} else {
|
||||||
|
# suppression d'une colonne
|
||||||
|
$mvalues[] = "alter table $tableName drop column $mcol";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($mvalues !== null) {
|
||||||
|
if ($mkey === null) $mkey = $lastMkey++;
|
||||||
|
$migration[$mkey] = $mvalues;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# si définition séquentielle, seules les définitions de clé
|
||||||
|
# primaires sont supportées
|
||||||
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
|
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
|
||||||
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
|
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
# chaine: c'est une définition
|
||||||
|
$def = strval($def);
|
||||||
if (preg_match('/\bprimary\s+key\b/i', $def)) {
|
if (preg_match('/\bprimary\s+key\b/i', $def)) {
|
||||||
$primaryKeys[] = $col;
|
$primaryKeys[] = $col;
|
||||||
}
|
}
|
||||||
@ -80,6 +117,7 @@ class CapacitorChannel {
|
|||||||
}
|
}
|
||||||
$this->columnDefinitions = $columnDefinitions;
|
$this->columnDefinitions = $columnDefinitions;
|
||||||
$this->primaryKeys = $primaryKeys;
|
$this->primaryKeys = $primaryKeys;
|
||||||
|
$this->migration = $migration;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string $name;
|
protected string $name;
|
||||||
@ -192,6 +230,12 @@ class CapacitorChannel {
|
|||||||
return $this->columnDefinitions;
|
return $this->columnDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ?array $migration;
|
||||||
|
|
||||||
|
function getMigration(): ?array {
|
||||||
|
return $this->migration;
|
||||||
|
}
|
||||||
|
|
||||||
protected ?array $primaryKeys;
|
protected ?array $primaryKeys;
|
||||||
|
|
||||||
function getPrimaryKeys(): ?array {
|
function getPrimaryKeys(): ?array {
|
||||||
@ -245,9 +289,6 @@ class CapacitorChannel {
|
|||||||
return $serial !== null? unserialize($serial): null;
|
return $serial !== null? unserialize($serial): null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SERIAL_DEFINITION = "mediumtext";
|
|
||||||
const SUM_DEFINITION = "varchar(40)";
|
|
||||||
|
|
||||||
final function sum(?string $serial, $value=null): ?string {
|
final function sum(?string $serial, $value=null): ?string {
|
||||||
if ($serial === null) $serial = $this->serialize($value);
|
if ($serial === null) $serial = $this->serialize($value);
|
||||||
return $serial !== null? sha1($serial): null;
|
return $serial !== null? sha1($serial): null;
|
||||||
@ -377,6 +418,42 @@ class CapacitorChannel {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function willUpdate(...$transactors): ITransactor {
|
||||||
|
return $this->capacitor->willUpdate(...$transactors);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inTransaction(): bool {
|
||||||
|
return $this->capacitor->inTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
||||||
|
$this->capacitor->beginTransaction($func, $commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit(): void {
|
||||||
|
$this->capacitor->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function rollback(): void {
|
||||||
|
$this->capacitor->rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
function db(): IDatabase {
|
||||||
|
return $this->capacitor->getStorage()->db();
|
||||||
|
}
|
||||||
|
|
||||||
|
function exists(): bool {
|
||||||
|
return $this->capacitor->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureExists(): void {
|
||||||
|
$this->capacitor->ensureExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(bool $recreate=false): void {
|
||||||
|
$this->capacitor->reset($recreate);
|
||||||
|
}
|
||||||
|
|
||||||
function charge($item, $func=null, ?array $args=null, ?array &$values=null): int {
|
function charge($item, $func=null, ?array $args=null, ?array &$values=null): int {
|
||||||
return $this->capacitor->charge($item, $func, $args, $values);
|
return $this->capacitor->charge($item, $func, $args, $values);
|
||||||
}
|
}
|
||||||
@ -404,4 +481,12 @@ class CapacitorChannel {
|
|||||||
function delete($filter, $func=null, ?array $args=null): int {
|
function delete($filter, $func=null, ?array $args=null): int {
|
||||||
return $this->capacitor->delete($filter, $func, $args);
|
return $this->capacitor->delete($filter, $func, $args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dbUpdate(array $update) {
|
||||||
|
return $this->capacitor->dbUpdate($update);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
$this->capacitor->close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\db\cache\cache;
|
use nulib\db\_private\_migration;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
@ -35,14 +35,28 @@ abstract class CapacitorStorage {
|
|||||||
/** DOIT être défini dans les classes dérivées */
|
/** DOIT être défini dans les classes dérivées */
|
||||||
const PRIMARY_KEY_DEFINITION = null;
|
const PRIMARY_KEY_DEFINITION = null;
|
||||||
|
|
||||||
|
const SERDATA_DEFINITION = "mediumtext";
|
||||||
|
const SERSUM_DEFINITION = "varchar(40)";
|
||||||
|
const SERTS_DEFINITION = "datetime";
|
||||||
|
|
||||||
|
protected static function sercol($def): string {
|
||||||
|
if (!is_string($def)) $def = strval($def);
|
||||||
|
switch ($def) {
|
||||||
|
case "serdata": $def = static::SERDATA_DEFINITION; break;
|
||||||
|
case "sersum": $def = static::SERSUM_DEFINITION; break;
|
||||||
|
case "serts": $def = static::SERTS_DEFINITION; break;
|
||||||
|
}
|
||||||
|
return $def;
|
||||||
|
}
|
||||||
|
|
||||||
const COLUMN_DEFINITIONS = [
|
const COLUMN_DEFINITIONS = [
|
||||||
"item__" => CapacitorChannel::SERIAL_DEFINITION,
|
"item__" => "serdata",
|
||||||
"item__sum_" => CapacitorChannel::SUM_DEFINITION,
|
"item__sum_" => "sersum",
|
||||||
"created_" => "datetime",
|
"created_" => "serts",
|
||||||
"modified_" => "datetime",
|
"modified_" => "serts",
|
||||||
];
|
];
|
||||||
|
|
||||||
protected function ColumnDefinitions(CapacitorChannel $channel): array {
|
protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
|
||||||
$definitions = [];
|
$definitions = [];
|
||||||
if ($channel->getPrimaryKeys() === null) {
|
if ($channel->getPrimaryKeys() === null) {
|
||||||
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
||||||
@ -58,14 +72,36 @@ abstract class CapacitorStorage {
|
|||||||
foreach ($tmp as $col => $def) {
|
foreach ($tmp as $col => $def) {
|
||||||
if ($col === $index) {
|
if ($col === $index) {
|
||||||
$index++;
|
$index++;
|
||||||
$constraints[] = $def;
|
if (is_array($def)) {
|
||||||
|
if (!$ignoreMigrations) {
|
||||||
|
$mdefs = $def;
|
||||||
|
$mindex = 0;
|
||||||
|
foreach ($mdefs as $mcol => $mdef) {
|
||||||
|
if ($mcol === $mindex) {
|
||||||
|
$mindex++;
|
||||||
} else {
|
} else {
|
||||||
$definitions[$col] = $def;
|
if ($mdef) {
|
||||||
|
$definitions[$mcol] = self::sercol($mdef);
|
||||||
|
} else {
|
||||||
|
unset($definitions[$mcol]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$constraints[] = $def;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$definitions[$col] = self::sercol($def);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cl::merge($definitions, $constraints);
|
return cl::merge($definitions, $constraints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getMigration(CapacitorChannel $channel): ?array {
|
||||||
|
return $channel->getMigration();
|
||||||
|
}
|
||||||
|
|
||||||
/** sérialiser les valeurs qui doivent l'être dans $values */
|
/** sérialiser les valeurs qui doivent l'être dans $values */
|
||||||
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
protected function serialize(CapacitorChannel $channel, ?array $values): ?array {
|
||||||
if ($values === null) return null;
|
if ($values === null) return null;
|
||||||
@ -128,46 +164,97 @@ abstract class CapacitorStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _createSql(CapacitorChannel $channel): array {
|
protected function _createSql(CapacitorChannel $channel): array {
|
||||||
$cols = $this->ColumnDefinitions($channel);
|
|
||||||
return [
|
return [
|
||||||
"create table if not exists",
|
"create table if not exists",
|
||||||
"table" => $channel->getTableName(),
|
"table" => $channel->getTableName(),
|
||||||
"cols" => $cols,
|
"cols" => $this->ColumnDefinitions($channel, true),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function format_sql(CapacitorChannel $channel, string $sql): string {
|
abstract protected function tableExists(string $tableName): bool;
|
||||||
$class = get_class($channel);
|
|
||||||
return <<<EOT
|
|
||||||
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
|
||||||
-- autogénéré à partir de $class
|
|
||||||
|
|
||||||
$sql;
|
const METADATA_TABLE = "_metadata";
|
||||||
|
const METADATA_COLS = [
|
||||||
|
"name" => "varchar not null primary key",
|
||||||
|
"value" => "varchar",
|
||||||
|
];
|
||||||
|
|
||||||
EOT;
|
protected function _prepareMetadata(): void {
|
||||||
|
if (!$this->tableExists(static::METADATA_TABLE)) {
|
||||||
|
$db = $this->db();
|
||||||
|
$db->exec([
|
||||||
|
"drop table if exists",
|
||||||
|
"table" => self::CHANNELS_TABLE,
|
||||||
|
]);
|
||||||
|
$db->exec([
|
||||||
|
"drop table if exists",
|
||||||
|
"table" => _migration::MIGRATION_TABLE,
|
||||||
|
]);
|
||||||
|
$db->exec([
|
||||||
|
"create table",
|
||||||
|
"table" => static::METADATA_TABLE,
|
||||||
|
"cols" => static::METADATA_COLS,
|
||||||
|
]);
|
||||||
|
$db->exec([
|
||||||
|
"insert",
|
||||||
|
"into" => static::METADATA_TABLE,
|
||||||
|
"values" => [
|
||||||
|
"name" => "version",
|
||||||
|
"value" => "1",
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract function _getCreateSql(CapacitorChannel $channel): string;
|
abstract function _getMigration(CapacitorChannel $channel): _migration;
|
||||||
|
|
||||||
/** obtenir la requête SQL utilisée pour créer la table */
|
const CHANNELS_TABLE = "_channels";
|
||||||
function getCreateSql(?string $channel): string {
|
const CHANNELS_COLS = [
|
||||||
return $this->_getCreateSql($this->getChannel($channel));
|
"name" => "varchar not null primary key",
|
||||||
|
"table_name" => "varchar",
|
||||||
|
"class_name" => "varchar",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function _createChannelsSql(): array {
|
||||||
|
return [
|
||||||
|
"create table if not exists",
|
||||||
|
"table" => static::CHANNELS_TABLE,
|
||||||
|
"cols" => static::CHANNELS_COLS,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
|
return [
|
||||||
|
"insert",
|
||||||
|
"into" => static::CHANNELS_TABLE,
|
||||||
|
"values" => [
|
||||||
|
"name" => $channel->getName(),
|
||||||
|
"table_name" => $channel->getTableName(),
|
||||||
|
"class_name" => get_class($channel),
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _afterCreate(CapacitorChannel $channel): void {
|
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||||
|
$db = $this->db();
|
||||||
|
$db->exec($this->_createChannelsSql());
|
||||||
|
$db->exec($this->_addToChannelsSql($channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _create(CapacitorChannel $channel): void {
|
protected function _create(CapacitorChannel $channel): void {
|
||||||
$channel->ensureSetup();
|
$channel->ensureSetup();
|
||||||
if (!$channel->isCreated()) {
|
if (!$channel->isCreated()) {
|
||||||
$this->db->exec($this->_createSql($channel));
|
$this->_prepareMetadata();
|
||||||
|
$this->_getMigration($channel)->migrate($this->db());
|
||||||
$this->_afterCreate($channel);
|
$this->_afterCreate($channel);
|
||||||
$channel->setCreated();
|
$channel->setCreated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** tester si le canal spécifié existe */
|
/** tester si le canal spécifié existe */
|
||||||
abstract function _exists(CapacitorChannel $channel): bool;
|
function _exists(CapacitorChannel $channel): bool {
|
||||||
|
return $this->tableExists($channel->getTableName());
|
||||||
|
}
|
||||||
|
|
||||||
function exists(?string $channel): bool {
|
function exists(?string $channel): bool {
|
||||||
return $this->_exists($this->getChannel($channel));
|
return $this->_exists($this->getChannel($channel));
|
||||||
@ -183,12 +270,28 @@ EOT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
protected function _beforeReset(CapacitorChannel $channel): void {
|
||||||
|
$db = $this->db;
|
||||||
|
$name = $channel->getName();
|
||||||
|
$db->exec([
|
||||||
|
"delete",
|
||||||
|
"from" => _migration::MIGRATION_TABLE,
|
||||||
|
"where" => [
|
||||||
|
"channel" => $name,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$db->exec([
|
||||||
|
"delete",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
|
"where" => [
|
||||||
|
"name" => $name,
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** supprimer le canal spécifié */
|
/** supprimer le canal spécifié */
|
||||||
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
||||||
$this->_beforeReset($channel);
|
$this->_beforeReset($channel);
|
||||||
$this->db->exec([
|
$this->db()->exec([
|
||||||
"drop table if exists",
|
"drop table if exists",
|
||||||
$channel->getTableName(),
|
$channel->getTableName(),
|
||||||
]);
|
]);
|
||||||
@ -230,10 +333,7 @@ EOT;
|
|||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$args ??= [];
|
$args ??= [];
|
||||||
|
|
||||||
$initFunc = [$channel, "getItemValues"];
|
$values = func::call([$channel, "getItemValues"], $item, ...$args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = nur_func::call($initFunc, $item, ...$initArgs);
|
|
||||||
if ($values === [false]) return 0;
|
if ($values === [false]) return 0;
|
||||||
|
|
||||||
$row = cl::merge(
|
$row = cl::merge(
|
||||||
@ -259,9 +359,7 @@ EOT;
|
|||||||
"modified_" => $now,
|
"modified_" => $now,
|
||||||
]);
|
]);
|
||||||
$insert = true;
|
$insert = true;
|
||||||
$initFunc = [$channel, "onCreate"];
|
$initFunc = func::with([$channel, "onCreate"], $args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = $this->unserialize($channel, $row);
|
$values = $this->unserialize($channel, $row);
|
||||||
$pvalues = null;
|
$pvalues = null;
|
||||||
} else {
|
} else {
|
||||||
@ -276,14 +374,12 @@ EOT;
|
|||||||
} else {
|
} else {
|
||||||
$row = cl::merge($prow, $row);
|
$row = cl::merge($prow, $row);
|
||||||
}
|
}
|
||||||
$initFunc = [$channel, "onUpdate"];
|
$initFunc = func::with([$channel, "onUpdate"], $args);
|
||||||
$initArgs = $args;
|
|
||||||
nur_func::ensure_func($initFunc, null, $initArgs);
|
|
||||||
$values = $this->unserialize($channel, $row);
|
$values = $this->unserialize($channel, $row);
|
||||||
$pvalues = $this->unserialize($channel, $prow);
|
$pvalues = $this->unserialize($channel, $prow);
|
||||||
}
|
}
|
||||||
|
|
||||||
$updates = nur_func::call($initFunc, $item, $values, $pvalues, ...$initArgs);
|
$updates = $initFunc->prependArgs([$item, $values, $pvalues])->invoke();
|
||||||
if ($updates === [false]) return 0;
|
if ($updates === [false]) return 0;
|
||||||
if (is_array($updates) && $updates) {
|
if (is_array($updates) && $updates) {
|
||||||
if ($insert === null) $insert = false;
|
if ($insert === null) $insert = false;
|
||||||
@ -295,8 +391,10 @@ EOT;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$updates = func::with($func)
|
||||||
$updates = nur_func::call($func, $item, $values, $pvalues, ...$args);
|
->prependArgs([$item, $values, $pvalues])
|
||||||
|
->bind($channel)
|
||||||
|
->invoke();
|
||||||
if ($updates === [false]) return 0;
|
if ($updates === [false]) return 0;
|
||||||
if (is_array($updates) && $updates) {
|
if (is_array($updates) && $updates) {
|
||||||
if ($insert === null) $insert = false;
|
if ($insert === null) $insert = false;
|
||||||
@ -510,8 +608,7 @@ EOT;
|
|||||||
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
if ($func === null) $func = CapacitorChannel::onEach;
|
if ($func === null) $func = CapacitorChannel::onEach;
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$onEach = func::with($func)->bind($channel);
|
||||||
$onEach = nur_func::_prepare($func);
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
@ -528,7 +625,7 @@ EOT;
|
|||||||
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
$all = $this->_allCached("each", $channel, $filter, $mergeQuery);
|
||||||
foreach ($all as $values) {
|
foreach ($all as $values) {
|
||||||
$rowIds = $this->getRowIds($channel, $values);
|
$rowIds = $this->getRowIds($channel, $values);
|
||||||
$updates = nur_func::_call($onEach, [$values["item"], $values, ...$args]);
|
$updates = $onEach->invoke([$values["item"], $values, ...$args]);
|
||||||
if (is_array($updates) && $updates) {
|
if (is_array($updates) && $updates) {
|
||||||
if (!array_key_exists("modified_", $updates)) {
|
if (!array_key_exists("modified_", $updates)) {
|
||||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||||
@ -579,8 +676,7 @@ EOT;
|
|||||||
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
||||||
$this->_create($channel);
|
$this->_create($channel);
|
||||||
if ($func === null) $func = CapacitorChannel::onDelete;
|
if ($func === null) $func = CapacitorChannel::onDelete;
|
||||||
nur_func::ensure_func($func, $channel, $args);
|
$onDelete = func::with($func)->bind($channel);
|
||||||
$onEach = nur_func::_prepare($func);
|
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||||
@ -596,8 +692,8 @@ EOT;
|
|||||||
$all = $this->_allCached("delete", $channel, $filter);
|
$all = $this->_allCached("delete", $channel, $filter);
|
||||||
foreach ($all as $values) {
|
foreach ($all as $values) {
|
||||||
$rowIds = $this->getRowIds($channel, $values);
|
$rowIds = $this->getRowIds($channel, $values);
|
||||||
$delete = boolval(nur_func::_call($onEach, [$values["item"], $values, ...$args]));
|
$shouldDelete = boolval($onDelete->invoke([$values["item"], $values, ...$args]));
|
||||||
if ($delete) {
|
if ($shouldDelete) {
|
||||||
$db->exec([
|
$db->exec([
|
||||||
"delete",
|
"delete",
|
||||||
"from" => $tableName,
|
"from" => $tableName,
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
namespace nulib\db;
|
namespace nulib\db;
|
||||||
|
|
||||||
interface IDatabase extends ITransactor {
|
interface IDatabase extends ITransactor {
|
||||||
|
/** obtenir la requête SQL correspondant à $query */
|
||||||
|
function getSql($query, ?array $params=null): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - si c'est un insert, retourner l'identifiant autogénéré de la ligne
|
* - si c'est un insert, retourner l'identifiant autogénéré de la ligne
|
||||||
* - sinon retourner le nombre de lignes modifiées en cas de succès, ou false
|
* - sinon retourner le nombre de lignes modifiées en cas de succès, ou false
|
||||||
@ -15,5 +18,9 @@ interface IDatabase extends ITransactor {
|
|||||||
|
|
||||||
function one($query, ?array $params=null): ?array;
|
function one($query, ?array $params=null): ?array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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): iterable;
|
function all($query, ?array $params=null, $primaryKeys=null): iterable;
|
||||||
}
|
}
|
||||||
|
@ -11,12 +11,13 @@ interface ITransactor {
|
|||||||
*/
|
*/
|
||||||
function willUpdate(...$transactors): self;
|
function willUpdate(...$transactors): self;
|
||||||
|
|
||||||
|
/** Indiquer si une transaction est en cours */
|
||||||
function inTransaction(): bool;
|
function inTransaction(): bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* démarrer une transaction
|
* démarrer une transaction
|
||||||
*
|
*
|
||||||
* si $func!==null, l'apppeler. ensuite, si $commit===true, commiter la
|
* si $func!==null, l'apppeler. ensuite, si $commit===true, valider la
|
||||||
* transaction. si une erreur se produit lors de l'appel de la fonction,
|
* transaction. si une erreur se produit lors de l'appel de la fonction,
|
||||||
* annuler la transaction
|
* annuler la transaction
|
||||||
*
|
*
|
||||||
@ -24,7 +25,9 @@ interface ITransactor {
|
|||||||
*/
|
*/
|
||||||
function beginTransaction(?callable $func=null, bool $commit=true): void;
|
function beginTransaction(?callable $func=null, bool $commit=true): void;
|
||||||
|
|
||||||
|
/** valider la transaction */
|
||||||
function commit(): void;
|
function commit(): void;
|
||||||
|
|
||||||
|
/** annuler la transaction */
|
||||||
function rollback(): void;
|
function rollback(): void;
|
||||||
}
|
}
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\_private;
|
|
||||||
|
|
||||||
trait Tcreate {
|
|
||||||
static function isa(string $sql): bool {
|
|
||||||
//return preg_match("/^create(?:\s+table)?\b/i", $sql);
|
|
||||||
#XXX implémentation minimale
|
|
||||||
return preg_match("/^create\s+table\b/i", $sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
static function parse(array $query, ?array &$bindings=null): string {
|
|
||||||
#XXX implémentation minimale
|
|
||||||
$sql = [self::merge_seq($query)];
|
|
||||||
|
|
||||||
## préfixe
|
|
||||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
|
||||||
|
|
||||||
## table
|
|
||||||
$sql[] = $query["table"];
|
|
||||||
|
|
||||||
## columns
|
|
||||||
$cols = $query["cols"];
|
|
||||||
$index = 0;
|
|
||||||
foreach ($cols as $col => &$definition) {
|
|
||||||
if ($col === $index) {
|
|
||||||
$index++;
|
|
||||||
} else {
|
|
||||||
$definition = "$col $definition";
|
|
||||||
}
|
|
||||||
}; unset($definition);
|
|
||||||
$sql[] = "(\n ".implode("\n, ", $cols)."\n)";
|
|
||||||
|
|
||||||
## suffixe
|
|
||||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
|
||||||
|
|
||||||
## fin de la requête
|
|
||||||
return implode(" ", $sql);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\_private;
|
|
||||||
|
|
||||||
trait Tdelete {
|
|
||||||
|
|
||||||
static function isa(string $sql): bool {
|
|
||||||
return preg_match("/^delete(?:\s+from)?\b/i", $sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
$sql = ["delete from", $tmpsql];
|
|
||||||
|
|
||||||
## préfixe
|
|
||||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
|
||||||
|
|
||||||
## table
|
|
||||||
$sql[] = $query["from"];
|
|
||||||
|
|
||||||
## where
|
|
||||||
$where = $query["where"] ?? null;
|
|
||||||
if ($where !== null) {
|
|
||||||
self::parse_conds($where, $wheresql, $bindings);
|
|
||||||
if ($wheresql) {
|
|
||||||
$sql[] = "where";
|
|
||||||
$sql[] = implode(" and ", $wheresql);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
## suffixe
|
|
||||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
|
||||||
|
|
||||||
## fin de la requête
|
|
||||||
return implode(" ", $sql);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\_private;
|
|
||||||
|
|
||||||
use nulib\cl;
|
|
||||||
use nulib\ValueException;
|
|
||||||
|
|
||||||
trait Tgeneric {
|
|
||||||
static function isa(string $sql): bool {
|
|
||||||
return preg_match('/^(?:drop\s+table)\b/i', $sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
return self::merge_seq($query);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\_private;
|
|
||||||
|
|
||||||
use nulib\cl;
|
|
||||||
use nulib\ValueException;
|
|
||||||
|
|
||||||
trait Tinsert {
|
|
||||||
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 &$bindings=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+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
|
|
||||||
$sql[] = $ms[1];
|
|
||||||
|
|
||||||
## 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";
|
|
||||||
$bindings[$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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,168 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\_private;
|
|
||||||
|
|
||||||
use nulib\cl;
|
|
||||||
use nulib\str;
|
|
||||||
use nulib\ValueException;
|
|
||||||
|
|
||||||
trait Tselect {
|
|
||||||
static function isa(string $sql): bool {
|
|
||||||
return preg_match("/^select\b/i", $sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function add_prefix(string $col, ?string $prefix): string {
|
|
||||||
if ($prefix === null) return $col;
|
|
||||||
if (strpos($col, ".") !== false) return $col;
|
|
||||||
return "$prefix$col";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 &$bindings=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*distinct)?)\s*', $tmpsql, $ms);
|
|
||||||
$sql[] = $ms[1];
|
|
||||||
|
|
||||||
## cols
|
|
||||||
$usercols = [];
|
|
||||||
if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
|
|
||||||
if ($ms[1]) $usercols[] = $ms[1];
|
|
||||||
}
|
|
||||||
$colPrefix = $query["col_prefix"] ?? null;
|
|
||||||
if ($colPrefix !== null) str::add_suffix($colPrefix, ".");
|
|
||||||
$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[] = self::add_prefix($col, $colPrefix);
|
|
||||||
} else {
|
|
||||||
$cols[] = $key;
|
|
||||||
$usercols[] = self::add_prefix($col, $colPrefix)." as $key";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$cols = null;
|
|
||||||
if ($schema && is_array($schema) && !in_array("*", $usercols)) {
|
|
||||||
$cols = array_keys($schema);
|
|
||||||
foreach ($cols as $col) {
|
|
||||||
$usercols[] = self::add_prefix($col, $colPrefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$usercols && !$cols) $usercols = [self::add_prefix("*", $colPrefix)];
|
|
||||||
$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, $bindings);
|
|
||||||
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, $bindings);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\_private;
|
|
||||||
|
|
||||||
trait Tupdate {
|
|
||||||
static function isa(string $sql): bool {
|
|
||||||
return preg_match("/^update\b/i", $sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
static function parse(array $query, ?array &$bindings=null): string {
|
|
||||||
#XXX implémentation minimale
|
|
||||||
$sql = [self::merge_seq($query)];
|
|
||||||
|
|
||||||
## préfixe
|
|
||||||
if (($prefix = $query["prefix"] ?? null) !== null) $sql[] = $prefix;
|
|
||||||
|
|
||||||
## table
|
|
||||||
$sql[] = $query["table"];
|
|
||||||
|
|
||||||
## set
|
|
||||||
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, $bindings);
|
|
||||||
if ($wheresql) {
|
|
||||||
$sql[] = "where";
|
|
||||||
$sql[] = implode(" and ", $wheresql);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
## suffixe
|
|
||||||
if (($suffix = $query["suffix"] ?? null) !== null) $sql[] = $suffix;
|
|
||||||
|
|
||||||
## fin de la requête
|
|
||||||
return implode(" ", $sql);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,259 +1,59 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
use nulib\cl;
|
|
||||||
use nulib\str;
|
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
|
|
||||||
abstract class _base {
|
abstract class _base extends _common {
|
||||||
protected static function consume(string $pattern, string &$string, ?array &$ms=null): bool {
|
protected static function verifix(&$sql, ?array &$bindings=null, ?array &$meta=null): void {
|
||||||
if (!preg_match("/^$pattern/i", $string, $ms)) return false;
|
if (is_array($sql)) {
|
||||||
$string = substr($string, strlen($ms[0]));
|
$prefix = $sql[0] ?? null;
|
||||||
return true;
|
if ($prefix === null) {
|
||||||
}
|
throw new ValueException("requête invalide");
|
||||||
|
} elseif (_create::isa($prefix)) {
|
||||||
/** fusionner toutes les parties séquentielles d'une requête */
|
$sql = _create::parse($sql, $bindings);
|
||||||
protected static function merge_seq(array $query): string {
|
$meta = ["isa" => "create", "type" => "ddl"];
|
||||||
$index = 0;
|
} elseif (_select::isa($prefix)) {
|
||||||
$sql = "";
|
$sql = _select::parse($sql, $bindings);
|
||||||
foreach ($query as $key => $value) {
|
$meta = ["isa" => "select", "type" => "dql"];
|
||||||
if ($key === $index) {
|
} elseif (_insert::isa($prefix)) {
|
||||||
$index++;
|
$sql = _insert::parse($sql, $bindings);
|
||||||
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
|
$meta = ["isa" => "insert", "type" => "dml"];
|
||||||
$sql .= " ";
|
} elseif (_update::isa($prefix)) {
|
||||||
}
|
$sql = _update::parse($sql, $bindings);
|
||||||
$sql .= $value;
|
$meta = ["isa" => "update", "type" => "dml"];
|
||||||
}
|
} elseif (_delete::isa($prefix)) {
|
||||||
}
|
$sql = _delete::parse($sql, $bindings);
|
||||||
return $sql;
|
$meta = ["isa" => "delete", "type" => "dml"];
|
||||||
}
|
} elseif (_generic::isa($prefix)) {
|
||||||
|
$sql = _generic::parse($sql, $bindings);
|
||||||
protected static function is_sep(&$cond): bool {
|
$meta = ["isa" => "generic", "type" => null];
|
||||||
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 &$bindings): 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_bool($cond)) {
|
|
||||||
# ignorer les valeurs true et false
|
|
||||||
} elseif (is_array($cond)) {
|
|
||||||
# condition récursive
|
|
||||||
self::parse_conds($cond, $condsql, $bindings);
|
|
||||||
} else {
|
} else {
|
||||||
# condition litérale
|
throw ValueException::invalid_kind($sql, "query");
|
||||||
$condsql[] = strval($cond);
|
|
||||||
}
|
|
||||||
$index++;
|
|
||||||
} elseif ($cond === false) {
|
|
||||||
## associatif
|
|
||||||
# condition litérale ignorée car condition false
|
|
||||||
} elseif ($cond === true) {
|
|
||||||
# condition litérale sélectionnée car condition true
|
|
||||||
$condsql[] = strval($key);
|
|
||||||
} else {
|
|
||||||
## associatif
|
|
||||||
# paramètre
|
|
||||||
$param0 = preg_replace('/^.+\./', "", $key);
|
|
||||||
$i = false;
|
|
||||||
if ($bindings !== null && array_key_exists($param0, $bindings)) {
|
|
||||||
$i = 2;
|
|
||||||
while (array_key_exists("$param0$i", $bindings)) {
|
|
||||||
$i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# value ou [operator, value]
|
|
||||||
$condprefix = $condsep = $condsuffix = null;
|
|
||||||
if (is_array($cond)) {
|
|
||||||
$condkey = 0;
|
|
||||||
$condkeys = array_keys($cond);
|
|
||||||
$op = null;
|
|
||||||
if (array_key_exists("op", $cond)) {
|
|
||||||
$op = $cond["op"];
|
|
||||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
|
||||||
$op = $cond[$condkeys[$condkey]];
|
|
||||||
$condkey++;
|
|
||||||
}
|
|
||||||
$op = strtolower($op);
|
|
||||||
$condvalues = null;
|
|
||||||
switch ($op) {
|
|
||||||
case "between":
|
|
||||||
# ["between", $upper, $lower]
|
|
||||||
$condsep = " and ";
|
|
||||||
if (array_key_exists("lower", $cond)) {
|
|
||||||
$condvalues[] = $cond["lower"];
|
|
||||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
|
||||||
$condvalues[] = $cond[$condkeys[$condkey]];
|
|
||||||
$condkey++;
|
|
||||||
}
|
|
||||||
if (array_key_exists("upper", $cond)) {
|
|
||||||
$condvalues[] = $cond["upper"];
|
|
||||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
|
||||||
$condvalues[] = $cond[$condkeys[$condkey]];
|
|
||||||
$condkey++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "any":
|
|
||||||
case "all":
|
|
||||||
case "not any":
|
|
||||||
case "not all":
|
|
||||||
# ["list", $values]
|
|
||||||
if ($op === "any" || $op === "all") {
|
|
||||||
$condprefix = $op;
|
|
||||||
$op = "=";
|
|
||||||
} elseif ($op === "not any" || $op === "not all") {
|
|
||||||
$condprefix = substr($op, strlen("not "));
|
|
||||||
$op = "<>";
|
|
||||||
}
|
|
||||||
$condprefix .= "(array[";
|
|
||||||
$condsep = ", ";
|
|
||||||
$condsuffix = "])";
|
|
||||||
$condvalues = null;
|
|
||||||
if (array_key_exists("values", $cond)) {
|
|
||||||
$condvalues = cl::with($cond["values"]);
|
|
||||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
|
||||||
$condvalues = cl::with($cond[$condkeys[$condkey]]);
|
|
||||||
$condkey++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "in":
|
|
||||||
# ["in", $values]
|
|
||||||
$condprefix = "(";
|
|
||||||
$condsep = ", ";
|
|
||||||
$condsuffix = ")";
|
|
||||||
$condvalues = null;
|
|
||||||
if (array_key_exists("values", $cond)) {
|
|
||||||
$condvalues = cl::with($cond["values"]);
|
|
||||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
|
||||||
$condvalues = cl::with($cond[$condkeys[$condkey]]);
|
|
||||||
$condkey++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "null":
|
|
||||||
case "is null":
|
|
||||||
$op = "is null";
|
|
||||||
break;
|
|
||||||
case "not null":
|
|
||||||
case "is not null":
|
|
||||||
$op = "is not null";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (array_key_exists("value", $cond)) {
|
|
||||||
$condvalues = [$cond["value"]];
|
|
||||||
} elseif (array_key_exists($condkey, $condkeys)) {
|
|
||||||
$condvalues = [$cond[$condkeys[$condkey]]];
|
|
||||||
$condkey++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} elseif ($cond !== null) {
|
|
||||||
$op = "=";
|
|
||||||
$condvalues = [$cond];
|
|
||||||
} else {
|
|
||||||
$op = "is null";
|
|
||||||
$condvalues = null;
|
|
||||||
}
|
|
||||||
$cond = [$key, $op];
|
|
||||||
if ($condvalues !== null) {
|
|
||||||
$parts = [];
|
|
||||||
foreach ($condvalues as $condvalue) {
|
|
||||||
if (is_array($condvalue)) {
|
|
||||||
$first = true;
|
|
||||||
foreach ($condvalue as $value) {
|
|
||||||
if ($first) {
|
|
||||||
$first = false;
|
|
||||||
} else {
|
|
||||||
if ($sep === null) $sep = "and";
|
|
||||||
$parts[] = " $sep ";
|
|
||||||
$parts[] = $key;
|
|
||||||
$parts[] = " $op ";
|
|
||||||
}
|
|
||||||
$param = "$param0$i";
|
|
||||||
$parts[] = ":$param";
|
|
||||||
$bindings[$param] = $value;
|
|
||||||
if ($i === false) $i = 2;
|
|
||||||
else $i++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$param = "$param0$i";
|
if (!is_string($sql)) $sql = strval($sql);
|
||||||
$parts[] = ":$param";
|
if (_create::isa($sql)) {
|
||||||
$bindings[$param] = $condvalue;
|
$meta = ["isa" => "create", "type" => "ddl"];
|
||||||
if ($i === false) $i = 2;
|
} elseif (_select::isa($sql)) {
|
||||||
else $i++;
|
$meta = ["isa" => "select", "type" => "dql"];
|
||||||
|
} elseif (_insert::isa($sql)) {
|
||||||
|
$meta = ["isa" => "insert", "type" => "dml"];
|
||||||
|
} elseif (_update::isa($sql)) {
|
||||||
|
$meta = ["isa" => "update", "type" => "dml"];
|
||||||
|
} elseif (_delete::isa($sql)) {
|
||||||
|
$meta = ["isa" => "delete", "type" => "dml"];
|
||||||
|
} elseif (_generic::isa($sql)) {
|
||||||
|
$meta = ["isa" => "generic", "type" => null];
|
||||||
|
} else {
|
||||||
|
$meta = ["isa" => "generic", "type" => null];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$cond[] = $condprefix.implode($condsep, $parts).$condsuffix;
|
|
||||||
}
|
|
||||||
$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 &$bindings): void {
|
static function with($sql, ?array $params=null): array {
|
||||||
if (!$values) return;
|
static::verifix($sql, $params);
|
||||||
$index = 0;
|
return [$sql, $params];
|
||||||
$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, $bindings);
|
|
||||||
} else {
|
|
||||||
# paramètre litéral
|
|
||||||
$parts[] = strval($part);
|
|
||||||
}
|
}
|
||||||
$index++;
|
|
||||||
} else {
|
|
||||||
## associatif
|
|
||||||
# paramètre
|
|
||||||
$param = $param0 = preg_replace('/^.+\./', "", $key);
|
|
||||||
if ($bindings !== null && array_key_exists($param0, $bindings)) {
|
|
||||||
$i = 2;
|
|
||||||
while (array_key_exists("$param0$i", $bindings)) {
|
|
||||||
$i++;
|
|
||||||
}
|
|
||||||
$param = "$param0$i";
|
|
||||||
}
|
|
||||||
# value
|
|
||||||
$value = $part;
|
|
||||||
$part = [$key, "="];
|
|
||||||
if ($value === null) {
|
|
||||||
$part[] = "null";
|
|
||||||
} else {
|
|
||||||
$part[] = ":$param";
|
|
||||||
$bindings[$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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract protected static function verifix(&$sql, ?array &$bindinds=null, ?array &$meta=null): void;
|
|
||||||
|
|
||||||
function __construct($sql, ?array $bindings=null) {
|
function __construct($sql, ?array $bindings=null) {
|
||||||
static::verifix($sql, $bindings, $meta);
|
static::verifix($sql, $bindings, $meta);
|
||||||
|
255
php/src/db/_private/_common.php
Normal file
255
php/src/db/_private/_common.php
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\str;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _common {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function parse_conds(?array $conds, ?array &$sql, ?array &$bindings): 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_bool($cond)) {
|
||||||
|
# ignorer les valeurs true et false
|
||||||
|
} elseif (is_array($cond)) {
|
||||||
|
# condition récursive
|
||||||
|
self::parse_conds($cond, $condsql, $bindings);
|
||||||
|
} else {
|
||||||
|
# condition litérale
|
||||||
|
$condsql[] = strval($cond);
|
||||||
|
}
|
||||||
|
$index++;
|
||||||
|
} elseif ($cond === false) {
|
||||||
|
## associatif
|
||||||
|
# condition litérale ignorée car condition false
|
||||||
|
} elseif ($cond === true) {
|
||||||
|
# condition litérale sélectionnée car condition true
|
||||||
|
$condsql[] = strval($key);
|
||||||
|
} else {
|
||||||
|
## associatif
|
||||||
|
# paramètre
|
||||||
|
$param0 = preg_replace('/^.+\./', "", $key);
|
||||||
|
$i = false;
|
||||||
|
if ($bindings !== null && array_key_exists($param0, $bindings)) {
|
||||||
|
$i = 2;
|
||||||
|
while (array_key_exists("$param0$i", $bindings)) {
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# value ou [operator, value]
|
||||||
|
$condprefix = $condsep = $condsuffix = null;
|
||||||
|
if (is_array($cond)) {
|
||||||
|
$condkey = 0;
|
||||||
|
$condkeys = array_keys($cond);
|
||||||
|
$op = null;
|
||||||
|
if (array_key_exists("op", $cond)) {
|
||||||
|
$op = $cond["op"];
|
||||||
|
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||||
|
$op = $cond[$condkeys[$condkey]];
|
||||||
|
$condkey++;
|
||||||
|
}
|
||||||
|
$op = strtolower($op);
|
||||||
|
$condvalues = null;
|
||||||
|
switch ($op) {
|
||||||
|
case "between":
|
||||||
|
# ["between", $upper, $lower]
|
||||||
|
$condsep = " and ";
|
||||||
|
if (array_key_exists("lower", $cond)) {
|
||||||
|
$condvalues[] = $cond["lower"];
|
||||||
|
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||||
|
$condvalues[] = $cond[$condkeys[$condkey]];
|
||||||
|
$condkey++;
|
||||||
|
}
|
||||||
|
if (array_key_exists("upper", $cond)) {
|
||||||
|
$condvalues[] = $cond["upper"];
|
||||||
|
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||||
|
$condvalues[] = $cond[$condkeys[$condkey]];
|
||||||
|
$condkey++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "any":
|
||||||
|
case "all":
|
||||||
|
case "not any":
|
||||||
|
case "not all":
|
||||||
|
# ["list", $values]
|
||||||
|
if ($op === "any" || $op === "all") {
|
||||||
|
$condprefix = $op;
|
||||||
|
$op = "=";
|
||||||
|
} elseif ($op === "not any" || $op === "not all") {
|
||||||
|
$condprefix = substr($op, strlen("not "));
|
||||||
|
$op = "<>";
|
||||||
|
}
|
||||||
|
$condprefix .= "(array[";
|
||||||
|
$condsep = ", ";
|
||||||
|
$condsuffix = "])";
|
||||||
|
$condvalues = null;
|
||||||
|
if (array_key_exists("values", $cond)) {
|
||||||
|
$condvalues = cl::with($cond["values"]);
|
||||||
|
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||||
|
$condvalues = cl::with($cond[$condkeys[$condkey]]);
|
||||||
|
$condkey++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "in":
|
||||||
|
# ["in", $values]
|
||||||
|
$condprefix = "(";
|
||||||
|
$condsep = ", ";
|
||||||
|
$condsuffix = ")";
|
||||||
|
$condvalues = null;
|
||||||
|
if (array_key_exists("values", $cond)) {
|
||||||
|
$condvalues = cl::with($cond["values"]);
|
||||||
|
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||||
|
$condvalues = cl::with($cond[$condkeys[$condkey]]);
|
||||||
|
$condkey++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "null":
|
||||||
|
case "is null":
|
||||||
|
$op = "is null";
|
||||||
|
break;
|
||||||
|
case "not null":
|
||||||
|
case "is not null":
|
||||||
|
$op = "is not null";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (array_key_exists("value", $cond)) {
|
||||||
|
$condvalues = [$cond["value"]];
|
||||||
|
} elseif (array_key_exists($condkey, $condkeys)) {
|
||||||
|
$condvalues = [$cond[$condkeys[$condkey]]];
|
||||||
|
$condkey++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($cond !== null) {
|
||||||
|
$op = "=";
|
||||||
|
$condvalues = [$cond];
|
||||||
|
} else {
|
||||||
|
$op = "is null";
|
||||||
|
$condvalues = null;
|
||||||
|
}
|
||||||
|
$cond = [$key, $op];
|
||||||
|
if ($condvalues !== null) {
|
||||||
|
$parts = [];
|
||||||
|
foreach ($condvalues as $condvalue) {
|
||||||
|
if (is_array($condvalue)) {
|
||||||
|
$first = true;
|
||||||
|
foreach ($condvalue as $value) {
|
||||||
|
if ($first) {
|
||||||
|
$first = false;
|
||||||
|
} else {
|
||||||
|
if ($sep === null) $sep = "and";
|
||||||
|
$parts[] = " $sep ";
|
||||||
|
$parts[] = $key;
|
||||||
|
$parts[] = " $op ";
|
||||||
|
}
|
||||||
|
$param = "$param0$i";
|
||||||
|
$parts[] = ":$param";
|
||||||
|
$bindings[$param] = $value;
|
||||||
|
if ($i === false) $i = 2;
|
||||||
|
else $i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$param = "$param0$i";
|
||||||
|
$parts[] = ":$param";
|
||||||
|
$bindings[$param] = $condvalue;
|
||||||
|
if ($i === false) $i = 2;
|
||||||
|
else $i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cond[] = $condprefix.implode($condsep, $parts).$condsuffix;
|
||||||
|
}
|
||||||
|
$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 &$bindings): 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, $bindings);
|
||||||
|
} else {
|
||||||
|
# paramètre litéral
|
||||||
|
$parts[] = strval($part);
|
||||||
|
}
|
||||||
|
$index++;
|
||||||
|
} else {
|
||||||
|
## associatif
|
||||||
|
# paramètre
|
||||||
|
$param = $param0 = preg_replace('/^.+\./', "", $key);
|
||||||
|
if ($bindings !== null && array_key_exists($param0, $bindings)) {
|
||||||
|
$i = 2;
|
||||||
|
while (array_key_exists("$param0$i", $bindings)) {
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
$param = "$param0$i";
|
||||||
|
}
|
||||||
|
# value
|
||||||
|
$value = $part;
|
||||||
|
$part = [$key, "="];
|
||||||
|
if ($value === null) {
|
||||||
|
$part[] = "null";
|
||||||
|
} else {
|
||||||
|
$part[] = ":$param";
|
||||||
|
$bindings[$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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\pdo;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
use nulib\php\nur_func;
|
use nulib\db\IDatabase;
|
||||||
|
use nulib\php\func;
|
||||||
|
|
||||||
class _config {
|
class _config {
|
||||||
static function with($configs): self {
|
static function with($configs): self {
|
||||||
@ -23,13 +24,12 @@ class _config {
|
|||||||
/** @var array */
|
/** @var array */
|
||||||
protected $configs;
|
protected $configs;
|
||||||
|
|
||||||
function configure(Pdo $pdo): void {
|
function configure(IDatabase $db): void {
|
||||||
foreach ($this->configs as $key => $config) {
|
foreach ($this->configs as $key => $config) {
|
||||||
if (is_string($config) && !nur_func::is_method($config)) {
|
if (is_string($config) && !func::is_method($config)) {
|
||||||
$pdo->exec($config);
|
$db->exec($config);
|
||||||
} else {
|
} else {
|
||||||
nur_func::ensure_func($config, $this, $args);
|
func::with($config)->bind($this)->invoke([$db, $key]);
|
||||||
nur_func::call($config, $pdo, $key, ...$args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _create {
|
class _create extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"table" => "string",
|
"table" => "string",
|
||||||
@ -9,4 +9,46 @@ class _create {
|
|||||||
"cols" => "?array",
|
"cols" => "?array",
|
||||||
"suffix" => "?string",
|
"suffix" => "?string",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static function isa(string $sql): bool {
|
||||||
|
#XXX implémentation minimale
|
||||||
|
return preg_match("/^create(?:\s+table)?\b/i", $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function parse(array $query, ?array &$bindings=null): string {
|
||||||
|
#XXX implémentation minimale
|
||||||
|
$tmpsql = self::merge_seq($query);
|
||||||
|
self::consume('create(?:\s+table)?\b', $tmpsql);
|
||||||
|
$sql = ["create table"];
|
||||||
|
if ($tmpsql) $sql[] = $tmpsql;
|
||||||
|
|
||||||
|
## préfixe
|
||||||
|
$prefix = $query["prefix"] ?? null;
|
||||||
|
if ($prefix !== null) $sql[] = $prefix;
|
||||||
|
|
||||||
|
## table
|
||||||
|
$table = $query["table"] ?? null;
|
||||||
|
if ($table !== null) $sql[] = $table;
|
||||||
|
|
||||||
|
## columns
|
||||||
|
$cols = $query["cols"] ?? null;
|
||||||
|
if ($cols !== null) {
|
||||||
|
$index = 0;
|
||||||
|
foreach ($cols as $col => &$definition) {
|
||||||
|
if ($col === $index) {
|
||||||
|
$index++;
|
||||||
|
} else {
|
||||||
|
$definition = "$col $definition";
|
||||||
|
}
|
||||||
|
}; unset($definition);
|
||||||
|
$sql[] = "(\n ".implode("\n, ", $cols)."\n)";
|
||||||
|
}
|
||||||
|
|
||||||
|
## suffixe
|
||||||
|
$suffix = $query["suffix"] ?? null;
|
||||||
|
if ($suffix !== null) $sql[] = $suffix;
|
||||||
|
|
||||||
|
## fin de la requête
|
||||||
|
return implode(" ", $sql);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,48 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _delete {
|
class _delete extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"from" => "?string",
|
"from" => "?string",
|
||||||
"where" => "?array",
|
"where" => "?array",
|
||||||
"suffix" => "?string",
|
"suffix" => "?string",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static function isa(string $sql): bool {
|
||||||
|
return preg_match("/^delete(?:\s+from)?\b/i", $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
$sql = ["delete from"];
|
||||||
|
if ($tmpsql) $sql[] = $tmpsql;
|
||||||
|
|
||||||
|
## préfixe
|
||||||
|
$prefix = $query["prefix"] ?? null;
|
||||||
|
if ($prefix !== null) $sql[] = $prefix;
|
||||||
|
|
||||||
|
## table
|
||||||
|
$from = $query["from"] ?? null;
|
||||||
|
if ($from !== null) $sql[] = $from;
|
||||||
|
|
||||||
|
## where
|
||||||
|
$where = $query["where"] ?? null;
|
||||||
|
if ($where !== null) {
|
||||||
|
self::parse_conds($where, $wheresql, $bindings);
|
||||||
|
if ($wheresql) {
|
||||||
|
$sql[] = "where";
|
||||||
|
$sql[] = implode(" and ", $wheresql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## suffixe
|
||||||
|
$suffix = $query["suffix"] ?? null;
|
||||||
|
if ($suffix !== null) $sql[] = $suffix;
|
||||||
|
|
||||||
|
## fin de la requête
|
||||||
|
return implode(" ", $sql);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _generic {
|
use nulib\str;
|
||||||
|
|
||||||
|
class _generic extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static function isa(string $sql): bool {
|
||||||
|
return preg_match('/^drop\s+table\b/i', $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function parse(array $query, ?array &$bindings=null): string {
|
||||||
|
$sql = "";
|
||||||
|
foreach ($query as $value) {
|
||||||
|
if ($sql && !str::ends_with(" ", $sql) && !str::starts_with(" ", $value)) {
|
||||||
|
$sql .= " ";
|
||||||
|
}
|
||||||
|
$sql .= $value;
|
||||||
|
}
|
||||||
|
return $sql;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _insert {
|
use nulib\cl;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _insert extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"into" => "?string",
|
"into" => "?string",
|
||||||
@ -10,4 +13,79 @@ class _insert {
|
|||||||
"values" => "?array",
|
"values" => "?array",
|
||||||
"suffix" => "?string",
|
"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 &$bindings=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+or\s+(?:ignore|replace))?)\s*', $tmpsql, $ms);
|
||||||
|
$sql[] = $ms[1];
|
||||||
|
|
||||||
|
## 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";
|
||||||
|
$bindings[$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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
99
php/src/db/_private/_migration.php
Normal file
99
php/src/db/_private/_migration.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\db\IDatabase;
|
||||||
|
use nulib\php\func;
|
||||||
|
|
||||||
|
abstract class _migration {
|
||||||
|
const MIGRATION = null;
|
||||||
|
|
||||||
|
function __construct($migrations, string $channel="", ?IDatabase $db=null) {
|
||||||
|
$this->db = $db;
|
||||||
|
$this->channel = $channel;
|
||||||
|
if ($migrations === null) $migrations = static::MIGRATION;
|
||||||
|
if ($migrations === null) $migrations = [];
|
||||||
|
elseif (is_string($migrations)) $migrations = [$migrations];
|
||||||
|
elseif (is_callable($migrations)) $migrations = [$migrations];
|
||||||
|
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
|
||||||
|
$this->migrations = $migrations;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ?IDatabase $db;
|
||||||
|
|
||||||
|
protected string $channel;
|
||||||
|
|
||||||
|
const MIGRATION_TABLE = "_migration";
|
||||||
|
const MIGRATION_COLS = [
|
||||||
|
"channel" => "varchar not null",
|
||||||
|
"name" => "varchar not null",
|
||||||
|
"done" => "integer not null default 0",
|
||||||
|
"primary key (channel, name)",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function ensureTable(): void {
|
||||||
|
$this->db->exec([
|
||||||
|
"create table if not exists",
|
||||||
|
"table" => static::MIGRATION_TABLE,
|
||||||
|
"cols" => static::MIGRATION_COLS,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isMigrated(string $name): bool {
|
||||||
|
return boolval($this->db->get([
|
||||||
|
"select 1",
|
||||||
|
"from" => static::MIGRATION_TABLE,
|
||||||
|
"where" => [
|
||||||
|
"channel" => $this->channel,
|
||||||
|
"name" => $name,
|
||||||
|
"done" => 1,
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function setMigrated(string $name, bool $done): void;
|
||||||
|
|
||||||
|
/** @var callable[]|string[] */
|
||||||
|
protected $migrations;
|
||||||
|
|
||||||
|
function migrate(?IDatabase $db=null): void {
|
||||||
|
$db = ($this->db ??= $db);
|
||||||
|
$this->ensureTable();
|
||||||
|
foreach ($this->migrations as $name => $migration) {
|
||||||
|
if ($this->isMigrated($name)) continue;
|
||||||
|
$this->setMigrated($name, false);
|
||||||
|
if (func::is_callable($migration)) {
|
||||||
|
func::with($migration)->bind($this)->invoke([$db, $name]);
|
||||||
|
} else {
|
||||||
|
foreach (cl::with($migration) as $query) {
|
||||||
|
$db->exec($query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->setMigrated($name, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function sql_prefix(?string $source=null): string {
|
||||||
|
$prefix = "-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8\n";
|
||||||
|
if ($source !== null) $prefix .= "-- autogénéré à partir de $source\n";
|
||||||
|
return $prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSql(?string $source=null, ?IDatabase $db=null): string {
|
||||||
|
$db = ($this->db ??= $db);
|
||||||
|
$lines = [self::sql_prefix($source)];
|
||||||
|
foreach ($this->migrations as $name => $migration) {
|
||||||
|
$lines[] = "-- $name";
|
||||||
|
if (func::is_callable($migration)) {
|
||||||
|
$lines[] = "-- <fonction PHP>";
|
||||||
|
} else {
|
||||||
|
foreach (cl::with($migration) as $query) {
|
||||||
|
$sql = $db->getSql($query);
|
||||||
|
$lines[] = "$sql;";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$lines[] = "";
|
||||||
|
}
|
||||||
|
return implode("\n", $lines);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _select {
|
use nulib\cl;
|
||||||
|
use nulib\str;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class _select extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"schema" => "?array",
|
"schema" => "?array",
|
||||||
@ -14,4 +18,165 @@ class _select {
|
|||||||
"having" => "?array",
|
"having" => "?array",
|
||||||
"suffix" => "?string",
|
"suffix" => "?string",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static function isa(string $sql): bool {
|
||||||
|
return preg_match("/^select\b/i", $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function add_prefix(?string $col, ?string $prefix): string {
|
||||||
|
$col ??= "null";
|
||||||
|
if ($prefix === null) return $col;
|
||||||
|
if (strpos($col, ".") !== false) return $col;
|
||||||
|
return "$prefix$col";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 &$bindings=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*distinct)?)\s*', $tmpsql, $ms);
|
||||||
|
$sql[] = $ms[1];
|
||||||
|
|
||||||
|
## cols
|
||||||
|
$usercols = [];
|
||||||
|
if (self::consume('(.*?)\s*(?=$|\bfrom\b)', $tmpsql, $ms)) {
|
||||||
|
if ($ms[1]) $usercols[] = $ms[1];
|
||||||
|
}
|
||||||
|
$colPrefix = $query["col_prefix"] ?? null;
|
||||||
|
if ($colPrefix !== null) str::add_suffix($colPrefix, ".");
|
||||||
|
$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[] = self::add_prefix($col, $colPrefix);
|
||||||
|
} else {
|
||||||
|
$cols[] = $key;
|
||||||
|
$usercols[] = self::add_prefix($col, $colPrefix)." as $key";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$cols = null;
|
||||||
|
if ($schema && is_array($schema) && !in_array("*", $usercols)) {
|
||||||
|
$cols = array_keys($schema);
|
||||||
|
foreach ($cols as $col) {
|
||||||
|
$usercols[] = self::add_prefix($col, $colPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$usercols && !$cols) $usercols = [self::add_prefix("*", $colPrefix)];
|
||||||
|
$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, $bindings);
|
||||||
|
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, $bindings);
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\_private;
|
namespace nulib\db\_private;
|
||||||
|
|
||||||
class _update {
|
class _update extends _common {
|
||||||
const SCHEMA = [
|
const SCHEMA = [
|
||||||
"prefix" => "?string",
|
"prefix" => "?string",
|
||||||
"table" => "?string",
|
"table" => "?string",
|
||||||
@ -11,4 +11,43 @@ class _update {
|
|||||||
"where" => "?array",
|
"where" => "?array",
|
||||||
"suffix" => "?string",
|
"suffix" => "?string",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static function isa(string $sql): bool {
|
||||||
|
return preg_match("/^update\b/i", $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function parse(array $query, ?array &$bindings=null): string {
|
||||||
|
#XXX implémentation minimale
|
||||||
|
$sql = [self::merge_seq($query)];
|
||||||
|
|
||||||
|
## préfixe
|
||||||
|
$prefix = $query["prefix"] ?? null;
|
||||||
|
if ($prefix !== null) $sql[] = $prefix;
|
||||||
|
|
||||||
|
## table
|
||||||
|
$table = $query["table"] ?? null;
|
||||||
|
if ($table !== null) $sql[] = $table;
|
||||||
|
|
||||||
|
## set
|
||||||
|
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, $bindings);
|
||||||
|
if ($wheresql) {
|
||||||
|
$sql[] = "where";
|
||||||
|
$sql[] = implode(" and ", $wheresql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
## suffixe
|
||||||
|
$suffix = $query["suffix"] ?? null;
|
||||||
|
if ($suffix !== null) $sql[] = $suffix;
|
||||||
|
|
||||||
|
## fin de la requête
|
||||||
|
return implode(" ", $sql);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
116
php/src/db/cache/CacheChannel.php
vendored
116
php/src/db/cache/CacheChannel.php
vendored
@ -1,116 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\cache;
|
|
||||||
|
|
||||||
use nulib\cl;
|
|
||||||
use nulib\db\CapacitorChannel;
|
|
||||||
use nulib\php\time\DateTime;
|
|
||||||
use nulib\php\time\Delay;
|
|
||||||
|
|
||||||
class CacheChannel extends CapacitorChannel {
|
|
||||||
/** @var int durée de vie par défaut du cache */
|
|
||||||
const DURATION = "1D"; // jusqu'au lendemain
|
|
||||||
|
|
||||||
const INCLUDES = null;
|
|
||||||
|
|
||||||
const EXCLUDES = null;
|
|
||||||
|
|
||||||
const COLUMN_DEFINITIONS = [
|
|
||||||
"group_id" => "varchar(64) not null",
|
|
||||||
"id" => "varchar(64) not null",
|
|
||||||
"date_start" => "datetime",
|
|
||||||
"duration_" => "text",
|
|
||||||
"primary key (group_id, id)",
|
|
||||||
];
|
|
||||||
|
|
||||||
static function get_cache_ids($id): array {
|
|
||||||
if (is_array($id)) {
|
|
||||||
$keys = array_keys($id);
|
|
||||||
if (array_key_exists("group_id", $id)) $groupIdKey = "group_id";
|
|
||||||
else $groupIdKey = $keys[1] ?? null;
|
|
||||||
$groupId = $id[$groupIdKey] ?? "";
|
|
||||||
if (array_key_exists("id", $id)) $idKey = "id";
|
|
||||||
else $idKey = $keys[0] ?? null;
|
|
||||||
$id = $id[$idKey] ?? "";
|
|
||||||
} else {
|
|
||||||
$groupId = "";
|
|
||||||
}
|
|
||||||
if (preg_match('/^(.*\\\\)?([^\\\\]+)$/', $groupId, $ms)) {
|
|
||||||
# si le groupe est une classe, faire un hash du package pour limiter la
|
|
||||||
# longueur du groupe
|
|
||||||
[$package, $groupId] = [$ms[1], $ms[2]];
|
|
||||||
$package = substr(md5($package), 0, 4);
|
|
||||||
$groupId = "${groupId}_$package";
|
|
||||||
}
|
|
||||||
return ["group_id" => $groupId, "id" => $id];
|
|
||||||
}
|
|
||||||
|
|
||||||
function __construct(?string $duration=null, ?string $name=null) {
|
|
||||||
parent::__construct($name);
|
|
||||||
$this->duration = $duration ?? static::DURATION;
|
|
||||||
$this->includes = static::INCLUDES;
|
|
||||||
$this->excludes = static::EXCLUDES;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected string $duration;
|
|
||||||
|
|
||||||
protected ?array $includes;
|
|
||||||
|
|
||||||
protected ?array $excludes;
|
|
||||||
|
|
||||||
function getItemValues($item): ?array {
|
|
||||||
return cl::merge(self::get_cache_ids($item), [
|
|
||||||
"item" => null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCreate($item, array $values, ?array $alwaysNull, ?string $duration=null): ?array {
|
|
||||||
$now = new DateTime();
|
|
||||||
$duration ??= $this->duration;
|
|
||||||
return [
|
|
||||||
"date_start" => $now,
|
|
||||||
"duration" => new Delay($duration, $now),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUpdate($item, array $values, array $pvalues, ?string $duration=null): ?array {
|
|
||||||
$now = new DateTime();
|
|
||||||
$duration ??= $this->duration;
|
|
||||||
return [
|
|
||||||
"date_start" => $now,
|
|
||||||
"duration" => new Delay($duration, $now),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldUpdate($id, bool $noCache=false): bool {
|
|
||||||
if ($noCache) return true;
|
|
||||||
|
|
||||||
$cacheIds = self::get_cache_ids($id);
|
|
||||||
$groupId = $cacheIds["group_id"];
|
|
||||||
if ($groupId) {
|
|
||||||
$includes = $this->includes;
|
|
||||||
$shouldInclude = $includes !== null && in_array($groupId, $includes);
|
|
||||||
$excludes = $this->excludes;
|
|
||||||
$shouldExclude = $excludes !== null && in_array($groupId, $excludes);
|
|
||||||
if (!$shouldInclude || $shouldExclude) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$found = false;
|
|
||||||
$expired = false;
|
|
||||||
$this->each($cacheIds,
|
|
||||||
function($item, $values) use (&$found, &$expired) {
|
|
||||||
$found = true;
|
|
||||||
$expired = $values["duration"]->isElapsed();
|
|
||||||
});
|
|
||||||
return !$found || $expired;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCached($id, ?string $duration=null): void {
|
|
||||||
$cacheIds = self::get_cache_ids($id);
|
|
||||||
$this->charge($cacheIds, null, [$duration]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetCached($id) {
|
|
||||||
$cacheIds = self::get_cache_ids($id);
|
|
||||||
$this->delete($cacheIds);
|
|
||||||
}
|
|
||||||
}
|
|
51
php/src/db/cache/RowsChannel.php
vendored
51
php/src/db/cache/RowsChannel.php
vendored
@ -1,51 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\cache;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use IteratorAggregate;
|
|
||||||
use nulib\cl;
|
|
||||||
use nulib\db\CapacitorChannel;
|
|
||||||
use Traversable;
|
|
||||||
|
|
||||||
class RowsChannel extends CapacitorChannel implements IteratorAggregate {
|
|
||||||
const COLUMN_DEFINITIONS = [
|
|
||||||
"key" => "varchar(128) primary key not null",
|
|
||||||
"all_values" => "mediumtext",
|
|
||||||
];
|
|
||||||
|
|
||||||
function __construct($id, callable $builder, ?string $duration=null) {
|
|
||||||
$this->cacheIds = $cacheIds = CacheChannel::get_cache_ids($id);
|
|
||||||
$this->builder = Closure::fromCallable($builder);
|
|
||||||
$this->duration = $duration;
|
|
||||||
$name = "{$cacheIds["group_id"]}-{$cacheIds["id"]}";
|
|
||||||
parent::__construct($name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected array $cacheIds;
|
|
||||||
|
|
||||||
protected Closure $builder;
|
|
||||||
|
|
||||||
protected ?string $duration = null;
|
|
||||||
|
|
||||||
function getItemValues($item): ?array {
|
|
||||||
$key = array_keys($item)[0];
|
|
||||||
$row = $item[$key];
|
|
||||||
return [
|
|
||||||
"key" => $key,
|
|
||||||
"item" => $row,
|
|
||||||
"all_values" => implode(" ", cl::filter_n(cl::with($row))),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIterator(): Traversable {
|
|
||||||
$cm = cache::get();
|
|
||||||
if ($cm->shouldUpdate($this->cacheIds)) {
|
|
||||||
$this->capacitor->reset();
|
|
||||||
foreach (($this->builder)() as $key => $row) {
|
|
||||||
$this->charge([$key => $row]);
|
|
||||||
}
|
|
||||||
$cm->setCached($this->cacheIds, $this->duration);
|
|
||||||
}
|
|
||||||
return $this->discharge(false);
|
|
||||||
}
|
|
||||||
}
|
|
37
php/src/db/cache/cache.php
vendored
37
php/src/db/cache/cache.php
vendored
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\cache;
|
|
||||||
|
|
||||||
use nulib\db\Capacitor;
|
|
||||||
use nulib\db\CapacitorStorage;
|
|
||||||
use nulib\db\sqlite\SqliteStorage;
|
|
||||||
|
|
||||||
class cache {
|
|
||||||
protected static ?CapacitorStorage $storage = null;
|
|
||||||
|
|
||||||
static function set_storage(CapacitorStorage $storage): CapacitorStorage {
|
|
||||||
return self::$storage = $storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static function get_storage(): CapacitorStorage {
|
|
||||||
return self::$storage ??= new SqliteStorage("");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static ?CacheChannel $channel = null;
|
|
||||||
|
|
||||||
static function set(?CacheChannel $channel): CacheChannel {
|
|
||||||
$channel ??= new CacheChannel();
|
|
||||||
new Capacitor(self::get_storage(), $channel);
|
|
||||||
return self::$channel = $channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
static function get(): CacheChannel {
|
|
||||||
if (self::$channel !== null) return self::$channel;
|
|
||||||
else return self::set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
static function new(?RowsChannel $channel, $id=null, ?callable $builder=null): RowsChannel {
|
|
||||||
$channel ??= new RowsChannel($id, $builder);
|
|
||||||
new Capacitor(self::get_storage(), $channel);
|
|
||||||
return $channel;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\mysql;
|
namespace nulib\db\mysql;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\CapacitorStorage;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
@ -12,8 +13,7 @@ class MysqlStorage extends CapacitorStorage {
|
|||||||
$this->db = Mysql::with($mysql);
|
$this->db = Mysql::with($mysql);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Mysql */
|
protected Mysql $db;
|
||||||
protected $db;
|
|
||||||
|
|
||||||
function db(): Mysql {
|
function db(): Mysql {
|
||||||
return $this->db;
|
return $this->db;
|
||||||
@ -23,21 +23,40 @@ class MysqlStorage extends CapacitorStorage {
|
|||||||
"id_" => "integer primary key auto_increment",
|
"id_" => "integer primary key auto_increment",
|
||||||
];
|
];
|
||||||
|
|
||||||
function _getCreateSql(CapacitorChannel $channel): string {
|
protected function tableExists(string $tableName): bool {
|
||||||
$query = new _query_base($this->_createSql($channel));
|
|
||||||
return self::format_sql($channel, $query->getSql());
|
|
||||||
}
|
|
||||||
|
|
||||||
function _exists(CapacitorChannel $channel): bool {
|
|
||||||
$db = $this->db;
|
$db = $this->db;
|
||||||
$tableName = $db->get([
|
$found = $db->get([
|
||||||
"select table_name from information_schema.tables",
|
"select table_name from information_schema.tables",
|
||||||
"where" => [
|
"where" => [
|
||||||
"table_schema" => $db->getDbname(),
|
"table_schema" => $db->getDbname(),
|
||||||
"table_name" => $channel->getTableName(),
|
"table_name" => $tableName,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
return $tableName !== null;
|
return $found !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const METADATA_COLS = [
|
||||||
|
"name" => "varchar(64) not null primary key",
|
||||||
|
"value" => "varchar(255)",
|
||||||
|
];
|
||||||
|
|
||||||
|
function _getMigration(CapacitorChannel $channel): _mysqlMigration {
|
||||||
|
$migrations = cl::merge([
|
||||||
|
"0init" => [$this->_createSql($channel)],
|
||||||
|
], $channel->getMigration());
|
||||||
|
return new _mysqlMigration($migrations, $channel->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHANNELS_COLS = [
|
||||||
|
"name" => "varchar(255) not null primary key",
|
||||||
|
"table_name" => "varchar(64)",
|
||||||
|
"class_name" => "varchar(255)",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
|
return cl::merge(parent::_addToChannelsSql($channel), [
|
||||||
|
"suffix" => "on duplicate key update name = name",
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function close(): void {
|
function close(): void {
|
||||||
|
31
php/src/db/mysql/_mysqlMigration.php
Normal file
31
php/src/db/mysql/_mysqlMigration.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\mysql;
|
||||||
|
|
||||||
|
use nulib\db\_private\_migration;
|
||||||
|
|
||||||
|
class _mysqlMigration extends _migration {
|
||||||
|
static function with($migration): self {
|
||||||
|
if ($migration instanceof self) return $migration;
|
||||||
|
else return new static($migration);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIGRATION_COLS = [
|
||||||
|
"channel" => "varchar(64) not null",
|
||||||
|
"name" => "varchar(64) not null",
|
||||||
|
"done" => "integer not null default 0",
|
||||||
|
"primary key (channel, name)",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function setMigrated(string $name, bool $done): void {
|
||||||
|
$this->db->exec([
|
||||||
|
"insert",
|
||||||
|
"into" => static::MIGRATION_TABLE,
|
||||||
|
"values" => [
|
||||||
|
"channel" => $this->channel,
|
||||||
|
"name" => $name,
|
||||||
|
"done" => $done? 1: 0,
|
||||||
|
],
|
||||||
|
"suffix" => "on duplicate key update done = :done",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
8
php/src/db/mysql/_mysqlQuery.php
Normal file
8
php/src/db/mysql/_mysqlQuery.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\mysql;
|
||||||
|
|
||||||
|
use nulib\db\pdo\_pdoQuery;
|
||||||
|
|
||||||
|
class _mysqlQuery extends _pdoQuery {
|
||||||
|
const DEBUG_QUERIES = false;
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\mysql;
|
|
||||||
|
|
||||||
use nulib\ValueException;
|
|
||||||
|
|
||||||
class _query_base extends \nulib\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, $bindinds);
|
|
||||||
$meta = ["isa" => "create", "type" => "ddl"];
|
|
||||||
} elseif (_query_select::isa($prefix)) {
|
|
||||||
$sql = _query_select::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "select", "type" => "dql"];
|
|
||||||
} elseif (_query_insert::isa($prefix)) {
|
|
||||||
$sql = _query_insert::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "insert", "type" => "dml"];
|
|
||||||
} elseif (_query_update::isa($prefix)) {
|
|
||||||
$sql = _query_update::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "update", "type" => "dml"];
|
|
||||||
} elseif (_query_delete::isa($prefix)) {
|
|
||||||
$sql = _query_delete::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "delete", "type" => "dml"];
|
|
||||||
} elseif (_query_generic::isa($prefix)) {
|
|
||||||
$sql = _query_generic::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "generic", "type" => null];
|
|
||||||
} else {
|
|
||||||
throw ValueException::invalid_kind($sql, "query");
|
|
||||||
}
|
|
||||||
} 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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\mysql;
|
|
||||||
|
|
||||||
use nulib\db\_private\_create;
|
|
||||||
use nulib\db\_private\Tcreate;
|
|
||||||
|
|
||||||
class _query_create extends _query_base {
|
|
||||||
use Tcreate;
|
|
||||||
const SCHEMA = _create::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\mysql;
|
|
||||||
|
|
||||||
use nulib\db\_private\_delete;
|
|
||||||
use nulib\db\_private\Tdelete;
|
|
||||||
|
|
||||||
class _query_delete extends _query_base {
|
|
||||||
use Tdelete;
|
|
||||||
const SCHEMA = _delete::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\mysql;
|
|
||||||
|
|
||||||
use nulib\db\_private\_generic;
|
|
||||||
use nulib\db\_private\Tgeneric;
|
|
||||||
|
|
||||||
class _query_generic extends _query_base {
|
|
||||||
use Tgeneric;
|
|
||||||
const SCHEMA = _generic::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\mysql;
|
|
||||||
|
|
||||||
use nulib\db\_private\_insert;
|
|
||||||
use nulib\db\_private\Tinsert;
|
|
||||||
|
|
||||||
class _query_insert extends _query_base {
|
|
||||||
use Tinsert;
|
|
||||||
const SCHEMA = _insert::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\mysql;
|
|
||||||
|
|
||||||
use nulib\db\_private\_select;
|
|
||||||
use nulib\db\_private\Tselect;
|
|
||||||
|
|
||||||
class _query_select extends _query_base {
|
|
||||||
use Tselect;
|
|
||||||
const SCHEMA = _select::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\mysql;
|
|
||||||
|
|
||||||
use nulib\db\_private\_update;
|
|
||||||
use nulib\db\_private\Tupdate;
|
|
||||||
|
|
||||||
class _query_update extends _query_base {
|
|
||||||
use Tupdate;
|
|
||||||
const SCHEMA = _update::SCHEMA;
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\mysql;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class query: classe outil temporaire pour générer les requêtes
|
|
||||||
*/
|
|
||||||
class query extends _query_base {
|
|
||||||
static function with($sql, ?array $params=null): array {
|
|
||||||
self::verifix($sql, $params);
|
|
||||||
return [$sql, $params];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\pdo;
|
namespace nulib\db\pdo;
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_config;
|
||||||
use nulib\db\_private\Tvalues;
|
use nulib\db\_private\Tvalues;
|
||||||
use nulib\db\IDatabase;
|
use nulib\db\IDatabase;
|
||||||
use nulib\db\ITransactor;
|
use nulib\db\ITransactor;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
|
|
||||||
class Pdo implements IDatabase {
|
class Pdo implements IDatabase {
|
||||||
@ -21,7 +21,7 @@ class Pdo implements IDatabase {
|
|||||||
"dbconn" => $pdo->dbconn,
|
"dbconn" => $pdo->dbconn,
|
||||||
"options" => $pdo->options,
|
"options" => $pdo->options,
|
||||||
"config" => $pdo->config,
|
"config" => $pdo->config,
|
||||||
"migrate" => $pdo->migration,
|
"migration" => $pdo->migration,
|
||||||
], $params));
|
], $params));
|
||||||
} else {
|
} else {
|
||||||
return new static($pdo, $params);
|
return new static($pdo, $params);
|
||||||
@ -49,7 +49,7 @@ class Pdo implements IDatabase {
|
|||||||
|
|
||||||
protected const CONFIG = null;
|
protected const CONFIG = null;
|
||||||
|
|
||||||
protected const MIGRATE = null;
|
protected const MIGRATION = null;
|
||||||
|
|
||||||
const dbconn_SCHEMA = [
|
const dbconn_SCHEMA = [
|
||||||
"name" => "string",
|
"name" => "string",
|
||||||
@ -62,7 +62,7 @@ class Pdo implements IDatabase {
|
|||||||
"options" => ["?array|callable"],
|
"options" => ["?array|callable"],
|
||||||
"replace_config" => ["?array|callable"],
|
"replace_config" => ["?array|callable"],
|
||||||
"config" => ["?array|callable"],
|
"config" => ["?array|callable"],
|
||||||
"migrate" => ["?array|string|callable"],
|
"migration" => ["?array|string|callable"],
|
||||||
"auto_open" => ["bool", true],
|
"auto_open" => ["bool", true],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ class Pdo implements IDatabase {
|
|||||||
}
|
}
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
# migrations
|
# migrations
|
||||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||||
#
|
#
|
||||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||||
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||||
@ -104,7 +104,7 @@ class Pdo implements IDatabase {
|
|||||||
protected ?array $dbconn;
|
protected ?array $dbconn;
|
||||||
|
|
||||||
/** @var array|callable */
|
/** @var array|callable */
|
||||||
protected array $options;
|
protected $options;
|
||||||
|
|
||||||
/** @var array|string|callable */
|
/** @var array|string|callable */
|
||||||
protected $config;
|
protected $config;
|
||||||
@ -114,13 +114,17 @@ class Pdo implements IDatabase {
|
|||||||
|
|
||||||
protected ?\PDO $db = null;
|
protected ?\PDO $db = null;
|
||||||
|
|
||||||
|
function getSql($query, ?array $params=null): string {
|
||||||
|
$query = new _pdoQuery($query, $params);
|
||||||
|
return $query->getSql();
|
||||||
|
}
|
||||||
|
|
||||||
function open(): self {
|
function open(): self {
|
||||||
if ($this->db === null) {
|
if ($this->db === null) {
|
||||||
$dbconn = $this->dbconn;
|
$dbconn = $this->dbconn;
|
||||||
$options = $this->options;
|
$options = $this->options;
|
||||||
if (is_callable($options)) {
|
if (is_callable($options)) {
|
||||||
nur_func::ensure_func($options, $this, $args);
|
$options = func::with($options)->bind($this)->invoke();
|
||||||
$options = nur_func::call($options, ...$args);
|
|
||||||
}
|
}
|
||||||
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
$this->db = new \PDO($dbconn["name"], $dbconn["user"], $dbconn["pass"], $options);
|
||||||
_config::with($this->config)->configure($this);
|
_config::with($this->config)->configure($this);
|
||||||
@ -143,21 +147,16 @@ class Pdo implements IDatabase {
|
|||||||
return $this->db()->exec($query);
|
return $this->db()->exec($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function is_insert(?string $sql): bool {
|
|
||||||
if ($sql === null) return false;
|
|
||||||
return preg_match('/^\s*insert\b/i', $sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exec($query, ?array $params=null) {
|
function exec($query, ?array $params=null) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return false;
|
if ($stmt->execute() === false) return false;
|
||||||
if ($query->isInsert()) return $db->lastInsertId();
|
if ($query->isInsert()) return $db->lastInsertId();
|
||||||
else return $stmt->rowCount();
|
else return $stmt->rowCount();
|
||||||
} else {
|
} else {
|
||||||
$rowCount = $db->exec($sql);
|
$rowCount = $db->exec($sql);
|
||||||
if (self::is_insert($sql)) return $db->lastInsertId();
|
if ($query->isInsert()) return $db->lastInsertId();
|
||||||
else return $rowCount;
|
else return $rowCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +190,7 @@ class Pdo implements IDatabase {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -222,11 +221,11 @@ class Pdo implements IDatabase {
|
|||||||
|
|
||||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
$stmt = null;
|
$stmt = null;
|
||||||
try {
|
try {
|
||||||
/** @var \PDOStatement $stmt */
|
/** @var \PDOStatement $stmt */
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return null;
|
if ($stmt->execute() === false) return null;
|
||||||
} else {
|
} else {
|
||||||
$stmt = $db->query($sql);
|
$stmt = $db->query($sql);
|
||||||
@ -245,22 +244,18 @@ class Pdo implements IDatabase {
|
|||||||
return $this->get($query, $params, true);
|
return $this->get($query, $params, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||||
* 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();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _pdoQuery($query, $params);
|
||||||
$stmt = null;
|
$stmt = null;
|
||||||
try {
|
try {
|
||||||
/** @var \PDOStatement $stmt */
|
/** @var \PDOStatement $stmt */
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
if ($stmt->execute() === false) return;
|
if ($stmt->execute() === false) return;
|
||||||
} else {
|
} else {
|
||||||
$stmt = $db->query($sql);
|
$stmt = $db->query($sql);
|
||||||
}
|
}
|
||||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
$primaryKeys = cl::withn($primaryKeys);
|
||||||
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
while (($row = $stmt->fetch(\PDO::FETCH_ASSOC)) !== false) {
|
||||||
$this->verifixRow($row);
|
$this->verifixRow($row);
|
||||||
if ($primaryKeys !== null) {
|
if ($primaryKeys !== null) {
|
||||||
|
30
php/src/db/pdo/_pdoQuery.php
Normal file
30
php/src/db/pdo/_pdoQuery.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\pdo;
|
||||||
|
|
||||||
|
use nulib\db\_private\_base;
|
||||||
|
use nulib\db\_private\Tbindings;
|
||||||
|
use nulib\output\msg;
|
||||||
|
|
||||||
|
class _pdoQuery extends _base {
|
||||||
|
use Tbindings;
|
||||||
|
|
||||||
|
const DEBUG_QUERIES = false;
|
||||||
|
|
||||||
|
function _use_stmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
|
||||||
|
if (static::DEBUG_QUERIES) {#XXX
|
||||||
|
msg::info($this->sql);
|
||||||
|
//msg::info(var_export($this->bindings, true));
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\pdo;
|
|
||||||
|
|
||||||
use nulib\db\_private\_base;
|
|
||||||
use nulib\db\_private\Tbindings;
|
|
||||||
use nulib\ValueException;
|
|
||||||
|
|
||||||
class _query_base extends _base {
|
|
||||||
use Tbindings;
|
|
||||||
|
|
||||||
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, $bindinds);
|
|
||||||
$meta = ["isa" => "create", "type" => "ddl"];
|
|
||||||
} elseif (_query_select::isa($prefix)) {
|
|
||||||
$sql = _query_select::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "select", "type" => "dql"];
|
|
||||||
} elseif (_query_insert::isa($prefix)) {
|
|
||||||
$sql = _query_insert::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "insert", "type" => "dml"];
|
|
||||||
} elseif (_query_update::isa($prefix)) {
|
|
||||||
$sql = _query_update::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "update", "type" => "dml"];
|
|
||||||
} elseif (_query_delete::isa($prefix)) {
|
|
||||||
$sql = _query_delete::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "delete", "type" => "dml"];
|
|
||||||
} elseif (_query_generic::isa($prefix)) {
|
|
||||||
$sql = _query_generic::parse($sql, $bindinds);
|
|
||||||
$meta = ["isa" => "generic", "type" => null];
|
|
||||||
} else {
|
|
||||||
throw ValueException::invalid_kind($sql, "query");
|
|
||||||
}
|
|
||||||
} 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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEBUG_QUERIES = false;
|
|
||||||
|
|
||||||
function useStmt(\PDO $db, ?\PDOStatement &$stmt=null, ?string &$sql=null): bool {
|
|
||||||
if (static::DEBUG_QUERIES) { #XXX
|
|
||||||
error_log($this->sql);
|
|
||||||
//error_log(var_export($this->bindings, true));
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\pdo;
|
|
||||||
|
|
||||||
use nulib\db\_private\_create;
|
|
||||||
use nulib\db\_private\Tcreate;
|
|
||||||
|
|
||||||
class _query_create extends _query_base {
|
|
||||||
use Tcreate;
|
|
||||||
const SCHEMA = _create::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\pdo;
|
|
||||||
|
|
||||||
use nulib\db\_private\_delete;
|
|
||||||
use nulib\db\_private\Tdelete;
|
|
||||||
|
|
||||||
class _query_delete extends _query_base {
|
|
||||||
use Tdelete;
|
|
||||||
const SCHEMA = _delete::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\pdo;
|
|
||||||
|
|
||||||
use nulib\db\_private\_generic;
|
|
||||||
use nulib\db\_private\Tgeneric;
|
|
||||||
|
|
||||||
class _query_generic extends _query_base {
|
|
||||||
use Tgeneric;
|
|
||||||
const SCHEMA = _generic::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\pdo;
|
|
||||||
|
|
||||||
use nulib\db\_private\_insert;
|
|
||||||
use nulib\db\_private\Tinsert;
|
|
||||||
|
|
||||||
class _query_insert extends _query_base {
|
|
||||||
use Tinsert;
|
|
||||||
const SCHEMA = _insert::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\pdo;
|
|
||||||
|
|
||||||
use nulib\db\_private\_select;
|
|
||||||
use nulib\db\_private\Tselect;
|
|
||||||
|
|
||||||
class _query_select extends _query_base {
|
|
||||||
use Tselect;
|
|
||||||
const SCHEMA = _select::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\pdo;
|
|
||||||
|
|
||||||
use nulib\db\_private\_update;
|
|
||||||
use nulib\db\_private\Tupdate;
|
|
||||||
|
|
||||||
class _query_update extends _query_base {
|
|
||||||
use Tupdate;
|
|
||||||
const SCHEMA = _update::SCHEMA;
|
|
||||||
}
|
|
309
php/src/db/pgsql/Pgsql.php
Normal file
309
php/src/db/pgsql/Pgsql.php
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\pgsql;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_config;
|
||||||
|
use nulib\db\_private\Tvalues;
|
||||||
|
use nulib\db\IDatabase;
|
||||||
|
use nulib\db\ITransactor;
|
||||||
|
use nulib\php\func;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class Pgsql implements IDatabase {
|
||||||
|
use Tvalues;
|
||||||
|
|
||||||
|
static function with($pgsql, ?array $params=null): self {
|
||||||
|
if ($pgsql instanceof static) {
|
||||||
|
return $pgsql;
|
||||||
|
} elseif ($pgsql instanceof self) {
|
||||||
|
# recréer avec les mêmes paramètres
|
||||||
|
return new static(null, cl::merge([
|
||||||
|
"dbconn" => $pgsql->dbconn,
|
||||||
|
"options" => $pgsql->options,
|
||||||
|
"config" => $pgsql->config,
|
||||||
|
"migration" => $pgsql->migration,
|
||||||
|
], $params));
|
||||||
|
} else {
|
||||||
|
return new static($pgsql, $params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected const OPTIONS = [
|
||||||
|
# XXX désactiver les connexions persistantes par défaut
|
||||||
|
# pour réactiver par défaut, il faudrait vérifier la connexion à chaque fois
|
||||||
|
# qu'elle est ouverte avec un "select 1". en effet, l'expérience jusqu'ici
|
||||||
|
# est que la première connexion après un long timeout échoue
|
||||||
|
"persistent" => false,
|
||||||
|
"force_new" => false,
|
||||||
|
"serial_support" => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
const CONFIG = null;
|
||||||
|
|
||||||
|
const MIGRATION = null;
|
||||||
|
|
||||||
|
const params_SCHEMA = [
|
||||||
|
"dbconn" => ["array"],
|
||||||
|
"options" => ["?array|callable"],
|
||||||
|
"replace_config" => ["?array|callable"],
|
||||||
|
"config" => ["?array|callable"],
|
||||||
|
"migration" => ["?array|string|callable"],
|
||||||
|
"auto_open" => ["bool", true],
|
||||||
|
];
|
||||||
|
|
||||||
|
const dbconn_SCHEMA = [
|
||||||
|
"" => "?string",
|
||||||
|
"host" => "string",
|
||||||
|
"hostaddr" => "?string",
|
||||||
|
"port" => "?int",
|
||||||
|
"dbname" => "string",
|
||||||
|
"user" => "string",
|
||||||
|
"password" => "string",
|
||||||
|
"connect_timeout" => "?int",
|
||||||
|
"options" => "?string",
|
||||||
|
"sslmode" => "?string",
|
||||||
|
"service" => "?string",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected const dbconn_MAP = [
|
||||||
|
"name" => "dbname",
|
||||||
|
"pass" => "password",
|
||||||
|
];
|
||||||
|
|
||||||
|
const options_SCHEMA = [
|
||||||
|
"persistent" => ["bool", self::OPTIONS["persistent"]],
|
||||||
|
"force_new" => ["bool", self::OPTIONS["force_new"]],
|
||||||
|
];
|
||||||
|
|
||||||
|
function __construct($dbconn=null, ?array $params=null) {
|
||||||
|
if ($dbconn !== null) {
|
||||||
|
if (!is_array($dbconn)) {
|
||||||
|
$dbconn = ["" => $dbconn];
|
||||||
|
#XXX à terme, il faudra interroger config
|
||||||
|
#$tmp = config::db($dbconn);
|
||||||
|
#if ($tmp !== null) $dbconn = $tmp;
|
||||||
|
#else $dbconn = ["" => $dbconn];
|
||||||
|
}
|
||||||
|
unset($dbconn["type"]);
|
||||||
|
$name = $dbconn["name"] ?? null;
|
||||||
|
if ($name !== null) {
|
||||||
|
$dbconn[""] = $name;
|
||||||
|
unset($dbconn["name"]);
|
||||||
|
}
|
||||||
|
$params["dbconn"] = $dbconn;
|
||||||
|
}
|
||||||
|
# dbconn
|
||||||
|
$this->dbconn = $params["dbconn"] ?? null;
|
||||||
|
# options
|
||||||
|
$this->options = $params["options"] ?? static::OPTIONS;
|
||||||
|
# configuration
|
||||||
|
$config = $params["replace_config"] ?? null;
|
||||||
|
if ($config === null) {
|
||||||
|
$config = $params["config"] ?? static::CONFIG;
|
||||||
|
if (is_callable($config)) $config = [$config];
|
||||||
|
}
|
||||||
|
$this->config = $config;
|
||||||
|
# migrations
|
||||||
|
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||||
|
#
|
||||||
|
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||||
|
if ($params["auto_open"] ?? $defaultAutoOpen) {
|
||||||
|
$this->open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ?array $dbconn;
|
||||||
|
|
||||||
|
/** @var array|callable|null */
|
||||||
|
protected $options;
|
||||||
|
|
||||||
|
/** @var array|string|callable */
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/** @var array|string|callable */
|
||||||
|
protected $migration;
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
protected $db = null;
|
||||||
|
|
||||||
|
function getSql($query, ?array $params=null): string {
|
||||||
|
$query = new _pgsqlQuery($query, $params);
|
||||||
|
return $query->getSql();
|
||||||
|
}
|
||||||
|
|
||||||
|
function open(): self {
|
||||||
|
if ($this->db === null) {
|
||||||
|
$dbconn = $this->dbconn;
|
||||||
|
$connection_string = [$dbconn[""] ?? null];
|
||||||
|
unset($dbconn[""]);
|
||||||
|
foreach ($dbconn as $key => $value) {
|
||||||
|
if ($value === null) continue;
|
||||||
|
$value = strval($value);
|
||||||
|
if ($value === "" || preg_match("/[ '\\\\]/", $value)) {
|
||||||
|
$value = str_replace("\\", "\\\\", $value);
|
||||||
|
$value = str_replace("'", "\\'", $value);
|
||||||
|
$value = "'$value'";
|
||||||
|
}
|
||||||
|
$key = cl::get(self::dbconn_MAP, $key, $key);
|
||||||
|
$connection_string[] = "$key=$value";
|
||||||
|
}
|
||||||
|
$connection_string = implode(" ", array_filter($connection_string));
|
||||||
|
$options = $this->options;
|
||||||
|
if (is_callable($options)) {
|
||||||
|
$options = func::with($options)->bind($this)->invoke();
|
||||||
|
}
|
||||||
|
$forceNew = $options["force_new"] ?? false;
|
||||||
|
$flags = $forceNew? PGSQL_CONNECT_FORCE_NEW: 0;
|
||||||
|
|
||||||
|
if ($options["persistent"] ?? true) $db = pg_pconnect($connection_string, $flags);
|
||||||
|
else $db = pg_connect($connection_string, $flags);
|
||||||
|
if ($db === false) throw new PgsqlException("unable to connect");
|
||||||
|
$this->db = $db;
|
||||||
|
|
||||||
|
_config::with($this->config)->configure($this);
|
||||||
|
//_migration::with($this->migration)->migrate($this);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): self {
|
||||||
|
if ($this->db !== null) {
|
||||||
|
pg_close($this->db);
|
||||||
|
$this->db = null;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function db() {
|
||||||
|
$this->open();
|
||||||
|
return $this->db;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _exec(string $query): bool {
|
||||||
|
$result = pg_query($this->db(), $query);
|
||||||
|
if ($result === false) return false;
|
||||||
|
pg_free_result($result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastSerial() {
|
||||||
|
$db = $this->db();
|
||||||
|
$result = @pg_query($db, "select lastval()");
|
||||||
|
if ($result === false) return false;
|
||||||
|
$lastSerial = pg_fetch_row($result)[0];
|
||||||
|
pg_free_result($result);
|
||||||
|
return $lastSerial;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exec($query, ?array $params=null) {
|
||||||
|
$db = $this->db();
|
||||||
|
$query = new _pgsqlQuery($query, $params);
|
||||||
|
$result = $query->_exec($db);
|
||||||
|
$serialSupport = $this->options["serial_support"] ?? true;
|
||||||
|
if ($serialSupport && $query->isInsert()) return $this->getLastSerial();
|
||||||
|
$affected_rows = pg_affected_rows($result);
|
||||||
|
pg_free_result($result);
|
||||||
|
return $affected_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var ITransactor[] */
|
||||||
|
protected ?array $transactors = null;
|
||||||
|
|
||||||
|
function willUpdate(...$transactors): self {
|
||||||
|
foreach ($transactors as $transactor) {
|
||||||
|
if ($transactor instanceof ITransactor) {
|
||||||
|
$this->transactors[] = $transactor;
|
||||||
|
$transactor->willUpdate();
|
||||||
|
} else {
|
||||||
|
throw ValueException::invalid_type($transactor, ITransactor::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inTransaction(?bool &$inerror=null): bool {
|
||||||
|
$status = pg_transaction_status($this->db());
|
||||||
|
if ($status === PGSQL_TRANSACTION_ACTIVE || $status === PGSQL_TRANSACTION_INTRANS) {
|
||||||
|
$inerror = false;
|
||||||
|
return true;
|
||||||
|
} elseif ($status === PGSQL_TRANSACTION_INERROR) {
|
||||||
|
$inerror = true;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
||||||
|
$this->_exec("begin");
|
||||||
|
if ($this->transactors !== null) {
|
||||||
|
foreach ($this->transactors as $transactor) {
|
||||||
|
$transactor->beginTransaction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($func !== null) {
|
||||||
|
$commited = false;
|
||||||
|
try {
|
||||||
|
func::call($func, $this);
|
||||||
|
if ($commit) {
|
||||||
|
$this->commit();
|
||||||
|
$commited = true;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if ($commit && !$commited) $this->rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit(): void {
|
||||||
|
$this->_exec("commit");
|
||||||
|
if ($this->transactors !== null) {
|
||||||
|
foreach ($this->transactors as $transactor) {
|
||||||
|
$transactor->commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rollback(): void {
|
||||||
|
$this->_exec("rollback");
|
||||||
|
if ($this->transactors !== null) {
|
||||||
|
foreach ($this->transactors as $transactor) {
|
||||||
|
$transactor->rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||||
|
$db = $this->db();
|
||||||
|
$query = new _pgsqlQuery($query, $params);
|
||||||
|
$result = $query->_exec($db);
|
||||||
|
$row = pg_fetch_assoc($result);
|
||||||
|
pg_free_result($result);
|
||||||
|
if ($row === false) return null;
|
||||||
|
$this->verifixRow($row);
|
||||||
|
if ($entireRow) return $row;
|
||||||
|
else return cl::first($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function one($query, ?array $params=null): ?array {
|
||||||
|
return $this->get($query, $params, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||||
|
$db = $this->db();
|
||||||
|
$query = new _pgsqlQuery($query, $params);
|
||||||
|
$result = $query->_exec($db);
|
||||||
|
$primaryKeys = cl::withn($primaryKeys);
|
||||||
|
while (($row = pg_fetch_assoc($result)) !== false) {
|
||||||
|
$this->verifixRow($row);
|
||||||
|
if ($primaryKeys !== null) {
|
||||||
|
$key = implode("-", cl::select($row, $primaryKeys));
|
||||||
|
yield $key => $row;
|
||||||
|
} else {
|
||||||
|
yield $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pg_free_result($result);
|
||||||
|
}
|
||||||
|
}
|
15
php/src/db/pgsql/PgsqlException.php
Normal file
15
php/src/db/pgsql/PgsqlException.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\pgsql;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class PgsqlException extends RuntimeException {
|
||||||
|
static final function last_error($db): self {
|
||||||
|
return new static(pg_last_error($db));
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function wrap(Exception $e): self {
|
||||||
|
return new static($e->getMessage(), $e->getCode(), $e);
|
||||||
|
}
|
||||||
|
}
|
60
php/src/db/pgsql/PgsqlStorage.php
Normal file
60
php/src/db/pgsql/PgsqlStorage.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\pgsql;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use nulib\db\CapacitorChannel;
|
||||||
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
|
class PgsqlStorage extends CapacitorStorage {
|
||||||
|
const SERDATA_DEFINITION = "text";
|
||||||
|
const SERSUM_DEFINITION = "varchar(40)";
|
||||||
|
const SERTS_DEFINITION = "timestamp";
|
||||||
|
|
||||||
|
function __construct($pgsql) {
|
||||||
|
$this->db = Pgsql::with($pgsql);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Pgsql $db;
|
||||||
|
|
||||||
|
function db(): Pgsql {
|
||||||
|
return $this->db;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRIMARY_KEY_DEFINITION = [
|
||||||
|
"id_" => "serial primary key",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function tableExists(string $tableName): bool {
|
||||||
|
if (($index = strpos($tableName, ".")) !== false) {
|
||||||
|
$schemaName = substr($tableName, 0, $index);
|
||||||
|
$tableName = substr($tableName, $index + 1);
|
||||||
|
} else {
|
||||||
|
$schemaName = "public";
|
||||||
|
}
|
||||||
|
$found = $this->db->get([
|
||||||
|
"select tablename from pg_tables",
|
||||||
|
"where" => [
|
||||||
|
"schemaname" => $schemaName,
|
||||||
|
"tablename" => $tableName,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
return $found !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
|
||||||
|
$migrations = cl::merge([
|
||||||
|
"0init" => [$this->_createSql($channel)],
|
||||||
|
], $channel->getMigration());
|
||||||
|
return new _pgsqlMigration($migrations, $channel->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
|
return cl::merge(parent::_addToChannelsSql($channel), [
|
||||||
|
"suffix" => "on conflict (name) do nothing",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
$this->db->close();
|
||||||
|
}
|
||||||
|
}
|
24
php/src/db/pgsql/_pgsqlMigration.php
Normal file
24
php/src/db/pgsql/_pgsqlMigration.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\pgsql;
|
||||||
|
|
||||||
|
use nulib\db\_private\_migration;
|
||||||
|
|
||||||
|
class _pgsqlMigration extends _migration {
|
||||||
|
static function with($migration): self {
|
||||||
|
if ($migration instanceof self) return $migration;
|
||||||
|
else return new static($migration);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setMigrated(string $name, bool $done): void {
|
||||||
|
$this->db->exec([
|
||||||
|
"insert",
|
||||||
|
"into" => static::MIGRATION_TABLE,
|
||||||
|
"values" => [
|
||||||
|
"channel" => $this->channel,
|
||||||
|
"name" => $name,
|
||||||
|
"done" => $done? 1: 0,
|
||||||
|
],
|
||||||
|
"suffix" => "on conflict (channel, name) do update set done = :done",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
44
php/src/db/pgsql/_pgsqlQuery.php
Normal file
44
php/src/db/pgsql/_pgsqlQuery.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\pgsql;
|
||||||
|
|
||||||
|
use nulib\cv;
|
||||||
|
use nulib\db\_private\_base;
|
||||||
|
use nulib\db\_private\Tbindings;
|
||||||
|
use nulib\output\msg;
|
||||||
|
|
||||||
|
class _pgsqlQuery extends _base {
|
||||||
|
use Tbindings;
|
||||||
|
|
||||||
|
const DEBUG_QUERIES = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource
|
||||||
|
*/
|
||||||
|
function _exec($db) {
|
||||||
|
$sql = $this->sql;
|
||||||
|
$bindings = $this->bindings;
|
||||||
|
if (static::DEBUG_QUERIES) {#XXX
|
||||||
|
msg::info($sql);
|
||||||
|
//msg::info(var_export($bindings, true));
|
||||||
|
}
|
||||||
|
if ($bindings !== null) {
|
||||||
|
# trier d'abord les champ par ordre de longueur, pour éviter les overlaps
|
||||||
|
$names = array_keys($bindings);
|
||||||
|
usort($names, function ($a, $b) {
|
||||||
|
return -cv::compare(strlen(strval($a)), strlen(strval($b)));
|
||||||
|
});
|
||||||
|
$bparams = [];
|
||||||
|
$number = 1;
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$sql = str_replace(":$name", "\$$number", $sql);
|
||||||
|
$bparams[] = $bindings[$name];
|
||||||
|
$number++;
|
||||||
|
}
|
||||||
|
$result = pg_query_params($db, $sql, $bparams);
|
||||||
|
} else {
|
||||||
|
$result = pg_query($db, $sql);
|
||||||
|
}
|
||||||
|
if ($result === false) throw PgsqlException::last_error($db);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,11 @@ namespace nulib\db\sqlite;
|
|||||||
|
|
||||||
use Generator;
|
use Generator;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nulib\db\_private\_config;
|
||||||
use nulib\db\_private\Tvalues;
|
use nulib\db\_private\Tvalues;
|
||||||
use nulib\db\IDatabase;
|
use nulib\db\IDatabase;
|
||||||
use nulib\db\ITransactor;
|
use nulib\db\ITransactor;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use SQLite3;
|
use SQLite3;
|
||||||
use SQLite3Result;
|
use SQLite3Result;
|
||||||
@ -29,7 +30,7 @@ class Sqlite implements IDatabase {
|
|||||||
"encryption_key" => $sqlite->encryptionKey,
|
"encryption_key" => $sqlite->encryptionKey,
|
||||||
"allow_wal" => $sqlite->allowWal,
|
"allow_wal" => $sqlite->allowWal,
|
||||||
"config" => $sqlite->config,
|
"config" => $sqlite->config,
|
||||||
"migrate" => $sqlite->migration,
|
"migration" => $sqlite->migration,
|
||||||
], $params));
|
], $params));
|
||||||
} elseif (is_array($sqlite)) {
|
} elseif (is_array($sqlite)) {
|
||||||
return new static(null, cl::merge($sqlite, $params));
|
return new static(null, cl::merge($sqlite, $params));
|
||||||
@ -71,7 +72,7 @@ class Sqlite implements IDatabase {
|
|||||||
|
|
||||||
const CONFIG = null;
|
const CONFIG = null;
|
||||||
|
|
||||||
const MIGRATE = null;
|
const MIGRATION = null;
|
||||||
|
|
||||||
const params_SCHEMA = [
|
const params_SCHEMA = [
|
||||||
"file" => ["string", ""],
|
"file" => ["string", ""],
|
||||||
@ -80,7 +81,7 @@ class Sqlite implements IDatabase {
|
|||||||
"allow_wal" => ["?bool"],
|
"allow_wal" => ["?bool"],
|
||||||
"replace_config" => ["?array|callable"],
|
"replace_config" => ["?array|callable"],
|
||||||
"config" => ["?array|callable"],
|
"config" => ["?array|callable"],
|
||||||
"migrate" => ["?array|string|callable"],
|
"migration" => ["?array|string|callable"],
|
||||||
"auto_open" => ["bool", true],
|
"auto_open" => ["bool", true],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ class Sqlite implements IDatabase {
|
|||||||
}
|
}
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
# migrations
|
# migrations
|
||||||
$this->migration = $params["migrate"] ?? static::MIGRATE;
|
$this->migration = $params["migration"] ?? static::MIGRATION;
|
||||||
#
|
#
|
||||||
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
$defaultAutoOpen = self::params_SCHEMA["auto_open"][1];
|
||||||
$this->inTransaction = false;
|
$this->inTransaction = false;
|
||||||
@ -145,11 +146,16 @@ class Sqlite implements IDatabase {
|
|||||||
|
|
||||||
protected bool $inTransaction;
|
protected bool $inTransaction;
|
||||||
|
|
||||||
|
function getSql($query, ?array $params=null): string {
|
||||||
|
$query = new _sqliteQuery($query, $params);
|
||||||
|
return $query->getSql();
|
||||||
|
}
|
||||||
|
|
||||||
function open(): self {
|
function open(): self {
|
||||||
if ($this->db === null) {
|
if ($this->db === null) {
|
||||||
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
$this->db = new SQLite3($this->file, $this->flags, $this->encryptionKey);
|
||||||
_config::with($this->config)->configure($this);
|
_config::with($this->config)->configure($this);
|
||||||
_migration::with($this->migration)->migrate($this);
|
_sqliteMigration::with($this->migration)->migrate($this);
|
||||||
$this->inTransaction = false;
|
$this->inTransaction = false;
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
@ -180,15 +186,10 @@ class Sqlite implements IDatabase {
|
|||||||
return $this->db()->exec($query);
|
return $this->db()->exec($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function is_insert(?string $sql): bool {
|
|
||||||
if ($sql === null) return false;
|
|
||||||
return preg_match('/^\s*insert\b/i', $sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exec($query, ?array $params=null) {
|
function exec($query, ?array $params=null) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
try {
|
try {
|
||||||
$result = $stmt->execute();
|
$result = $stmt->execute();
|
||||||
if ($result === false) return false;
|
if ($result === false) return false;
|
||||||
@ -201,7 +202,7 @@ class Sqlite implements IDatabase {
|
|||||||
} else {
|
} else {
|
||||||
$result = $db->exec($sql);
|
$result = $db->exec($sql);
|
||||||
if ($result === false) return false;
|
if ($result === false) return false;
|
||||||
if (self::is_insert($sql)) return $db->lastInsertRowID();
|
if ($query->isInsert()) return $db->lastInsertRowID();
|
||||||
else return $db->changes();
|
else return $db->changes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,7 +238,7 @@ class Sqlite implements IDatabase {
|
|||||||
if ($func !== null) {
|
if ($func !== null) {
|
||||||
$commited = false;
|
$commited = false;
|
||||||
try {
|
try {
|
||||||
nur_func::call($func, $this);
|
func::call($func, $this);
|
||||||
if ($commit) {
|
if ($commit) {
|
||||||
$this->commit();
|
$this->commit();
|
||||||
$commited = true;
|
$commited = true;
|
||||||
@ -274,8 +275,8 @@ class Sqlite implements IDatabase {
|
|||||||
|
|
||||||
function get($query, ?array $params=null, bool $entireRow=false) {
|
function get($query, ?array $params=null, bool $entireRow=false) {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
try {
|
try {
|
||||||
$result = $this->checkResult($stmt->execute());
|
$result = $this->checkResult($stmt->execute());
|
||||||
try {
|
try {
|
||||||
@ -300,7 +301,7 @@ class Sqlite implements IDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
|
protected function _fetchResult(SQLite3Result $result, ?SQLite3Stmt $stmt=null, $primaryKeys=null): Generator {
|
||||||
if ($primaryKeys !== null) $primaryKeys = cl::with($primaryKeys);
|
$primaryKeys = cl::withn($primaryKeys);
|
||||||
try {
|
try {
|
||||||
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
while (($row = $result->fetchArray(SQLITE3_ASSOC)) !== false) {
|
||||||
$this->verifixRow($row);
|
$this->verifixRow($row);
|
||||||
@ -317,14 +318,10 @@ class Sqlite implements IDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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): iterable {
|
function all($query, ?array $params=null, $primaryKeys=null): iterable {
|
||||||
$db = $this->db();
|
$db = $this->db();
|
||||||
$query = new _query_base($query, $params);
|
$query = new _sqliteQuery($query, $params);
|
||||||
if ($query->useStmt($db, $stmt, $sql)) {
|
if ($query->_use_stmt($db, $stmt, $sql)) {
|
||||||
$result = $this->checkResult($stmt->execute());
|
$result = $this->checkResult($stmt->execute());
|
||||||
return $this->_fetchResult($result, $stmt, $primaryKeys);
|
return $this->_fetchResult($result, $stmt, $primaryKeys);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace nulib\db\sqlite;
|
namespace nulib\db\sqlite;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
use nulib\db\CapacitorChannel;
|
use nulib\db\CapacitorChannel;
|
||||||
use nulib\db\CapacitorStorage;
|
use nulib\db\CapacitorStorage;
|
||||||
|
|
||||||
@ -12,8 +13,7 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
$this->db = Sqlite::with($sqlite);
|
$this->db = Sqlite::with($sqlite);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Sqlite */
|
protected Sqlite $db;
|
||||||
protected $db;
|
|
||||||
|
|
||||||
function db(): Sqlite {
|
function db(): Sqlite {
|
||||||
return $this->db;
|
return $this->db;
|
||||||
@ -23,74 +23,59 @@ class SqliteStorage extends CapacitorStorage {
|
|||||||
"id_" => "integer primary key autoincrement",
|
"id_" => "integer primary key autoincrement",
|
||||||
];
|
];
|
||||||
|
|
||||||
function _getCreateSql(CapacitorChannel $channel): string {
|
protected function tableExists(string $tableName): bool {
|
||||||
$query = new _query_base($this->_createSql($channel));
|
$found = $this->db->get([
|
||||||
return self::format_sql($channel, $query->getSql());
|
|
||||||
}
|
|
||||||
|
|
||||||
function tableExists(string $tableName): bool {
|
|
||||||
$name = $this->db->get([
|
|
||||||
# depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
|
# depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
|
||||||
# mais le nom sqlite_master est toujours valable pour le moment
|
# mais le nom sqlite_master est toujours valable pour le moment
|
||||||
"select name from sqlite_master ",
|
"select name from sqlite_master ",
|
||||||
"where" => ["name" => $tableName],
|
"where" => ["name" => $tableName],
|
||||||
]);
|
]);
|
||||||
return $name !== null;
|
return $found !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function channelExists(string $name): bool {
|
function _getMigration(CapacitorChannel $channel): _sqliteMigration {
|
||||||
$name = $this->db->get([
|
$migrations = cl::merge([
|
||||||
"select name from _channels",
|
"0init" => [$this->_createSql($channel)],
|
||||||
|
], $channel->getMigration());
|
||||||
|
return new _sqliteMigration($migrations, $channel->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
function channelExists(string $name, ?array &$row=null): bool {
|
||||||
|
$row = $this->db->one([
|
||||||
|
"select",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
"where" => ["name" => $name],
|
"where" => ["name" => $name],
|
||||||
]);
|
]);
|
||||||
return $name !== null;
|
return $row !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannels(): iterable {
|
||||||
|
return $this->db->all([
|
||||||
|
"select",
|
||||||
|
"from" => static::CHANNELS_TABLE,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||||
|
$sql = parent::_addToChannelsSql($channel);
|
||||||
|
$sql[0] = "insert or ignore";
|
||||||
|
return $sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _afterCreate(CapacitorChannel $channel): void {
|
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||||
$db = $this->db;
|
$db = $this->db;
|
||||||
if (!$this->tableExists("_channels")) {
|
if (!$this->tableExists(static::CHANNELS_TABLE)) {
|
||||||
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
||||||
# verrou en écriture
|
# verrou en écriture
|
||||||
$db->exec([
|
$db->exec($this->_createChannelsSql());
|
||||||
"create table if not exists",
|
|
||||||
"table" => "_channels",
|
|
||||||
"cols" => [
|
|
||||||
"name" => "varchar primary key",
|
|
||||||
"table_name" => "varchar",
|
|
||||||
"class" => "varchar",
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
if (!$this->channelExists($channel->getName())) {
|
if (!$this->channelExists($channel->getName())) {
|
||||||
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
||||||
# verrou en écriture
|
# verrou en écriture
|
||||||
$db->exec([
|
$db->exec($this->_addToChannelsSql($channel));
|
||||||
"insert",
|
|
||||||
"into" => "_channels",
|
|
||||||
"values" => [
|
|
||||||
"name" => $channel->getName(),
|
|
||||||
"table_name" => $channel->getTableName(),
|
|
||||||
"class" => get_class($channel),
|
|
||||||
],
|
|
||||||
"suffix" => "on conflict do nothing",
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function _beforeReset(CapacitorChannel $channel): void {
|
|
||||||
$this->db->exec([
|
|
||||||
"delete",
|
|
||||||
"from" => "_channels",
|
|
||||||
"where" => [
|
|
||||||
"name" => $channel->getName(),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _exists(CapacitorChannel $channel): bool {
|
|
||||||
return $this->tableExists($channel->getTableName());
|
|
||||||
}
|
|
||||||
|
|
||||||
function close(): void {
|
function close(): void {
|
||||||
$this->db->close();
|
$this->db->close();
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\php\nur_func;
|
|
||||||
|
|
||||||
class _config {
|
|
||||||
static function with($configs): self {
|
|
||||||
if ($configs instanceof static) return $configs;
|
|
||||||
return new static($configs);
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONFIG = null;
|
|
||||||
|
|
||||||
function __construct($configs) {
|
|
||||||
if ($configs === null) $configs = static::CONFIG;
|
|
||||||
if ($configs === null) $configs = [];
|
|
||||||
elseif (is_string($configs)) $configs = [$configs];
|
|
||||||
elseif (is_callable($configs)) $configs = [$configs];
|
|
||||||
elseif (!is_array($configs)) $configs = [strval($configs)];
|
|
||||||
$this->configs = $configs;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var array */
|
|
||||||
protected $configs;
|
|
||||||
|
|
||||||
function configure(Sqlite $sqlite): void {
|
|
||||||
foreach ($this->configs as $key => $config) {
|
|
||||||
if (is_string($config) && !nur_func::is_method($config)) {
|
|
||||||
$sqlite->exec($config);
|
|
||||||
} else {
|
|
||||||
nur_func::ensure_func($config, $this, $args);
|
|
||||||
nur_func::call($config, $sqlite, $key, ...$args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\php\nur_func;
|
|
||||||
|
|
||||||
class _migration {
|
|
||||||
static function with($migrations): self {
|
|
||||||
if ($migrations instanceof static) {
|
|
||||||
return $migrations;
|
|
||||||
} elseif ($migrations instanceof self) {
|
|
||||||
return new static($migrations->migrations);
|
|
||||||
} else {
|
|
||||||
return new static($migrations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MIGRATE = null;
|
|
||||||
|
|
||||||
function __construct($migrations) {
|
|
||||||
if ($migrations === null) $migrations = static::MIGRATE;
|
|
||||||
if ($migrations === null) $migrations = [];
|
|
||||||
elseif (is_string($migrations)) $migrations = [$migrations];
|
|
||||||
elseif (is_callable($migrations)) $migrations = [$migrations];
|
|
||||||
elseif (!is_array($migrations)) $migrations = [strval($migrations)];
|
|
||||||
$this->migrations = $migrations;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var callable[]|string[] */
|
|
||||||
protected $migrations;
|
|
||||||
|
|
||||||
function migrate(Sqlite $sqlite): void {
|
|
||||||
$sqlite->exec("create table if not exists _migration(key varchar primary key, value varchar not null, done integer default 0)");
|
|
||||||
foreach ($this->migrations as $key => $migration) {
|
|
||||||
$exists = $sqlite->get("select 1 from _migration where key = :key and done = 1", [
|
|
||||||
"key" => $key,
|
|
||||||
]);
|
|
||||||
if (!$exists) {
|
|
||||||
$sqlite->exec("insert or replace into _migration(key, value, done) values(:key, :value, :done)", [
|
|
||||||
"key" => $key,
|
|
||||||
"value" => $migration,
|
|
||||||
"done" => 0,
|
|
||||||
]);
|
|
||||||
if (is_string($migration) && !nur_func::is_method($migration)) {
|
|
||||||
$sqlite->exec($migration);
|
|
||||||
} else {
|
|
||||||
nur_func::ensure_func($migration, $this, $args);
|
|
||||||
nur_func::call($migration, $sqlite, $key, ...$args);
|
|
||||||
}
|
|
||||||
$sqlite->exec("update _migration set done = 1 where key = :key", [
|
|
||||||
"key" => $key,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\db\_private\_base;
|
|
||||||
use nulib\db\_private\Tbindings;
|
|
||||||
use nulib\output\msg;
|
|
||||||
use nulib\ValueException;
|
|
||||||
use SQLite3;
|
|
||||||
use SQLite3Stmt;
|
|
||||||
|
|
||||||
class _query_base extends _base {
|
|
||||||
use Tbindings;
|
|
||||||
|
|
||||||
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, $bindinds);
|
|
||||||
} elseif (_query_select::isa($prefix)) {
|
|
||||||
$sql = _query_select::parse($sql, $bindinds);
|
|
||||||
} elseif (_query_insert::isa($prefix)) {
|
|
||||||
$sql = _query_insert::parse($sql, $bindinds);
|
|
||||||
} elseif (_query_update::isa($prefix)) {
|
|
||||||
$sql = _query_update::parse($sql, $bindinds);
|
|
||||||
} elseif (_query_delete::isa($prefix)) {
|
|
||||||
$sql = _query_delete::parse($sql, $bindinds);
|
|
||||||
} elseif (_query_generic::isa($prefix)) {
|
|
||||||
$sql = _query_generic::parse($sql, $bindinds);
|
|
||||||
} else {
|
|
||||||
throw SqliteException::wrap(ValueException::invalid_kind($sql, "query"));
|
|
||||||
}
|
|
||||||
} elseif (!is_string($sql)) {
|
|
||||||
$sql = strval($sql);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEBUG_QUERIES = false;
|
|
||||||
|
|
||||||
function useStmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
|
|
||||||
if (static::DEBUG_QUERIES) msg::info($this->sql); #XXX
|
|
||||||
if ($this->bindings !== null) {
|
|
||||||
/** @var SQLite3Stmt $stmt */
|
|
||||||
$stmt = SqliteException::check($db, $db->prepare($this->sql));
|
|
||||||
$close = true;
|
|
||||||
try {
|
|
||||||
foreach ($this->bindings as $param => $value) {
|
|
||||||
$this->verifixBindings($value);
|
|
||||||
SqliteException::check($db, $stmt->bindValue($param, $value));
|
|
||||||
}
|
|
||||||
$close = false;
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
if ($close) $stmt->close();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$sql = $this->sql;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\db\_private\_create;
|
|
||||||
use nulib\db\_private\Tcreate;
|
|
||||||
|
|
||||||
class _query_create extends _query_base {
|
|
||||||
use Tcreate;
|
|
||||||
const SCHEMA = _create::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\db\_private\_delete;
|
|
||||||
use nulib\db\_private\Tdelete;
|
|
||||||
|
|
||||||
class _query_delete extends _query_base {
|
|
||||||
use Tdelete;
|
|
||||||
const SCHEMA = _delete::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\db\_private\_generic;
|
|
||||||
use nulib\db\_private\Tgeneric;
|
|
||||||
|
|
||||||
class _query_generic extends _query_base {
|
|
||||||
use Tgeneric;
|
|
||||||
const SCHEMA = _generic::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\db\_private\_insert;
|
|
||||||
use nulib\db\_private\Tinsert;
|
|
||||||
|
|
||||||
class _query_insert extends _query_base {
|
|
||||||
use Tinsert;
|
|
||||||
const SCHEMA = _insert::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\db\_private\_select;
|
|
||||||
use nulib\db\_private\Tselect;
|
|
||||||
|
|
||||||
class _query_select extends _query_base {
|
|
||||||
use Tselect;
|
|
||||||
const SCHEMA = _select::SCHEMA;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\db\sqlite;
|
|
||||||
|
|
||||||
use nulib\db\_private\_update;
|
|
||||||
use nulib\db\_private\Tupdate;
|
|
||||||
|
|
||||||
class _query_update extends _query_base {
|
|
||||||
use Tupdate;
|
|
||||||
const SCHEMA = _update::SCHEMA;
|
|
||||||
}
|
|
23
php/src/db/sqlite/_sqliteMigration.php
Normal file
23
php/src/db/sqlite/_sqliteMigration.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\sqlite;
|
||||||
|
|
||||||
|
use nulib\db\_private\_migration;
|
||||||
|
|
||||||
|
class _sqliteMigration extends _migration {
|
||||||
|
static function with($migration): self {
|
||||||
|
if ($migration instanceof self) return $migration;
|
||||||
|
else return new static($migration);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setMigrated(string $name, bool $done): void {
|
||||||
|
$this->db->exec([
|
||||||
|
"insert or replace",
|
||||||
|
"into" => static::MIGRATION_TABLE,
|
||||||
|
"values" => [
|
||||||
|
"channel" => $this->channel,
|
||||||
|
"name" => $name,
|
||||||
|
"done" => $done? 1: 0,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
39
php/src/db/sqlite/_sqliteQuery.php
Normal file
39
php/src/db/sqlite/_sqliteQuery.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\sqlite;
|
||||||
|
|
||||||
|
use nulib\db\_private\_base;
|
||||||
|
use nulib\db\_private\Tbindings;
|
||||||
|
use nulib\output\msg;
|
||||||
|
use SQLite3;
|
||||||
|
use SQLite3Stmt;
|
||||||
|
|
||||||
|
class _sqliteQuery extends _base {
|
||||||
|
use Tbindings;
|
||||||
|
|
||||||
|
const DEBUG_QUERIES = false;
|
||||||
|
|
||||||
|
function _use_stmt(SQLite3 $db, ?SQLite3Stmt &$stmt=null, ?string &$sql=null): bool {
|
||||||
|
if (static::DEBUG_QUERIES) {#XXX
|
||||||
|
msg::info($this->sql);
|
||||||
|
//msg::info(var_export($this->bindings, true));
|
||||||
|
}
|
||||||
|
if ($this->bindings !== null) {
|
||||||
|
/** @var SQLite3Stmt $stmt */
|
||||||
|
$stmt = SqliteException::check($db, $db->prepare($this->sql));
|
||||||
|
$close = true;
|
||||||
|
try {
|
||||||
|
foreach ($this->bindings as $param => $value) {
|
||||||
|
$this->verifixBindings($value);
|
||||||
|
SqliteException::check($db, $stmt->bindValue($param, $value));
|
||||||
|
}
|
||||||
|
$close = false;
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
if ($close) $stmt->close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$sql = $this->sql;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,4 +40,6 @@ interface IReader extends _IFile {
|
|||||||
function unserialize(?array $options=null, bool $close=true, bool $alreadyLocked=false);
|
function unserialize(?array $options=null, bool $close=true, bool $alreadyLocked=false);
|
||||||
|
|
||||||
function copyTo(IWriter $dest, bool $closeWriter=false, bool $closeReader=true): void;
|
function copyTo(IWriter $dest, bool $closeWriter=false, bool $closeReader=true): void;
|
||||||
|
|
||||||
|
function setCsvFlavour(?string $flavour): void;
|
||||||
}
|
}
|
||||||
|
@ -299,8 +299,8 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||||||
|
|
||||||
/** retourner le contenu du fichier sous forme de chaine */
|
/** retourner le contenu du fichier sous forme de chaine */
|
||||||
function getContents(bool $close=true, bool $alreadyLocked=false): string {
|
function getContents(bool $close=true, bool $alreadyLocked=false): string {
|
||||||
$useLocking = $this->useLocking;
|
$useLocking = $this->useLocking && !$alreadyLocked;
|
||||||
if ($useLocking && !$alreadyLocked) $this->lock(LOCK_SH);
|
if ($useLocking) $this->lock(LOCK_SH);
|
||||||
try {
|
try {
|
||||||
return IOException::ensure_valid(stream_get_contents($this->fd), $this->throwOnError);
|
return IOException::ensure_valid(stream_get_contents($this->fd), $this->throwOnError);
|
||||||
} finally {
|
} finally {
|
||||||
@ -380,7 +380,9 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||||||
/** @throws IOException */
|
/** @throws IOException */
|
||||||
function ftruncate(int $size=0, bool $rewind=true): self {
|
function ftruncate(int $size=0, bool $rewind=true): self {
|
||||||
$fd = $this->getResource();
|
$fd = $this->getResource();
|
||||||
IOException::ensure_valid(ftruncate($fd, $size), $this->throwOnError);
|
$r = ftruncate($fd, $size);
|
||||||
|
$this->stat = null;
|
||||||
|
IOException::ensure_valid($r, $this->throwOnError);
|
||||||
if ($rewind) rewind($fd);
|
if ($rewind) rewind($fd);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -390,6 +392,7 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||||||
$fd = $this->getResource();
|
$fd = $this->getResource();
|
||||||
if ($length === null) $r = fwrite($fd, $data);
|
if ($length === null) $r = fwrite($fd, $data);
|
||||||
else $r = fwrite($fd, $data, $length);
|
else $r = fwrite($fd, $data, $length);
|
||||||
|
$this->stat = null;
|
||||||
return IOException::ensure_valid($r, $this->throwOnError);
|
return IOException::ensure_valid($r, $this->throwOnError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,9 +406,13 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||||||
$line[] = strval($col);
|
$line[] = strval($col);
|
||||||
}
|
}
|
||||||
$line = implode($sep, $line);
|
$line = implode($sep, $line);
|
||||||
IOException::ensure_valid(fwrite($fd, "$line\n"), $this->throwOnError);
|
$r = fwrite($fd, "$line\n");
|
||||||
|
$this->stat = null;
|
||||||
|
IOException::ensure_valid($r, $this->throwOnError);
|
||||||
} else {
|
} else {
|
||||||
IOException::ensure_valid(fputcsv($fd, $row, $params[0], $params[1], $params[2]), $this->throwOnError);
|
$r = fputcsv($fd, $row, $params[0], $params[1], $params[2]);
|
||||||
|
$this->stat = null;
|
||||||
|
IOException::ensure_valid($r, $this->throwOnError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,8 +478,8 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function putContents(string $contents, bool $close=true, bool $alreadyLocked=false): void {
|
function putContents(string $contents, bool $close=true, bool $alreadyLocked=false): void {
|
||||||
$useLocking = $this->useLocking;
|
$useLocking = $this->useLocking && !$alreadyLocked;
|
||||||
if ($useLocking && !$alreadyLocked) $this->lock(LOCK_EX);
|
if ($useLocking) $this->lock(LOCK_EX);
|
||||||
try {
|
try {
|
||||||
$this->fwrite($contents);
|
$this->fwrite($contents);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
namespace nulib\file\csv;
|
namespace nulib\file\csv;
|
||||||
|
|
||||||
use nulib\file;
|
use nulib\file;
|
||||||
|
use nulib\file\_IFile;
|
||||||
use nulib\file\FileReader;
|
use nulib\file\FileReader;
|
||||||
|
use nulib\file\IReader;
|
||||||
use nulib\file\tab\AbstractReader;
|
use nulib\file\tab\AbstractReader;
|
||||||
use nulib\file\tab\TAbstractReader;
|
use nulib\file\tab\TAbstractReader;
|
||||||
|
|
||||||
@ -20,7 +22,12 @@ class CsvReader extends AbstractReader {
|
|||||||
protected ?string $inputEncoding;
|
protected ?string $inputEncoding;
|
||||||
|
|
||||||
function getIterator() {
|
function getIterator() {
|
||||||
$reader = new FileReader(file::fix_dash($this->input));
|
$input = $this->input;
|
||||||
|
if ($input instanceof IReader) {
|
||||||
|
$reader = $input;
|
||||||
|
} else {
|
||||||
|
$reader = new FileReader(file::fix_dash($input));
|
||||||
|
}
|
||||||
$inputEncoding = $this->inputEncoding;
|
$inputEncoding = $this->inputEncoding;
|
||||||
if ($inputEncoding !== null) {
|
if ($inputEncoding !== null) {
|
||||||
$reader->appendFilter("convert.iconv.$inputEncoding.utf-8");
|
$reader->appendFilter("convert.iconv.$inputEncoding.utf-8");
|
||||||
|
@ -5,7 +5,7 @@ use DateTimeInterface;
|
|||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\file\TempStream;
|
use nulib\file\TempStream;
|
||||||
use nulib\os\path;
|
use nulib\os\path;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
use nulib\php\time\DateTime;
|
use nulib\php\time\DateTime;
|
||||||
use nulib\web\http;
|
use nulib\web\http;
|
||||||
|
|
||||||
@ -35,13 +35,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
$this->rows = $rows;
|
$this->rows = $rows;
|
||||||
$this->index = 0;
|
$this->index = 0;
|
||||||
$cookFunc = $params["cook_func"] ?? null;
|
$cookFunc = $params["cook_func"] ?? null;
|
||||||
$cookCtx = $cookArgs = null;
|
if ($cookFunc !== null) $cookFunc = func::with($cookFunc)->bind($this);
|
||||||
if ($cookFunc !== null) {
|
$this->cookFunc = $cookFunc;
|
||||||
nur_func::ensure_func($cookFunc, $this, $cookArgs);
|
|
||||||
$cookCtx = nur_func::_prepare($cookFunc);
|
|
||||||
}
|
|
||||||
$this->cookCtx = $cookCtx;
|
|
||||||
$this->cookArgs = $cookArgs;
|
|
||||||
$this->output = $params["output"] ?? static::OUTPUT;
|
$this->output = $params["output"] ?? static::OUTPUT;
|
||||||
$maxMemory = $params["max_memory"] ?? null;
|
$maxMemory = $params["max_memory"] ?? null;
|
||||||
$throwOnError = $params["throw_on_error"] ?? null;
|
$throwOnError = $params["throw_on_error"] ?? null;
|
||||||
@ -60,9 +55,7 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
|
|
||||||
protected ?string $output;
|
protected ?string $output;
|
||||||
|
|
||||||
protected ?array $cookCtx;
|
protected ?func $cookFunc;
|
||||||
|
|
||||||
protected ?array $cookArgs;
|
|
||||||
|
|
||||||
protected function ensureHeaders(?array $row=null): void {
|
protected function ensureHeaders(?array $row=null): void {
|
||||||
if ($this->headers !== null || !$this->useHeaders) return;
|
if ($this->headers !== null || !$this->useHeaders) return;
|
||||||
@ -87,9 +80,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function cookRow(?array $row): ?array {
|
protected function cookRow(?array $row): ?array {
|
||||||
if ($this->cookCtx !== null) {
|
if ($this->cookFunc !== null) {
|
||||||
$args = cl::merge([$row], $this->cookArgs);
|
$row = $this->cookFunc->prependArgs([$row])->invoke();
|
||||||
$row = nur_func::_call($this->cookCtx, $args);
|
|
||||||
}
|
}
|
||||||
if ($row !== null) {
|
if ($row !== null) {
|
||||||
foreach ($row as &$col) {
|
foreach ($row as &$col) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
namespace nulib\output;
|
namespace nulib\output;
|
||||||
|
|
||||||
use nulib\output\std\ProxyMessenger;
|
use nulib\output\std\ProxyMessenger;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class msg: inscrire un message dans les logs ET l'afficher à l'utilisateur
|
* Class msg: inscrire un message dans les logs ET l'afficher à l'utilisateur
|
||||||
@ -39,30 +39,21 @@ class msg extends _messenger {
|
|||||||
if ($log !== null && $log !== false) {
|
if ($log !== null && $log !== false) {
|
||||||
if ($log instanceof IMessenger) log::set_messenger($log);
|
if ($log instanceof IMessenger) log::set_messenger($log);
|
||||||
elseif (is_string($log)) log::set_messenger_class($log);
|
elseif (is_string($log)) log::set_messenger_class($log);
|
||||||
elseif (is_array($log)) {
|
else $log = func::call($log);
|
||||||
nur_func::ensure_class($log, $args);
|
|
||||||
$log = nur_func::cons($log, $args);
|
|
||||||
}
|
|
||||||
log::set_messenger($log);
|
log::set_messenger($log);
|
||||||
$msgs[] = $log;
|
$msgs[] = $log;
|
||||||
}
|
}
|
||||||
if ($console !== null && $console !== false) {
|
if ($console !== null && $console !== false) {
|
||||||
if ($console instanceof IMessenger) console::set_messenger($console);
|
if ($console instanceof IMessenger) console::set_messenger($console);
|
||||||
elseif (is_string($console)) console::set_messenger_class($console);
|
elseif (is_string($console)) console::set_messenger_class($console);
|
||||||
elseif (is_array($console)) {
|
else $console = func::call($console);
|
||||||
nur_func::ensure_class($console, $args);
|
|
||||||
$console = nur_func::cons($console, $args);
|
|
||||||
}
|
|
||||||
console::set_messenger($console);
|
console::set_messenger($console);
|
||||||
$msgs[] = $console;
|
$msgs[] = $console;
|
||||||
}
|
}
|
||||||
if ($say !== null && $say !== false) {
|
if ($say !== null && $say !== false) {
|
||||||
if ($say instanceof IMessenger) say::set_messenger($say);
|
if ($say instanceof IMessenger) say::set_messenger($say);
|
||||||
elseif (is_string($say)) say::set_messenger_class($say);
|
elseif (is_string($say)) say::set_messenger_class($say);
|
||||||
elseif (is_array($say)) {
|
else $say = func::call($say);
|
||||||
nur_func::ensure_class($say, $args);
|
|
||||||
$say = nur_func::cons($say, $args);
|
|
||||||
}
|
|
||||||
say::set_messenger($say);
|
say::set_messenger($say);
|
||||||
$msgs[] = $say;
|
$msgs[] = $say;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ namespace nulib\php\content;
|
|||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\php\nur_func;
|
use nulib\php\func;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class c: classe outil pour gérer du contenu
|
* Class c: classe outil pour gérer du contenu
|
||||||
@ -62,8 +62,7 @@ class c {
|
|||||||
# contenu dynamique: le contenu est la valeur de retour de la fonction
|
# contenu dynamique: le contenu est la valeur de retour de la fonction
|
||||||
# ce contenu est rajouté à la suite après avoir été quoté avec self::q()
|
# ce contenu est rajouté à la suite après avoir été quoté avec self::q()
|
||||||
$func = $value;
|
$func = $value;
|
||||||
nur_func::ensure_func($func, $object_or_class, $args);
|
$values = self::q(func::call($func));
|
||||||
$values = self::q(nur_func::call($func, ...$args));
|
|
||||||
self::add_static_content($dest, $values, $key, $seq);
|
self::add_static_content($dest, $values, $key, $seq);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -83,16 +82,7 @@ class c {
|
|||||||
$arg = self::resolve($arg, $object_or_class, false);
|
$arg = self::resolve($arg, $object_or_class, false);
|
||||||
if (!$array) $arg = $arg[0];
|
if (!$array) $arg = $arg[0];
|
||||||
}; unset($arg);
|
}; unset($arg);
|
||||||
if (nur_func::is_static($func)) {
|
$value = func::with($func, $args)->bind($object_or_class)->invoke();
|
||||||
nur_func::ensure_func($func, $object_or_class, $args);
|
|
||||||
$value = nur_func::call($func, ...$args);
|
|
||||||
} elseif (nur_func::is_class($func)) {
|
|
||||||
nur_func::fix_class_args($func, $args);
|
|
||||||
$value = nur_func::cons($func, ...$args);
|
|
||||||
} else {
|
|
||||||
nur_func::ensure_func($func, $object_or_class, $args);
|
|
||||||
$value = nur_func::call($func, ...$args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($seq) $dest[] = $value;
|
if ($seq) $dest[] = $value;
|
||||||
|
@ -4,12 +4,14 @@ namespace nulib\php;
|
|||||||
use Closure;
|
use Closure;
|
||||||
use Exception;
|
use Exception;
|
||||||
use nulib\A;
|
use nulib\A;
|
||||||
|
use nulib\cl;
|
||||||
use nulib\cv;
|
use nulib\cv;
|
||||||
use nulib\StateException;
|
use nulib\StateException;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionFunction;
|
use ReflectionFunction;
|
||||||
use ReflectionMethod;
|
use ReflectionMethod;
|
||||||
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class func: outils pour appeler fonctions et méthodes dynamiquement
|
* Class func: outils pour appeler fonctions et méthodes dynamiquement
|
||||||
@ -57,12 +59,10 @@ class func {
|
|||||||
* @param bool $strict vérifier l'inexistence de la classe et l'existence de
|
* @param bool $strict vérifier l'inexistence de la classe et l'existence de
|
||||||
* la fonction (ne pas uniquement faire une vérification syntaxique)
|
* la fonction (ne pas uniquement faire une vérification syntaxique)
|
||||||
*/
|
*/
|
||||||
static function verifix_function(&$func, bool $strict=true, ?string &$reason=null): bool {
|
static function verifix_function(&$func, ?array &$args=null, bool $strict=true, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionFunction) return true;
|
if ($func instanceof ReflectionFunction) return true;
|
||||||
|
$rargs = null;
|
||||||
if (is_string($func)) {
|
if (is_string($func)) {
|
||||||
$c = false;
|
$c = false;
|
||||||
$f = $func;
|
$f = $func;
|
||||||
@ -71,6 +71,7 @@ class func {
|
|||||||
$c = $func[0];
|
$c = $func[0];
|
||||||
if (!array_key_exists(1, $func)) return false;
|
if (!array_key_exists(1, $func)) return false;
|
||||||
$f = $func[1];
|
$f = $func[1];
|
||||||
|
$rargs = array_slice($func, 2);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -82,15 +83,16 @@ class func {
|
|||||||
if ($strict) {
|
if ($strict) {
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if (class_exists($f)) {
|
if (class_exists($f)) {
|
||||||
$reason = "$msg: is a class";
|
$reason = "$f: is a class";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!function_exists($f)) {
|
if (!function_exists($f)) {
|
||||||
$reason = "$msg: function not found";
|
$reason = "$f: function not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$func = [false, $f];
|
$func = [false, $f];
|
||||||
|
if ($rargs) $args = cl::merge($rargs, $args);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +101,7 @@ class func {
|
|||||||
* {@link self::verifix_function()}
|
* {@link self::verifix_function()}
|
||||||
*/
|
*/
|
||||||
static function is_function($func, bool $strict=true, ?string &$reason=null): bool {
|
static function is_function($func, bool $strict=true, ?string &$reason=null): bool {
|
||||||
return self::verifix_function($func, $strict, $reason);
|
return self::verifix_function($func, $args, $strict, $reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@ -116,12 +118,10 @@ class func {
|
|||||||
* @param bool $strict vérifier l'existence de la classe (ne pas uniquement
|
* @param bool $strict vérifier l'existence de la classe (ne pas uniquement
|
||||||
* faire une vérification syntaxique)
|
* faire une vérification syntaxique)
|
||||||
*/
|
*/
|
||||||
static function verifix_class(&$func, bool $strict=true, ?string &$reason=null): bool {
|
static function verifix_class(&$func, ?array &$args=null, bool $strict=true, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionClass) return true;
|
if ($func instanceof ReflectionClass) return true;
|
||||||
|
$rargs = null;
|
||||||
if (is_string($func)) {
|
if (is_string($func)) {
|
||||||
$c = $func;
|
$c = $func;
|
||||||
$f = false;
|
$f = false;
|
||||||
@ -130,6 +130,7 @@ class func {
|
|||||||
$c = $func[0];
|
$c = $func[0];
|
||||||
if (!array_key_exists(1, $func)) return false;
|
if (!array_key_exists(1, $func)) return false;
|
||||||
$f = $func[1];
|
$f = $func[1];
|
||||||
|
$rargs = array_slice($func, 2);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -138,13 +139,12 @@ class func {
|
|||||||
if (self::_parse_static($c)) return false;
|
if (self::_parse_static($c)) return false;
|
||||||
if (self::_parse_method($c)) return false;
|
if (self::_parse_method($c)) return false;
|
||||||
if ($f !== false) return false;
|
if ($f !== false) return false;
|
||||||
if ($strict) {
|
if ($strict && !class_exists($c)) {
|
||||||
if (!class_exists($c)) {
|
$reason = "$c: class not found";
|
||||||
$reason = "$msg: class not found";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$func = [$c, false];
|
$func = [$c, false];
|
||||||
|
if ($rargs) $args = cl::merge($rargs, $args);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ class func {
|
|||||||
* {@link self::verifix_class()}
|
* {@link self::verifix_class()}
|
||||||
*/
|
*/
|
||||||
static function is_class($func, bool $strict=true, ?string &$reason=null): bool {
|
static function is_class($func, bool $strict=true, ?string &$reason=null): bool {
|
||||||
return self::verifix_class($func, $strict, $reason);
|
return self::verifix_class($func, $args, $strict, $reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@ -204,17 +204,16 @@ class func {
|
|||||||
* liée à une classe avant d'être utilisée
|
* liée à une classe avant d'être utilisée
|
||||||
*
|
*
|
||||||
* @param bool $strict vérifier l'existence de la classe et de la méthode si
|
* @param bool $strict vérifier l'existence de la classe et de la méthode si
|
||||||
|
* @param array|null &$args
|
||||||
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
||||||
*/
|
*/
|
||||||
static function verifix_static(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
static function verifix_static(&$func, ?array &$args = null, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionMethod) {
|
if ($func instanceof ReflectionMethod) {
|
||||||
$bound = false;
|
$bound = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
$rargs = null;
|
||||||
if (is_string($func)) {
|
if (is_string($func)) {
|
||||||
if (!self::_parse_c_static($func, $c, $f, $bound)) return false;
|
if (!self::_parse_c_static($func, $c, $f, $bound)) return false;
|
||||||
$cf = [$c, $f];
|
$cf = [$c, $f];
|
||||||
@ -261,25 +260,30 @@ class func {
|
|||||||
self::_parse_static($f);
|
self::_parse_static($f);
|
||||||
}
|
}
|
||||||
$cf[1] = $f;
|
$cf[1] = $f;
|
||||||
|
$rargs = array_slice($func, 2);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($strict) {
|
if ($strict) {
|
||||||
|
[$c, $f] = $cf;
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if ($bound) {
|
if ($bound) {
|
||||||
if (!class_exists($c)) {
|
if (!class_exists($c)) {
|
||||||
$reason = "$msg: class not found";
|
$reason = "$c: class not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!method_exists($c, $f)) {
|
if (!method_exists($c, $f)) {
|
||||||
$reason = "$msg: method not found";
|
$reason = "$c::$f: method not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$method = new ReflectionMethod($c, $f);
|
||||||
|
if (!$method->isStatic()) return false;
|
||||||
} else {
|
} else {
|
||||||
$reason = "$msg: not bound";
|
$reason = "$c::$f: not bound";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$func = $cf;
|
$func = $cf;
|
||||||
|
if ($rargs) $args = cl::merge($rargs, $args);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +292,7 @@ class func {
|
|||||||
* {@link self::verifix_static()}
|
* {@link self::verifix_static()}
|
||||||
*/
|
*/
|
||||||
static function is_static($func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
static function is_static($func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||||
return self::verifix_static($func, $strict, $bound, $reason);
|
return self::verifix_static($func, $args, $strict, $bound, $reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@ -341,15 +345,13 @@ class func {
|
|||||||
* @param bool $strict vérifier l'existence de la classe et de la méthode si
|
* @param bool $strict vérifier l'existence de la classe et de la méthode si
|
||||||
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
* la méthode est liée (ne pas uniquement faire une vérification syntaxique)
|
||||||
*/
|
*/
|
||||||
static function verifix_method(&$func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
static function verifix_method(&$func, ?array &$args=null, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||||
if ($strict) {
|
if ($strict) $reason = null;
|
||||||
$msg = var_export($func, true);
|
|
||||||
$reason = null;
|
|
||||||
}
|
|
||||||
if ($func instanceof ReflectionMethod) {
|
if ($func instanceof ReflectionMethod) {
|
||||||
$bound = false;
|
$bound = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
$rargs = null;
|
||||||
if (is_string($func)) {
|
if (is_string($func)) {
|
||||||
if (!self::_parse_c_method($func, $c, $f, $bound)) return false;
|
if (!self::_parse_c_method($func, $c, $f, $bound)) return false;
|
||||||
$cf = [$c, $f];
|
$cf = [$c, $f];
|
||||||
@ -397,25 +399,30 @@ class func {
|
|||||||
self::_parse_method($f);
|
self::_parse_method($f);
|
||||||
}
|
}
|
||||||
$cf[1] = $f;
|
$cf[1] = $f;
|
||||||
|
$rargs = array_slice($func, 2);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($strict) {
|
if ($strict) {
|
||||||
|
[$c, $f] = $cf;
|
||||||
$reason = null;
|
$reason = null;
|
||||||
if ($bound) {
|
if ($bound) {
|
||||||
if (!is_object($c) && !class_exists($c)) {
|
if (!is_object($c) && !class_exists($c)) {
|
||||||
$reason = "$msg: class not found";
|
$reason = "$c: class not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!method_exists($c, $f)) {
|
if (!method_exists($c, $f)) {
|
||||||
$reason = "$msg: method not found";
|
$reason = "$c::$f: method not found";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
$method = new ReflectionMethod($c, $f);
|
||||||
|
if ($method->isStatic()) return false;
|
||||||
} else {
|
} else {
|
||||||
$reason = "$msg: not bound";
|
$reason = "$c::$f: not bound";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$func = $cf;
|
$func = $cf;
|
||||||
|
if ($rargs) $args = cl::merge($rargs, $args);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,7 +431,7 @@ class func {
|
|||||||
* {@link self::verifix_method()}
|
* {@link self::verifix_method()}
|
||||||
*/
|
*/
|
||||||
static function is_method($func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
static function is_method($func, bool $strict=true, ?bool &$bound=null, ?string &$reason=null): bool {
|
||||||
return self::verifix_method($func, $strict, $bound, $reason);
|
return self::verifix_method($func, $args, $strict, $bound, $reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@ -446,7 +453,7 @@ class func {
|
|||||||
return new ValueException($reason);
|
return new ValueException($reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
static function with($func, ?array $args=null, bool $strict=true): self {
|
private static function _with($func, ?array $args=null, bool $strict=true, ?string &$reason=null): ?self {
|
||||||
if (!is_array($func)) {
|
if (!is_array($func)) {
|
||||||
if ($func instanceof Closure) {
|
if ($func instanceof Closure) {
|
||||||
return new self(self::TYPE_CLOSURE, $func, $args);
|
return new self(self::TYPE_CLOSURE, $func, $args);
|
||||||
@ -458,18 +465,30 @@ class func {
|
|||||||
return new self(self::TYPE_METHOD, $func, $args, false);
|
return new self(self::TYPE_METHOD, $func, $args, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (self::verifix_function($func, $strict, $reason)) {
|
if (self::verifix_function($func, $args, $strict, $reason)) {
|
||||||
return new self(self::TYPE_FUNCTION, $func, $args, false, $reason);
|
return new self(self::TYPE_FUNCTION, $func, $args, false, $reason);
|
||||||
} elseif (self::verifix_class($func, $strict, $reason)) {
|
} elseif (self::verifix_class($func, $args, $strict, $reason)) {
|
||||||
return new self(self::TYPE_CLASS, $func, $args, false, $reason);
|
return new self(self::TYPE_CLASS, $func, $args, false, $reason);
|
||||||
} elseif (self::verifix_method($func, $strict, $bound, $reason)) {
|
} elseif (self::verifix_method($func, $args, $strict, $bound, $reason)) {
|
||||||
return new self(self::TYPE_METHOD, $func, $args, $bound, $reason);
|
return new self(self::TYPE_METHOD, $func, $args, $bound, $reason);
|
||||||
} elseif (self::verifix_static($func, $strict, $bound, $reason)) {
|
} elseif (self::verifix_static($func, $args, $strict, $bound, $reason)) {
|
||||||
return new self(self::TYPE_STATIC, $func, $args, $bound, $reason);
|
return new self(self::TYPE_STATIC, $func, $args, $bound, $reason);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function with($func, ?array $args=null, bool $strict=true): self {
|
||||||
|
if ($func instanceof self) return $func;
|
||||||
|
$func = self::_with($func, $args, $strict, $reason);
|
||||||
|
if ($func !== null) return $func;
|
||||||
throw self::not_a_callable($func, $reason);
|
throw self::not_a_callable($func, $reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function withn($func, ?array $args=null, bool $strict=true): ?self {
|
||||||
|
if ($func === null) return null;
|
||||||
|
else return self::with($func, $args, $strict);
|
||||||
|
}
|
||||||
|
|
||||||
static function ensure($func, ?array $args=null, bool $strict=true): self {
|
static function ensure($func, ?array $args=null, bool $strict=true): self {
|
||||||
$func = self::with($func, $args, $strict);
|
$func = self::with($func, $args, $strict);
|
||||||
if (!$func->isBound()) {
|
if (!$func->isBound()) {
|
||||||
@ -487,10 +506,45 @@ class func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static function is_callable($func): bool {
|
||||||
|
$func = self::_with($func);
|
||||||
|
if ($func === null) return false;
|
||||||
|
if (!$func->isBound()) return false;
|
||||||
|
return $func->type !== self::TYPE_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
static function call($func, ...$args) {
|
static function call($func, ...$args) {
|
||||||
return self::with($func)->invoke($args);
|
return self::with($func)->invoke($args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* si $value est une fonction, l'appeler
|
||||||
|
* si $value ou le résultat de l'appel est un Traversable, le résoudre
|
||||||
|
* sinon retourner $value tel quel
|
||||||
|
*
|
||||||
|
* en définitive, la valeur de retour de cette fonction est soit un scalaire,
|
||||||
|
* soit un array, soit un objet qui n'est pas Traversable
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
static function get_value($value, ...$args) {
|
||||||
|
if ($value instanceof self) $value = $value->invoke($args);
|
||||||
|
elseif (is_callable($value)) $value = self::call($value, ...$args);
|
||||||
|
if ($value instanceof Traversable) $value = cl::all($value);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* si $value est une fonction, l'appeler
|
||||||
|
* si $value ou le résultat de l'appel est un Traversable, le retourner
|
||||||
|
* sinon retourner $value en tant qu'array
|
||||||
|
*/
|
||||||
|
static function get_iterable($value, ...$args): ?iterable {
|
||||||
|
if ($value instanceof self) $value = $value->invoke($args);
|
||||||
|
elseif (is_callable($value)) $value = self::call($value, ...$args);
|
||||||
|
if ($value instanceof Traversable) return $value;
|
||||||
|
else return cl::withn($value);
|
||||||
|
}
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) {
|
protected function __construct(int $type, $func, ?array $args=null, bool $bound=false, ?string $reason=null) {
|
||||||
@ -561,6 +615,27 @@ class func {
|
|||||||
|
|
||||||
protected int $maxArgs;
|
protected int $maxArgs;
|
||||||
|
|
||||||
|
function replaceArgs(?array $args): self {
|
||||||
|
$this->prefixArgs = $args?? [];
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prependArgs(?array $args, ?int $stripCount=null): self {
|
||||||
|
if ($stripCount !== null || $args !== null) {
|
||||||
|
array_splice($this->prefixArgs, 0, $stripCount ?? 0, $args);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendArgs(?array $args, ?int $stripCount=null): self {
|
||||||
|
if ($stripCount !== null || $args !== null) {
|
||||||
|
$stripCount ??= 0;
|
||||||
|
if ($stripCount > 0) array_splice($this->prefixArgs, -$stripCount);
|
||||||
|
$this->prefixArgs = array_merge($this->prefixArgs, $args);
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
protected function updateReflection($reflection): void {
|
protected function updateReflection($reflection): void {
|
||||||
$variadic = false;
|
$variadic = false;
|
||||||
$minArgs = $maxArgs = 0;
|
$minArgs = $maxArgs = 0;
|
||||||
@ -596,11 +671,16 @@ class func {
|
|||||||
else return $this->bound && $this->object !== null;
|
else return $this->bound && $this->object !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function bind($object): self {
|
function bind($object, bool $rebind=false, bool $replace=false): self {
|
||||||
if ($this->type !== self::TYPE_METHOD) return $this;
|
if ($this->type !== self::TYPE_METHOD) return $this;
|
||||||
|
if (!$rebind && $this->isBound()) return $this;
|
||||||
|
|
||||||
[$c, $f] = $this->func;
|
[$c, $f] = $this->func;
|
||||||
if ($this->reflection === null) {
|
if ($replace) {
|
||||||
|
$c = $object;
|
||||||
|
$this->func = [$c, $f];
|
||||||
|
$this->updateReflection(new ReflectionMethod($c, $f));
|
||||||
|
} elseif ($this->reflection === null) {
|
||||||
$this->func[0] = $c = $object;
|
$this->func[0] = $c = $object;
|
||||||
$this->updateReflection(new ReflectionMethod($c, $f));
|
$this->updateReflection(new ReflectionMethod($c, $f));
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class mprop {
|
|||||||
} catch (ReflectionException $e) {
|
} catch (ReflectionException $e) {
|
||||||
return oprop::get($object, $property, $default);
|
return oprop::get($object, $property, $default);
|
||||||
}
|
}
|
||||||
return nur_func::call([$object, $m], $default);
|
return func::call([$object, $m], $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** spécifier la valeur d'une propriété */
|
/** spécifier la valeur d'une propriété */
|
||||||
@ -60,7 +60,7 @@ class mprop {
|
|||||||
} catch (ReflectionException $e) {
|
} catch (ReflectionException $e) {
|
||||||
return oprop::_set($c, $object, $property, $value);
|
return oprop::_set($c, $object, $property, $value);
|
||||||
}
|
}
|
||||||
nur_func::call([$object, $m], $value);
|
func::call([$object, $m], $value);
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,453 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib\php;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use nulib\cl;
|
|
||||||
use nulib\ref\php\ref_func;
|
|
||||||
use nulib\ValueException;
|
|
||||||
use ReflectionClass;
|
|
||||||
use ReflectionFunction;
|
|
||||||
use ReflectionMethod;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class func: outils pour appeler des fonctions et méthodes dynamiquement
|
|
||||||
*/
|
|
||||||
class nur_func {
|
|
||||||
/**
|
|
||||||
* tester si $func est une chaine de la forme "XXX::method" où XXX est une
|
|
||||||
* chaine quelconque éventuellement vide, ou un tableau de la forme ["method"]
|
|
||||||
* ou [anything, "method", ...]
|
|
||||||
*
|
|
||||||
* Avec la forme tableau, "method" ne doit pas contenir le caractère '\', pour
|
|
||||||
* pouvoir utiliser conjointement {@link is_class()}
|
|
||||||
*/
|
|
||||||
static final function is_static($func, bool $allowClass=false): bool {
|
|
||||||
if (is_string($func)) {
|
|
||||||
$pos = strpos($func, "::");
|
|
||||||
if ($pos === false) return false;
|
|
||||||
return $pos + 2 < strlen($func);
|
|
||||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
|
||||||
$count = count($func);
|
|
||||||
if ($count == 1) {
|
|
||||||
if (!is_string($func[0]) || strlen($func[0]) == 0) return false;
|
|
||||||
if (strpos($func[0], "\\") !== false) return false;
|
|
||||||
return true;
|
|
||||||
} elseif ($count > 1) {
|
|
||||||
if (!array_key_exists(1, $func)) return false;
|
|
||||||
if (!is_string($func[1]) || strlen($func[1]) == 0) return false;
|
|
||||||
if (strpos($func[1], "\\") !== false) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* si $func est une chaine de la forme "::method" alors la remplacer par la
|
|
||||||
* chaine "$class::method"
|
|
||||||
*
|
|
||||||
* si $func est un tableau de la forme ["method"] ou [null, "method"], alors
|
|
||||||
* le remplacer par [$class, "method"]
|
|
||||||
*
|
|
||||||
* on assume que {@link is_static()}($func) retourne true
|
|
||||||
*
|
|
||||||
* @return bool true si la correction a été faite
|
|
||||||
*/
|
|
||||||
static final function fix_static(&$func, $class): bool {
|
|
||||||
if (is_object($class)) $class = get_class($class);
|
|
||||||
|
|
||||||
if (is_string($func) && substr($func, 0, 2) == "::") {
|
|
||||||
$func = "$class$func";
|
|
||||||
return true;
|
|
||||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
|
||||||
$count = count($func);
|
|
||||||
if ($count == 1) {
|
|
||||||
$func = [$class, $func[0]];
|
|
||||||
return true;
|
|
||||||
} elseif ($count > 1 && $func[0] === null) {
|
|
||||||
$func[0] = $class;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** tester si $method est une chaine de la forme "->method" */
|
|
||||||
private static function isam($method): bool {
|
|
||||||
return is_string($method)
|
|
||||||
&& strlen($method) > 2
|
|
||||||
&& substr($method, 0, 2) == "->";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* tester si $func est une chaine de la forme "->method" ou un tableau de la
|
|
||||||
* forme ["->method", ...] ou [anything, "->method", ...]
|
|
||||||
*/
|
|
||||||
static final function is_method($func): bool {
|
|
||||||
if (is_string($func)) {
|
|
||||||
return self::isam($func);
|
|
||||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
|
||||||
if (self::isam($func[0])) {
|
|
||||||
# ["->method", ...]
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (array_key_exists(1, $func) && self::isam($func[1])) {
|
|
||||||
# [anything, "->method", ...]
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* si $func est une chaine de la forme "->method" alors la remplacer par le
|
|
||||||
* tableau [$object, "method"]
|
|
||||||
*
|
|
||||||
* si $func est un tableau de la forme ["->method"] ou [anything, "->method"],
|
|
||||||
* alors le remplacer par [$object, "method"]
|
|
||||||
*
|
|
||||||
* @return bool true si la correction a été faite
|
|
||||||
*/
|
|
||||||
static final function fix_method(&$func, $object): bool {
|
|
||||||
if (!is_object($object)) return false;
|
|
||||||
|
|
||||||
if (is_string($func)) {
|
|
||||||
if (self::isam($func)) {
|
|
||||||
$func = [$object, substr($func, 2)];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} elseif (is_array($func) && array_key_exists(0, $func)) {
|
|
||||||
if (self::isam($func[0])) $func = array_merge([null], $func);
|
|
||||||
if (count($func) > 1 && array_key_exists(1, $func) && self::isam($func[1])) {
|
|
||||||
$func[0] = $object;
|
|
||||||
$func[1] = substr($func[1], 2);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* si $func est un tableau de plus de 2 éléments, alors déplacer les éléments
|
|
||||||
* supplémentaires au début de $args. par exemple:
|
|
||||||
* ~~~
|
|
||||||
* $func = ["class", "method", "arg1", "arg2"];
|
|
||||||
* $args = ["arg3"];
|
|
||||||
* func::fix_args($func, $args)
|
|
||||||
* # $func === ["class", "method"]
|
|
||||||
* # $args === ["arg1", "arg2", "arg3"]
|
|
||||||
* ~~~
|
|
||||||
*
|
|
||||||
* @return bool true si la correction a été faite
|
|
||||||
*/
|
|
||||||
static final function fix_args(&$func, ?array &$args): bool {
|
|
||||||
if ($args === null) $args = [];
|
|
||||||
if (is_array($func) && count($func) > 2) {
|
|
||||||
$prefix_args = array_slice($func, 2);
|
|
||||||
$func = array_slice($func, 0, 2);
|
|
||||||
$args = array_merge($prefix_args, $args);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* s'assurer que $func est un appel de méthode ou d'une méthode statique;
|
|
||||||
* et renseigner le cas échéant les arguments. si $func ne fait pas mention
|
|
||||||
* de la classe ou de l'objet, le renseigner avec $class_or_object.
|
|
||||||
*
|
|
||||||
* @return bool true si c'est une fonction valide. il ne reste plus qu'à
|
|
||||||
* l'appeler avec {@link call()}
|
|
||||||
*/
|
|
||||||
static final function check_func(&$func, $class_or_object, &$args=null): bool {
|
|
||||||
if ($func instanceof Closure) return true;
|
|
||||||
if (self::is_method($func)) {
|
|
||||||
# méthode
|
|
||||||
self::fix_method($func, $class_or_object);
|
|
||||||
self::fix_args($func, $args);
|
|
||||||
return true;
|
|
||||||
} elseif (self::is_static($func)) {
|
|
||||||
# méthode statique
|
|
||||||
self::fix_static($func, $class_or_object);
|
|
||||||
self::fix_args($func, $args);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comme {@link check_func()} mais lance une exception si la fonction est
|
|
||||||
* invalide
|
|
||||||
*
|
|
||||||
* @throws ValueException si $func n'est pas une fonction ou une méthode valide
|
|
||||||
*/
|
|
||||||
static final function ensure_func(&$func, $class_or_object, &$args=null): void {
|
|
||||||
if (!self::check_func($func, $class_or_object, $args)) {
|
|
||||||
throw ValueException::invalid_type($func, "callable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final function _prepare($func): array {
|
|
||||||
$object = null;
|
|
||||||
if (is_callable($func)) {
|
|
||||||
if (is_array($func)) {
|
|
||||||
$rf = new ReflectionMethod(...$func);
|
|
||||||
$object = $func[0];
|
|
||||||
if (is_string($object)) $object = null;
|
|
||||||
} elseif ($func instanceof Closure) {
|
|
||||||
$rf = new ReflectionFunction($func);
|
|
||||||
} elseif (is_string($func) && strpos($func, "::") === false) {
|
|
||||||
$rf = new ReflectionFunction($func);
|
|
||||||
} else {
|
|
||||||
$rf = new ReflectionMethod($func);
|
|
||||||
}
|
|
||||||
} elseif ($func instanceof ReflectionMethod) {
|
|
||||||
$rf = $func;
|
|
||||||
} elseif ($func instanceof ReflectionFunction) {
|
|
||||||
$rf = $func;
|
|
||||||
} elseif (is_array($func) && count($func) == 2 && isset($func[0]) && isset($func[1])
|
|
||||||
&& ($func[1] instanceof ReflectionMethod || $func[1] instanceof ReflectionFunction)) {
|
|
||||||
$object = $func[0];
|
|
||||||
if (is_string($object)) $object = null;
|
|
||||||
$rf = $func[1];
|
|
||||||
} elseif (is_string($func) && strpos($func, "::") === false) {
|
|
||||||
$rf = new ReflectionFunction($func);
|
|
||||||
} else {
|
|
||||||
throw ValueException::invalid_type($func, "callable");
|
|
||||||
}
|
|
||||||
$minArgs = $rf->getNumberOfRequiredParameters();
|
|
||||||
$maxArgs = $rf->getNumberOfParameters();
|
|
||||||
$variadic = $rf->isVariadic();
|
|
||||||
return [$rf instanceof ReflectionMethod, $object, $rf, $minArgs, $maxArgs, $variadic];
|
|
||||||
}
|
|
||||||
|
|
||||||
static final function _fill(array $context, array &$args): void {
|
|
||||||
$minArgs = $context[3];
|
|
||||||
$maxArgs = $context[4];
|
|
||||||
$variadic = $context[5];
|
|
||||||
if (!$variadic) $args = array_slice($args, 0, $maxArgs);
|
|
||||||
while (count($args) < $minArgs) $args[] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static final function _call($context, array $args) {
|
|
||||||
self::_fill($context, $args);
|
|
||||||
$use_object = $context[0];
|
|
||||||
$object = $context[1];
|
|
||||||
$method = $context[2];
|
|
||||||
if ($use_object) {
|
|
||||||
if (count($args) === 0) return $method->invoke($object);
|
|
||||||
else return $method->invokeArgs($object, $args);
|
|
||||||
} else {
|
|
||||||
if (count($args) === 0) return $method->invoke();
|
|
||||||
else return $method->invokeArgs($args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appeler la fonction spécifiée avec les arguments spécifiés.
|
|
||||||
* Adapter $args en fonction du nombre réel d'arguments de $func
|
|
||||||
*
|
|
||||||
* @param callable|ReflectionFunction|ReflectionMethod $func
|
|
||||||
*/
|
|
||||||
static final function call($func, ...$args) {
|
|
||||||
return self::_call(self::_prepare($func), $args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** remplacer $value par $func($value, ...$args) */
|
|
||||||
static final function apply(&$value, $func, ...$args): void {
|
|
||||||
if ($func !== null) {
|
|
||||||
if ($args) $args = array_merge([$value], $args);
|
|
||||||
else $args = [$value];
|
|
||||||
$value = self::call($func, ...$args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MASK_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC;
|
|
||||||
const MASK_P = ReflectionMethod::IS_PUBLIC;
|
|
||||||
const METHOD_PS = ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC;
|
|
||||||
const METHOD_P = ReflectionMethod::IS_PUBLIC;
|
|
||||||
|
|
||||||
private static function matches(string $name, array $includes, array $excludes): bool {
|
|
||||||
if ($includes) {
|
|
||||||
$matches = false;
|
|
||||||
foreach ($includes as $include) {
|
|
||||||
if (substr($include, 0, 1) == "/") {
|
|
||||||
# expression régulière
|
|
||||||
if (preg_match($include, $name)) {
|
|
||||||
$matches = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
# tester la présence de la sous-chaine
|
|
||||||
if (strpos($name, $include) !== false) {
|
|
||||||
$matches = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!$matches) return false;
|
|
||||||
}
|
|
||||||
foreach ($excludes as $exclude) {
|
|
||||||
if (substr($exclude, 0, 1) == "/") {
|
|
||||||
# expression régulière
|
|
||||||
if (preg_match($exclude, $name)) return false;
|
|
||||||
} else {
|
|
||||||
# tester la présence de la sous-chaine
|
|
||||||
if (strpos($name, $exclude) !== false) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @var Schema */
|
|
||||||
private static $call_all_params_schema;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* retourner la liste des méthodes de $class_or_object qui correspondent au
|
|
||||||
* filtre $options. le filtre doit respecter le schéme {@link CALL_ALL_PARAMS_SCHEMA}
|
|
||||||
*/
|
|
||||||
static function get_all($class_or_object, $params=null): array {
|
|
||||||
Schema::nv($paramsv, $params, null
|
|
||||||
, self::$call_all_params_schema, ref_func::CALL_ALL_PARAMS_SCHEMA);
|
|
||||||
if (is_callable($class_or_object, true) && is_array($class_or_object)) {
|
|
||||||
# callable sous forme de tableau
|
|
||||||
$class_or_object = $class_or_object[0];
|
|
||||||
}
|
|
||||||
if (is_string($class_or_object)) {
|
|
||||||
# lister les méthodes publiques statiques de la classe
|
|
||||||
$mask = self::MASK_PS;
|
|
||||||
$expected = self::METHOD_PS;
|
|
||||||
$c = new ReflectionClass($class_or_object);
|
|
||||||
} elseif (is_object($class_or_object)) {
|
|
||||||
# lister les méthodes publiques de la classe
|
|
||||||
$c = new ReflectionClass($class_or_object);
|
|
||||||
$mask = $params["static_only"]? self::MASK_PS: self::MASK_P;
|
|
||||||
$expected = $params["static_only"]? self::METHOD_PS: self::METHOD_P;
|
|
||||||
} else {
|
|
||||||
throw new ValueException("$class_or_object: vous devez spécifier une classe ou un objet");
|
|
||||||
}
|
|
||||||
$prefix = $params["prefix"]; $prefixlen = strlen($prefix);
|
|
||||||
$args = $params["args"];
|
|
||||||
$includes = $params["include"];
|
|
||||||
$excludes = $params["exclude"];
|
|
||||||
$methods = [];
|
|
||||||
foreach ($c->getMethods() as $m) {
|
|
||||||
if (($m->getModifiers() & $mask) != $expected) continue;
|
|
||||||
$name = $m->getName();
|
|
||||||
if (substr($name, 0, $prefixlen) != $prefix) continue;
|
|
||||||
if (!self::matches($name, $includes, $excludes)) continue;
|
|
||||||
$methods[] = cl::merge([$class_or_object, $name], $args);
|
|
||||||
}
|
|
||||||
return $methods;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Appeler toutes les méthodes publiques de $object_or_class et retourner un
|
|
||||||
* tableau [$method_name => $return_value] des valeurs de retour.
|
|
||||||
*/
|
|
||||||
static final function call_all($class_or_object, $params=null): array {
|
|
||||||
$methods = self::get_all($class_or_object, $params);
|
|
||||||
$values = [];
|
|
||||||
foreach ($methods as $method) {
|
|
||||||
self::fix_args($method, $args);
|
|
||||||
$values[$method[1]] = self::call($method, ...$args);
|
|
||||||
}
|
|
||||||
return $values;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* tester si $func est une chaine de la forme "XXX" où XXX est une classe
|
|
||||||
* valide, ou un tableau de la forme ["XXX", ...]
|
|
||||||
*
|
|
||||||
* NB: il est possible d'avoir {@link is_static()} et {@link is_class()}
|
|
||||||
* vraies pour la même valeur. s'il faut supporter les deux cas, appeler
|
|
||||||
* {@link is_static()} d'abord, mais dans ce cas, on ne supporte que les
|
|
||||||
* classes qui sont dans un package
|
|
||||||
*/
|
|
||||||
static final function is_class($class): bool {
|
|
||||||
if (is_string($class)) {
|
|
||||||
return class_exists($class);
|
|
||||||
} elseif (is_array($class) && array_key_exists(0, $class)) {
|
|
||||||
return class_exists($class[0]);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* en assumant que {@link is_class()} est vrai, si $class est un tableau de
|
|
||||||
* plus de 1 éléments, alors déplacer les éléments supplémentaires au début de
|
|
||||||
* $args. par exemple:
|
|
||||||
* ~~~
|
|
||||||
* $class = ["class", "arg1", "arg2"];
|
|
||||||
* $args = ["arg3"];
|
|
||||||
* func::fix_class_args($class, $args)
|
|
||||||
* # $class === "class"
|
|
||||||
* # $args === ["arg1", "arg2", "arg3"]
|
|
||||||
* ~~~
|
|
||||||
*
|
|
||||||
* @return bool true si la correction a été faite
|
|
||||||
*/
|
|
||||||
static final function fix_class_args(&$class, ?array &$args): bool {
|
|
||||||
if ($args === null) $args = [];
|
|
||||||
if (is_array($class)) {
|
|
||||||
if (count($class) > 1) {
|
|
||||||
$prefix_args = array_slice($class, 1);
|
|
||||||
$class = array_slice($class, 0, 1)[0];
|
|
||||||
$args = array_merge($prefix_args, $args);
|
|
||||||
} else {
|
|
||||||
$class = $class[0];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* s'assurer que $class est une classe et renseigner le cas échéant les
|
|
||||||
* arguments.
|
|
||||||
*
|
|
||||||
* @return bool true si c'est une classe valide. il ne reste plus qu'à
|
|
||||||
* l'instancier avec {@link cons()}
|
|
||||||
*/
|
|
||||||
static final function check_class(&$class, &$args=null): bool {
|
|
||||||
if (self::is_class($class)) {
|
|
||||||
self::fix_class_args($class, $args);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comme {@link check_class()} mais lance une exception si la classe est
|
|
||||||
* invalide
|
|
||||||
*
|
|
||||||
* @throws ValueException si $class n'est pas une classe valide
|
|
||||||
*/
|
|
||||||
static final function ensure_class(&$class, &$args=null): void {
|
|
||||||
if (!self::check_class($class, $args)) {
|
|
||||||
throw ValueException::invalid_type($class, "class");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instancier la classe avec les arguments spécifiés.
|
|
||||||
* Adapter $args en fonction du nombre réel d'arguments du constructeur
|
|
||||||
*/
|
|
||||||
static final function cons(string $class, ...$args) {
|
|
||||||
$c = new ReflectionClass($class);
|
|
||||||
$rf = $c->getConstructor();
|
|
||||||
if ($rf === null) {
|
|
||||||
return $c->newInstance();
|
|
||||||
} else {
|
|
||||||
if (!$rf->isVariadic()) {
|
|
||||||
$minArgs = $rf->getNumberOfRequiredParameters();
|
|
||||||
$maxArgs = $rf->getNumberOfParameters();
|
|
||||||
$args = array_slice($args, 0, $maxArgs);
|
|
||||||
while (count($args) < $minArgs) {
|
|
||||||
$args[] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $c->newInstanceArgs($args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,7 +29,7 @@ class DateInterval extends \DateInterval {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function __construct($duration) {
|
function __construct($duration) {
|
||||||
if (is_int($duration)) $duration = "PT${duration}S";
|
if (is_numeric($duration)) $duration = "PT${duration}S";
|
||||||
if ($duration instanceof \DateInterval) {
|
if ($duration instanceof \DateInterval) {
|
||||||
$this->y = $duration->y;
|
$this->y = $duration->y;
|
||||||
$this->m = $duration->m;
|
$this->m = $duration->m;
|
||||||
|
@ -3,6 +3,7 @@ namespace nulib\php\time;
|
|||||||
|
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Delay: une durée jusqu'à un moment destination. le moment destination
|
* Class Delay: une durée jusqu'à un moment destination. le moment destination
|
||||||
@ -115,6 +116,10 @@ class Delay {
|
|||||||
$this->repr = $repr;
|
$this->repr = $repr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function __clone() {
|
||||||
|
$this->dest = clone $this->dest;
|
||||||
|
}
|
||||||
|
|
||||||
function __serialize(): array {
|
function __serialize(): array {
|
||||||
return [$this->dest, $this->repr];
|
return [$this->dest, $this->repr];
|
||||||
}
|
}
|
||||||
@ -130,7 +135,7 @@ class Delay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addDuration($duration) {
|
function addDuration($duration) {
|
||||||
if (is_int($duration) && $duration < 0) {
|
if (is_numeric($duration) && $duration < 0) {
|
||||||
$this->dest->sub(DateInterval::with(-$duration));
|
$this->dest->sub(DateInterval::with(-$duration));
|
||||||
} else {
|
} else {
|
||||||
$this->dest->add(DateInterval::with($duration));
|
$this->dest->add(DateInterval::with($duration));
|
||||||
@ -138,7 +143,7 @@ class Delay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function subDuration($duration) {
|
function subDuration($duration) {
|
||||||
if (is_int($duration) && $duration < 0) {
|
if (is_numeric($duration) && $duration < 0) {
|
||||||
$this->dest->add(DateInterval::with(-$duration));
|
$this->dest->add(DateInterval::with(-$duration));
|
||||||
} else {
|
} else {
|
||||||
$this->dest->sub(DateInterval::with($duration));
|
$this->dest->sub(DateInterval::with($duration));
|
||||||
|
42
php/src/ref/schema/ref_input.php
Normal file
42
php/src/ref/schema/ref_input.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\ref\schema;
|
||||||
|
|
||||||
|
class ref_input {
|
||||||
|
const ACCESS_AUTO = 0, ACCESS_KEY = 1, ACCESS_PROPERTY = 2;
|
||||||
|
|
||||||
|
const INPUT_PARAMS_SCHEMA = [
|
||||||
|
"access_type" => ["int", self::ACCESS_AUTO, "type d'accès: clé ou propriété"],
|
||||||
|
"allow_empty" => ["bool", true, "la chaine vide est-elle autorisée?"],
|
||||||
|
"allow_null" => ["bool", true, "la valeur null est-elle autorisée?"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const ACCESS_PARAMS_SCHEMA = [
|
||||||
|
"allow_empty" => ["bool", true, "la chaine vide est-elle autorisée?"],
|
||||||
|
"allow_null" => ["bool", null, "la valeur null est-elle autorisée?"],
|
||||||
|
"allow_false" => ["bool", null, "la valeur false est-elle autorisée?"],
|
||||||
|
"protect_dest" => ["bool", null, "faut-il protéger la destination?"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const VALUE_ACCESS_PARAMS_SCHEMA = [
|
||||||
|
"allow_null" => ["bool", false],
|
||||||
|
"allow_false" => ["bool", true],
|
||||||
|
"protect_dest" => ["bool", false],
|
||||||
|
];
|
||||||
|
|
||||||
|
const ARRAY_ACCESS_PARAMS_SCHEMA = [
|
||||||
|
"allow_null" => ["bool", true],
|
||||||
|
"allow_false" => ["bool", false],
|
||||||
|
"protect_dest" => ["bool", true],
|
||||||
|
"key_prefix" => ["?string", null, "préfixe des clés pour les méthodes ensureXxx()"],
|
||||||
|
"key_suffix" => ["?string", null, "suffixe des clés pour les méthodes ensureXxx()"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const PROPERTY_ACCESS_PARAMS_SCHEMA = [
|
||||||
|
"allow_null" => ["bool", true],
|
||||||
|
"allow_false" => ["bool", false],
|
||||||
|
"protect_dest" => ["bool", true],
|
||||||
|
"key_prefix" => ["?string", null, "préfixe des clés pour les méthodes ensureXxx()"],
|
||||||
|
"key_suffix" => ["?string", null, "suffixe des clés pour les méthodes ensureXxx()"],
|
||||||
|
"map_names" => ["bool", true, "faut-il mapper les clés en camelCase?"]
|
||||||
|
];
|
||||||
|
}
|
@ -26,6 +26,8 @@ class ref_schema {
|
|||||||
"messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"],
|
"messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"],
|
||||||
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
|
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
|
||||||
"format" => [null, null, "format à utiliser pour l'affichage"],
|
"format" => [null, null, "format à utiliser pour l'affichage"],
|
||||||
|
"size" => ["?int", null, "nom de caractères ou de chiffres de la valeur"],
|
||||||
|
"precision" => ["?int", null, "nombre de chiffres après la virgule pour une valeur numérique flottante"],
|
||||||
"" => ["array", ["scalar"], "nature du schéma",
|
"" => ["array", ["scalar"], "nature du schéma",
|
||||||
"schema" => self::NATURE_METASCHEMA,
|
"schema" => self::NATURE_METASCHEMA,
|
||||||
],
|
],
|
||||||
@ -37,25 +39,48 @@ class ref_schema {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const MESSAGES = [
|
const MESSAGES = [
|
||||||
"missing" => "Vous devez spécifier cette valeur",
|
"missing" => "vous devez spécifier cette valeur",
|
||||||
"unavailable" => "Vous devez spécifier cette valeur",
|
"unavailable" => "vous devez spécifier cette valeur",
|
||||||
"null" => "Cette valeur ne doit pas être nulle",
|
"null" => "cette valeur ne doit pas être nulle",
|
||||||
"empty" => "Cette valeur ne doit pas être vide",
|
"empty" => "cette valeur ne doit pas être vide",
|
||||||
"invalid" => "Cette valeur est invalide",
|
"invalid" => "cette valeur est invalide",
|
||||||
|
];
|
||||||
|
|
||||||
|
const PARAMS_SCHEMA = [
|
||||||
|
"analyze" => ["bool", true, "faut-il analyser la valeur?"],
|
||||||
|
"reanalyze" => ["bool", true, "faut-il forcer l'analyse de la valeur?"],
|
||||||
|
"normalize" => ["bool", true, "faut-il normaliser la valeur?"],
|
||||||
|
"renormalize" => ["bool", true, "faut-il forcer la normalisation de la valeur?"],
|
||||||
|
"throw" => ["bool", true, "faut-il lancer une exception en cas d'erreur?"],
|
||||||
|
//...ref_input::INPUT_PARAMS_SCHEMA,
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array clés supplémentaires de schéma de la nature scalaire */
|
/** @var array clés supplémentaires de schéma de la nature scalaire */
|
||||||
const SCALAR_NATURE_METASCHEMA = [
|
const SCALAR_NATURE_METASCHEMA = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SCALAR_PARAMS_SCHEMA = [
|
||||||
|
];
|
||||||
|
|
||||||
/** @var array clés supplémentaires de schéma de la nature associative */
|
/** @var array clés supplémentaires de schéma de la nature associative */
|
||||||
const ASSOC_NATURE_METASCHEMA = [
|
const ASSOC_NATURE_METASCHEMA = [
|
||||||
"ensure_array" => ["bool", false, "faut-il s'assurer que le tableau destination est non nul?"],
|
"ensure_array" => ["bool", null, "faut-il s'assurer que le tableau destination est non nul?"],
|
||||||
"ensure_keys" => ["bool", true, "faut-il s'assurer que toutes les clés existent?"],
|
"ensure_assoc" => ["bool", null, "faut-il s'assurer que le tableau destination est associatif?"],
|
||||||
"ensure_order" => ["bool", true, "faut-il s'assurer que les clés soient dans l'ordre?"],
|
"ensure_keys" => ["bool", null, "faut-il s'assurer que toutes les clés existent avec la valeur par défaut?"],
|
||||||
|
"ensure_order" => ["bool", null, "faut-il s'assurer que les clés soient dans l'ordre?"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const ASSOC_PARAMS_SCHEMA = [
|
||||||
|
"ensure_array" => ["bool", false],
|
||||||
|
"ensure_assoc" => ["bool", true],
|
||||||
|
"ensure_keys" => ["bool", true],
|
||||||
|
"ensure_order" => ["bool", true],
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array clés supplémentaires de schéma de la nature liste */
|
/** @var array clés supplémentaires de schéma de la nature liste */
|
||||||
const LIST_NATURE_METASCHEMA = [
|
const LIST_NATURE_METASCHEMA = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const LIST_PARAMS_SCHEMA = [
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,21 @@ class str {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vérifier si $s a le préfixe $prefix
|
||||||
|
* - si $prefix commence par /, c'est une expression régulière, et elle doit
|
||||||
|
* matcher $s
|
||||||
|
* - sinon $s doit commencer par la chaine $prefix
|
||||||
|
*/
|
||||||
|
static final function match_prefix(?string $s, ?string $prefix): bool {
|
||||||
|
if ($s === null || $prefix === null) return false;
|
||||||
|
if (substr($prefix, 0, 1) === "/") {
|
||||||
|
return preg_match($prefix, $s);
|
||||||
|
} else {
|
||||||
|
return self::_starts_with($prefix, $s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ajouter $sep$prefix$text$suffix à $s si $text est non vide
|
* ajouter $sep$prefix$text$suffix à $s si $text est non vide
|
||||||
*
|
*
|
||||||
@ -253,6 +268,21 @@ class str {
|
|||||||
$s .= $prefix.$text.$suffix;
|
$s .= $prefix.$text.$suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dans $s, faire les remplacements $key => $value du tableau $replaces
|
||||||
|
*
|
||||||
|
* si $verifix_order, le tableau est réordonné par taille de chaine source
|
||||||
|
*/
|
||||||
|
static final function replace(?string $s, ?array $replaces, bool $verifix_order=true): ?string {
|
||||||
|
if ($s === null || $replaces === null) return $s;
|
||||||
|
if ($verifix_order) {
|
||||||
|
uksort($replaces, function ($a, $b) {
|
||||||
|
return -cv::compare(strlen($a), strlen($b));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return str_replace(array_keys($replaces), array_values($replaces), $s);
|
||||||
|
}
|
||||||
|
|
||||||
/** si $text est non vide, ajouter $prefix$text$suffix à $s en séparant la valeur avec un espace */
|
/** si $text est non vide, ajouter $prefix$text$suffix à $s en séparant la valeur avec un espace */
|
||||||
static final function add(?string &$s, ?string $text, ?string $prefix=null, ?string $suffix=null): void {
|
static final function add(?string &$s, ?string $text, ?string $prefix=null, ?string $suffix=null): void {
|
||||||
self::addsep($s, " ", $text, $prefix, $suffix);
|
self::addsep($s, " ", $text, $prefix, $suffix);
|
||||||
@ -298,6 +328,22 @@ class str {
|
|||||||
else return preg_split('/\s+/', $s);
|
else return preg_split('/\s+/', $s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* découper la chaine:
|
||||||
|
* - avec preg_split si $sep est une expression régulière /re/
|
||||||
|
* - avec explode sinon
|
||||||
|
*/
|
||||||
|
static final function split(string $sep, ?string $s): ?array {
|
||||||
|
if ($s === null) return null;
|
||||||
|
if ($sep === "") {
|
||||||
|
return [$s];
|
||||||
|
} elseif (substr($sep, 0, 1) === "/") {
|
||||||
|
return preg_split($sep, $s);
|
||||||
|
} else {
|
||||||
|
return explode($sep, $s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* joindre les éléments de $parts comme avec implode(), mais en ignorant les
|
* joindre les éléments de $parts comme avec implode(), mais en ignorant les
|
||||||
* valeurs fausses (cela n'inclue pas la chaine "0")
|
* valeurs fausses (cela n'inclue pas la chaine "0")
|
||||||
|
@ -78,7 +78,7 @@ class ComposerFile {
|
|||||||
];
|
];
|
||||||
|
|
||||||
function selectProfile(string $profile, ComposerPmanFile $config): void {
|
function selectProfile(string $profile, ComposerPmanFile $config): void {
|
||||||
$config = $config->getProfileConfig($profile);
|
$config = $config->getProfileConfig($profile, $this->getRequires(), $this->getRequireDevs());
|
||||||
// corriger les liens
|
// corriger les liens
|
||||||
$deps = cl::merge(array_keys($config["require"]), array_keys($config["require-dev"]));
|
$deps = cl::merge(array_keys($config["require"]), array_keys($config["require-dev"]));
|
||||||
$paths = [];
|
$paths = [];
|
||||||
|
@ -4,6 +4,7 @@ namespace nulib\tools\pman;
|
|||||||
use nulib\A;
|
use nulib\A;
|
||||||
use nulib\ext\yaml;
|
use nulib\ext\yaml;
|
||||||
use nulib\os\path;
|
use nulib\os\path;
|
||||||
|
use nulib\str;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
|
|
||||||
class ComposerPmanFile {
|
class ComposerPmanFile {
|
||||||
@ -49,6 +50,8 @@ class ComposerPmanFile {
|
|||||||
$composer =& $data["composer"];
|
$composer =& $data["composer"];
|
||||||
A::ensure_array($composer);
|
A::ensure_array($composer);
|
||||||
A::ensure_array($composer["profiles"]);
|
A::ensure_array($composer["profiles"]);
|
||||||
|
A::ensure_array($composer["match_require"]);
|
||||||
|
A::ensure_array($composer["match_require-dev"]);
|
||||||
foreach ($composer["profiles"] as $profileName) {
|
foreach ($composer["profiles"] as $profileName) {
|
||||||
$profile =& $composer[$profileName];
|
$profile =& $composer[$profileName];
|
||||||
A::ensure_array($profile);
|
A::ensure_array($profile);
|
||||||
@ -61,11 +64,43 @@ class ComposerPmanFile {
|
|||||||
return $this->data;
|
return $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProfileConfig(string $profile): array {
|
function getProfileConfig(string $profile, ?array $composerRequires=null, ?array $composerRequireDevs=null): array {
|
||||||
$config = $this->data["composer"][$profile] ?? null;
|
$config = $this->data["composer"][$profile] ?? null;
|
||||||
if ($config === null) {
|
if ($config === null) {
|
||||||
throw new ValueException("$profile: profil invalide");
|
throw new ValueException("$profile: profil invalide");
|
||||||
}
|
}
|
||||||
|
if ($composerRequires !== null) {
|
||||||
|
$matchRequires = $this->data["composer"]["match_require"];
|
||||||
|
foreach ($composerRequires as $dep => $version) {
|
||||||
|
$found = false;
|
||||||
|
foreach ($matchRequires as $matchRequire) {
|
||||||
|
if (str::match_prefix($dep, $matchRequire)) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$require = $config["require"][$dep] ?? null;
|
||||||
|
if ($found && $require === null) {
|
||||||
|
$config["require"][$dep] = $version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($composerRequireDevs !== null) {
|
||||||
|
$matchRequireDevs = $this->data["composer"]["match_require-dev"];
|
||||||
|
foreach ($composerRequireDevs as $dep => $version) {
|
||||||
|
$found = false;
|
||||||
|
foreach ($matchRequireDevs as $matchRequireDev) {
|
||||||
|
if (str::match_prefix($dep, $matchRequireDev)) {
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$requireDev = $config["require-dev"][$dep] ?? null;
|
||||||
|
if ($found && $requireDev === null) {
|
||||||
|
$config["require"][$dep] = $version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace nulib {
|
|
||||||
use nulib\tests\TestCase;
|
|
||||||
use nulib\impl\config;
|
|
||||||
use nulib\impl\myapp;
|
|
||||||
use nulib\impl\MyApplication1;
|
|
||||||
use nulib\impl\MyApplication2;
|
|
||||||
|
|
||||||
class appTest extends TestCase {
|
|
||||||
function testWith() {
|
|
||||||
$projdir = config::get_projdir();
|
|
||||||
$cwd = getcwd();
|
|
||||||
|
|
||||||
myapp::reset();
|
|
||||||
$app1 = myapp::with(MyApplication1::class);
|
|
||||||
self::assertSame([
|
|
||||||
"projdir" => $projdir,
|
|
||||||
"vendor" => [
|
|
||||||
"bindir" => "$projdir/vendor/bin",
|
|
||||||
"autoload" => "$projdir/vendor/autoload.php",
|
|
||||||
],
|
|
||||||
"appcode" => "nur-sery",
|
|
||||||
"cwd" => $cwd,
|
|
||||||
"datadir" => "$projdir/devel",
|
|
||||||
"etcdir" => "$projdir/devel/etc",
|
|
||||||
"vardir" => "$projdir/devel/var",
|
|
||||||
"logdir" => "$projdir/devel/log",
|
|
||||||
"profile" => "devel",
|
|
||||||
"appgroup" => null,
|
|
||||||
"name" => "my-application1",
|
|
||||||
"title" => null,
|
|
||||||
], $app1->getParams());
|
|
||||||
|
|
||||||
$app2 = myapp::with(MyApplication2::class, $app1);
|
|
||||||
self::assertSame([
|
|
||||||
"projdir" => $projdir,
|
|
||||||
"vendor" => [
|
|
||||||
"bindir" => "$projdir/vendor/bin",
|
|
||||||
"autoload" => "$projdir/vendor/autoload.php",
|
|
||||||
],
|
|
||||||
"appcode" => "nur-sery",
|
|
||||||
"cwd" => $cwd,
|
|
||||||
"datadir" => "$projdir/devel",
|
|
||||||
"etcdir" => "$projdir/devel/etc",
|
|
||||||
"vardir" => "$projdir/devel/var",
|
|
||||||
"logdir" => "$projdir/devel/log",
|
|
||||||
"profile" => "devel",
|
|
||||||
"appgroup" => null,
|
|
||||||
"name" => "my-application2",
|
|
||||||
"title" => null,
|
|
||||||
], $app2->getParams());
|
|
||||||
}
|
|
||||||
|
|
||||||
function testInit() {
|
|
||||||
$projdir = config::get_projdir();
|
|
||||||
$cwd = getcwd();
|
|
||||||
|
|
||||||
myapp::reset();
|
|
||||||
myapp::init(MyApplication1::class);
|
|
||||||
self::assertSame([
|
|
||||||
"projdir" => $projdir,
|
|
||||||
"vendor" => [
|
|
||||||
"bindir" => "$projdir/vendor/bin",
|
|
||||||
"autoload" => "$projdir/vendor/autoload.php",
|
|
||||||
],
|
|
||||||
"appcode" => "nur-sery",
|
|
||||||
"cwd" => $cwd,
|
|
||||||
"datadir" => "$projdir/devel",
|
|
||||||
"etcdir" => "$projdir/devel/etc",
|
|
||||||
"vardir" => "$projdir/devel/var",
|
|
||||||
"logdir" => "$projdir/devel/log",
|
|
||||||
"profile" => "devel",
|
|
||||||
"appgroup" => null,
|
|
||||||
"name" => "my-application1",
|
|
||||||
"title" => null,
|
|
||||||
], myapp::get()->getParams());
|
|
||||||
|
|
||||||
myapp::init(MyApplication2::class);
|
|
||||||
self::assertSame([
|
|
||||||
"projdir" => $projdir,
|
|
||||||
"vendor" => [
|
|
||||||
"bindir" => "$projdir/vendor/bin",
|
|
||||||
"autoload" => "$projdir/vendor/autoload.php",
|
|
||||||
],
|
|
||||||
"appcode" => "nur-sery",
|
|
||||||
"cwd" => $cwd,
|
|
||||||
"datadir" => "$projdir/devel",
|
|
||||||
"etcdir" => "$projdir/devel/etc",
|
|
||||||
"vardir" => "$projdir/devel/var",
|
|
||||||
"logdir" => "$projdir/devel/log",
|
|
||||||
"profile" => "devel",
|
|
||||||
"appgroup" => null,
|
|
||||||
"name" => "my-application2",
|
|
||||||
"title" => null,
|
|
||||||
], myapp::get()->getParams());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace nulib\impl {
|
|
||||||
|
|
||||||
use nulib\app\cli\Application;
|
|
||||||
use nulib\os\path;
|
|
||||||
use nulib\app;
|
|
||||||
|
|
||||||
class config {
|
|
||||||
const PROJDIR = __DIR__.'/..';
|
|
||||||
|
|
||||||
static function get_projdir(): string {
|
|
||||||
return path::abspath(self::PROJDIR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class myapp extends app {
|
|
||||||
static function reset(): void {
|
|
||||||
self::$app = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyApplication1 extends Application {
|
|
||||||
const PROJDIR = config::PROJDIR;
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class MyApplication2 extends Application {
|
|
||||||
const PROJDIR = null;
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,10 +12,10 @@ class clTest extends TestCase {
|
|||||||
}
|
}
|
||||||
function test_same_keys() {
|
function test_same_keys() {
|
||||||
$array = ["a" => 42, "b" => "tesxt"]; $arrayKeys = array_keys($array);
|
$array = ["a" => 42, "b" => "tesxt"]; $arrayKeys = array_keys($array);
|
||||||
$xarray = ["parasite0", "a" => 42, "parasite1", "b" => "tesxt"]; $xarrayKeys = array_keys($array);
|
$missingArray = ["c" => true]; $missingArrayKeys = array_keys($missingArray);
|
||||||
$ref = ["a" => "int", "b" => "text"]; $refKeys = array_keys($ref);
|
$ref = ["a" => "int", "b" => "text"]; $refKeys = array_keys($ref);
|
||||||
$missingArray = ["c" => true]; $missingKeys = array_keys($missingArray);
|
$missingRef = ["c" => "bool"]; $missingRefKeys = array_keys($missingRef);
|
||||||
$missingRef = ["c" => "bool"]; $missingKeys = array_keys($missingRef);
|
$xarray = ["parasite0", "a" => 42, "parasite1", "b" => "tesxt"];
|
||||||
|
|
||||||
$this->checkKeys(null, null, true, [], [], []);
|
$this->checkKeys(null, null, true, [], [], []);
|
||||||
$this->checkKeys(null, [], true, [], [], []);
|
$this->checkKeys(null, [], true, [], [], []);
|
||||||
@ -29,8 +29,8 @@ class clTest extends TestCase {
|
|||||||
$this->checkKeys($array, [], true, [], $arrayKeys, []);
|
$this->checkKeys($array, [], true, [], $arrayKeys, []);
|
||||||
|
|
||||||
$this->checkKeys($array, $ref, true, $arrayKeys, [], []);
|
$this->checkKeys($array, $ref, true, $arrayKeys, [], []);
|
||||||
$this->checkKeys(cl::merge($array, $missingArray), $ref, true, $arrayKeys, $missingKeys, []);
|
$this->checkKeys(cl::merge($array, $missingArray), $ref, true, $arrayKeys, $missingArrayKeys, []);
|
||||||
$this->checkKeys($array, cl::merge($ref, $missingRef), false, $arrayKeys, [], $missingKeys);
|
$this->checkKeys($array, cl::merge($ref, $missingRef), false, $arrayKeys, [], $missingRefKeys);
|
||||||
|
|
||||||
$this->checkKeys($xarray, $ref, false, $arrayKeys, [0, 1], []);
|
$this->checkKeys($xarray, $ref, false, $arrayKeys, [0, 1], []);
|
||||||
}
|
}
|
||||||
|
75
php/tests/db/sqlite/ChannelMigrationTest.php
Normal file
75
php/tests/db/sqlite/ChannelMigrationTest.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
namespace nulib\db\sqlite;
|
||||||
|
|
||||||
|
use nulib\db\Capacitor;
|
||||||
|
use nulib\db\sqlite\impl\MyChannel;
|
||||||
|
use nulib\db\sqlite\impl\MyChannelV2;
|
||||||
|
use nulib\db\sqlite\impl\MyChannelV3;
|
||||||
|
use nulib\output\msg;
|
||||||
|
use nulib\output\std\StdMessenger;
|
||||||
|
use nulib\php\time\DateTime;
|
||||||
|
use nulib\tests\TestCase;
|
||||||
|
|
||||||
|
class ChannelMigrationTest extends TestCase {
|
||||||
|
static function setUpBeforeClass(): void {
|
||||||
|
parent::setUpBeforeClass();
|
||||||
|
msg::set_messenger_class(StdMessenger::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addData(MyChannel $channel, array $data): void {
|
||||||
|
foreach ($data as [$name, $value, $dateCre, $dateMod, $age]) {
|
||||||
|
$channel->charge([
|
||||||
|
"name" => $name,
|
||||||
|
"value" => $value,
|
||||||
|
"date_cre" => $dateCre,
|
||||||
|
"date_mod" => $dateMod,
|
||||||
|
"age" => $age,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMigration() {
|
||||||
|
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||||
|
$data = [
|
||||||
|
["first", "premier", new DateTime(), new DateTime(), 15],
|
||||||
|
["second", "deuxieme", new DateTime(), new DateTime(), 15],
|
||||||
|
];
|
||||||
|
|
||||||
|
new Capacitor($storage, $channel = new MyChannel());
|
||||||
|
$channel->reset(true);
|
||||||
|
$this->addData($channel, $data);
|
||||||
|
|
||||||
|
new Capacitor($storage, $channel = new MyChannelV2());
|
||||||
|
$this->addData($channel, $data);
|
||||||
|
|
||||||
|
new Capacitor($storage, $channel = new MyChannelV3());
|
||||||
|
$this->addData($channel, $data);
|
||||||
|
|
||||||
|
$sql = $channel->getCapacitor()->getCreateSql();
|
||||||
|
$class = MyChannelV3::class;
|
||||||
|
$expected = <<<EOT
|
||||||
|
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||||
|
-- autogénéré à partir de $class
|
||||||
|
|
||||||
|
-- 0init
|
||||||
|
create table if not exists my (
|
||||||
|
id_ integer primary key autoincrement
|
||||||
|
, name varchar
|
||||||
|
, value varchar
|
||||||
|
, item__ mediumtext
|
||||||
|
, item__sum_ varchar(40)
|
||||||
|
, created_ datetime
|
||||||
|
, modified_ datetime
|
||||||
|
);
|
||||||
|
|
||||||
|
-- dates
|
||||||
|
alter table my add column date_cre datetime;
|
||||||
|
alter table my add column date_mod datetime;
|
||||||
|
|
||||||
|
-- infos
|
||||||
|
alter table my add column age integer;
|
||||||
|
|
||||||
|
EOT;
|
||||||
|
self::assertSame($expected, $sql);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user