Compare commits
1 Commits
dev74
...
wip74/upda
Author | SHA1 | Date | |
---|---|---|---|
c457152c7f |
177
bash/src/pman.sh
177
bash/src/pman.sh
@ -28,152 +28,6 @@ CONFIG_VARS=(
|
||||
UPSTREAM DEVELOP FEATURE RELEASE MAIN TAG_PREFIX TAG_SUFFIX HOTFIX DIST NOAUTO
|
||||
)
|
||||
|
||||
################################################################################
|
||||
|
||||
PMAN_TOOL_PUPS=UPSTREAM
|
||||
PMAN_TOOL_PDEV=DEVELOP
|
||||
PMAN_TOOL_PWIP=FEATURE
|
||||
PMAN_TOOL_PMAIN=MAIN
|
||||
PMAN_TOOL_PDIST=DIST
|
||||
UPSTREAM_BASE= ; UPSTREAM_MERGE_FROM= ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL= ; UPSTREAM_DELETE=
|
||||
DEVELOP_BASE=MAIN ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN ; DEVELOP_PREL=to ; DEVELOP_DELETE=from
|
||||
MAIN_BASE= ; MAIN_MERGE_FROM=DEVELOP ; MAIN_MERGE_TO=DIST ; MAIN_PREL=from ; MAIN_DELETE=
|
||||
DIST_BASE=MAIN ; DIST_MERGE_FROM=MAIN ; DIST_MERGE_TO= ; DIST_PREL= ; DIST_DELETE=
|
||||
FEATURE_BASE=DEVELOP ; FEATURE_MERGE_FROM= ; FEATURE_MERGE_TO=DEVELOP ; FEATURE_PREL= ; FEATURE_DELETE=to
|
||||
|
||||
function get_base_branch() {
|
||||
# afficher la branche depuis laquelle créer la branche $1
|
||||
# retourner 1 en cas d'erreur (pas de branche source)
|
||||
local branch="$1" infos
|
||||
[ -n "$branch" ] || return 1
|
||||
infos="${branch^^}_BASE"; branch="${!infos}"
|
||||
[ -n "$branch" ] && echo "$branch" || return 1
|
||||
}
|
||||
|
||||
function get_merge_from_branch() {
|
||||
# afficher la branche depuis laquelle la branche $1 doit merger
|
||||
# retourner 1 en cas d'erreur (pas de branche source)
|
||||
local branch="$1" infos
|
||||
[ -n "$branch" ] || return 1
|
||||
infos="${branch^^}_MERGE_FROM"; branch="${!infos}"
|
||||
[ -n "$branch" ] && echo "$branch" || return 1
|
||||
}
|
||||
|
||||
function get_merge_to_branch() {
|
||||
# afficher la branche dans laquelle la branche $1 doit merger
|
||||
# retourner 1 en cas d'erreur (pas de branche destination)
|
||||
local branch="$1" infos
|
||||
[ -n "$branch" ] || return 1
|
||||
infos="${branch^^}_MERGE_TO"; branch="${!infos}"
|
||||
[ -n "$branch" ] && echo "$branch" || return 1
|
||||
}
|
||||
|
||||
function should_prel_merge() {
|
||||
# tester si la branche $1 doit être mergée avec prel dans la direction
|
||||
# $2(=to)
|
||||
local branch="$1" dir="${2:-to}" infos
|
||||
[ -n "$branch" ] || return 1
|
||||
infos="${branch^^}_PREL"
|
||||
[ "${!infos}" == "$dir" ]
|
||||
}
|
||||
|
||||
function should_delete_merged() {
|
||||
# tester si la branche $1 doit être supprimée après avoir été mergée dans la
|
||||
# direction $2(=to)
|
||||
local branch="$1" dir="${2:-to}" infos
|
||||
[ -n "$branch" ] || return 1
|
||||
infos="${branch^^}_DELETE"
|
||||
[ "${!infos}" == "$dir" ]
|
||||
}
|
||||
|
||||
: "
|
||||
# description des variables #
|
||||
|
||||
* PMAN_TOOL -- nom de l'outil, e.g pdev, pmain, pdist
|
||||
|
||||
* PMAN_REF_BRANCH -- code de la branche de référence basé sur le nom de l'outil
|
||||
* PmanRefBranch -- nom effectif de la branche si elle est définie dans
|
||||
.pman.conf, vide sinon
|
||||
* IfRefBranch -- nom effectif de la branche *si elle existe*, vide sinon
|
||||
|
||||
* PMAN_UNIQUE -- si la branche de référence est unique. est vide pour les
|
||||
codes de branches multiples, telle que FEATURE
|
||||
|
||||
* PMAN_BASE_BRANCH -- branche de base à partir de laquelle créer la branche
|
||||
de référence
|
||||
* PmanBaseBranch -- nom effectif de la branche de base si elle est définie
|
||||
dans .pman.conf, vide sinon
|
||||
* IfBaseBranch -- nom effectif de la branche de base *si elle existe*, vide
|
||||
sinon
|
||||
|
||||
* PMAN_MERGE_FROM -- code de la branche source à partir de laquelle la fusion
|
||||
est faite dans PMAN_REF_BRANCH. vide si la branche n'a pas de source
|
||||
* PMAN_MERGE_TO -- code de la branche destination dans laquelle la fusion est
|
||||
faite depuis PMAN_REF_BRANCH. vide si la branche n'a pas de destination
|
||||
* PMAN_DIR -- direction de la fusion:
|
||||
'from' si on fait PMAN_MERGE_FROM --> PMAN_REF_BRANCH
|
||||
'to' si on fait PMAN_REF_BRANCH --> PMAN_MERGE_TO
|
||||
* PMAN_PREL_MERGE -- si la fusion devrait se faire avec prel
|
||||
* PMAN_DELETE_MERGED -- s'il faut supprimer la branche source après la fusion
|
||||
|
||||
* PMAN_MERGE_SRC -- code de la branche source pour la fusion, ou vide si la
|
||||
fusion n'est pas possible
|
||||
* PmanMergeSrc -- nom effectif de la branche source si elle est définie
|
||||
dans .pman.conf
|
||||
* IfMergeSrc -- nom effectif de la branche source *si elle existe*, vide
|
||||
sinon
|
||||
|
||||
* PMAN_MERGE_DEST -- code de la branche destination pour la fusion, ou vide si
|
||||
la fusion n'est pas possible
|
||||
* PmanMergeDest -- nom effectif de la branche destination si elle est
|
||||
définie dans .pman.conf
|
||||
* IfMergeDest -- nom effectif de la branche source *si elle existe*, vide
|
||||
sinon
|
||||
|
||||
* PMAN_CAN_MERGE -- indique si la fusion est théoriquement possible, c'est à
|
||||
dire que $PMAN_MERGE_SRC et $PMAN_MERGE_DEST sont tous les deux non vides
|
||||
* IfCanMerge -- indique si la fusion est effectivement possible, c'est à dire
|
||||
que $IfMergeSrc et $IfMergeDest sont tous les deux non vides"
|
||||
|
||||
[ -n "$PMAN_TOOL" ] || PMAN_TOOL="$MYNAME"
|
||||
PMAN_REF_BRANCH="PMAN_TOOL_${PMAN_TOOL^^}"; PMAN_REF_BRANCH="${!PMAN_REF_BRANCH}"
|
||||
function set_pman_vars() {
|
||||
PmanRefBranch="${!PMAN_REF_BRANCH}"
|
||||
case "$PMAN_REF_BRANCH" in
|
||||
FEATURE|RELEASE|HOTFIX) PMAN_UNIQUE=;;
|
||||
*) PMAN_UNIQUE=1;;
|
||||
esac
|
||||
|
||||
PMAN_BASE_BRANCH=$(get_base_branch "$PMAN_REF_BRANCH")
|
||||
[ -n "$PMAN_BASE_BRANCH" ] && PmanBaseBranch="${!PMAN_BASE_BRANCH}" || PmanBaseBranch=
|
||||
|
||||
PMAN_MERGE_FROM=$(get_merge_from_branch "$PMAN_REF_BRANCH")
|
||||
PMAN_MERGE_TO=$(get_merge_to_branch "$PMAN_REF_BRANCH")
|
||||
if [ -n "$1" ]; then PMAN_DIR="$1"
|
||||
else PMAN_DIR=to
|
||||
#elif [ -n "$PMAN_MERGE_TO" ]; then PMAN_DIR=to
|
||||
#else PMAN_DIR=from
|
||||
fi
|
||||
PMAN_PREL_MERGE=$(should_prel_merge "$PMAN_REF_BRANCH" "$PMAN_DIR" && echo 1)
|
||||
PMAN_DELETE_MERGED=$(should_delete_merged "$PMAN_REF_BRANCH" "$PMAN_DIR" && echo 1)
|
||||
case "$PMAN_DIR" in
|
||||
to)
|
||||
PMAN_MERGE_SRC="$PMAN_REF_BRANCH"
|
||||
PMAN_MERGE_DEST="$PMAN_MERGE_TO"
|
||||
;;
|
||||
from)
|
||||
PMAN_MERGE_SRC="$PMAN_MERGE_FROM"
|
||||
PMAN_MERGE_DEST="$PMAN_REF_BRANCH"
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -n "$PMAN_MERGE_SRC" -a -n "$PMAN_MERGE_DEST" ] && PMAN_CAN_MERGE=1 || PMAN_CAN_MERGE=
|
||||
[ -n "$PMAN_MERGE_SRC" ] && PmanMergeSrc="${!PMAN_MERGE_SRC}" || PmanMergeSrc=
|
||||
[ -n "$PMAN_MERGE_DEST" ] && PmanMergeDest="${!PMAN_MERGE_DEST}" || PmanMergeDest=
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
function _init_changelog() {
|
||||
setx date=date +%d/%m/%Y-%H:%M
|
||||
ac_set_tmpfile changelog
|
||||
@ -302,14 +156,14 @@ function check_gitdir() {
|
||||
|
||||
# se mettre à la racine du dépôt git
|
||||
local gitdir
|
||||
git_check_gitvcs || return 1
|
||||
git_ensure_gitvcs
|
||||
setx gitdir=git_get_toplevel
|
||||
cd "$gitdir" || return 1
|
||||
}
|
||||
|
||||
function ensure_gitdir() {
|
||||
# commencer dans le répertoire indiqué
|
||||
check_gitdir "$@" || die || return 1
|
||||
check_gitdir "$@" || die || return
|
||||
}
|
||||
|
||||
function load_branches() {
|
||||
@ -354,32 +208,23 @@ function load_branches() {
|
||||
HotfixBranch=
|
||||
MainBranch=
|
||||
DistBranch=
|
||||
IfRefBranch=
|
||||
IfBaseBranch=
|
||||
IfMergeSrc=
|
||||
IfMergeDest=
|
||||
for branch in "${LocalBranches[@]}"; do
|
||||
if [ "$branch" == "$UPSTREAM" ]; then
|
||||
UpstreamBranch="$branch"
|
||||
elif [ -n "$FEATURE" ] && [[ "$branch" == "$FEATURE"* ]]; then
|
||||
elif [[ "$branch" == "$FEATURE"* ]]; then
|
||||
FeatureBranches+=("$branch")
|
||||
elif [ -n "$DEVELOP" -a "$branch" == "$DEVELOP" ]; then
|
||||
elif [ "$branch" == "$DEVELOP" ]; then
|
||||
DevelopBranch="$branch"
|
||||
elif [ -n "$RELEASE" ] && [[ "$branch" == "$RELEASE"* ]]; then
|
||||
elif [[ "$branch" == "$RELEASE"* ]]; then
|
||||
ReleaseBranch="$branch"
|
||||
elif [ -n "$HOTFIX" ] && [[ "$branch" == "$HOTFIX"* ]]; then
|
||||
elif [[ "$branch" == "$HOTFIX"* ]]; then
|
||||
HotfixBranch="$branch"
|
||||
elif [ -n "$MAIN" -a "$branch" == "$MAIN" ]; then
|
||||
elif [ "$branch" == "$MAIN" ]; then
|
||||
MainBranch="$branch"
|
||||
elif [ -n "$DIST" -a "$branch" == "$DIST" ]; then
|
||||
elif [ "$branch" == "$DIST" ]; then
|
||||
DistBranch="$branch"
|
||||
fi
|
||||
[ -n "$PmanRefBranch" -a "$branch" == "$PmanRefBranch" ] && IfRefBranch="$branch"
|
||||
[ -n "$PmanBaseBranch" -a "$branch" == "$PmanBaseBranch" ] && IfBaseBranch="$branch"
|
||||
[ -n "$PmanMergeSrc" -a "$branch" == "$PmanMergeSrc" ] && IfMergeSrc="$branch"
|
||||
[ -n "$PmanMergeDest" -a "$branch" == "$PmanMergeDest" ] && IfMergeDest="$branch"
|
||||
done
|
||||
[ -n "$IfMergeSrc" -a "$IfMergeDest" ] && IfCanMerge=1 || IfCanMerge=
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@ -404,6 +249,12 @@ function load_config() {
|
||||
elif [ -f .pman.conf ]; then
|
||||
ConfigFile="$(pwd)/.pman.conf"
|
||||
source "$ConfigFile"
|
||||
elif [ -n "$1" -a -n "${MYNAME#$1}" ]; then
|
||||
# $1 est le nom de base de l'outil e.g "pdev", et le suffixe est la
|
||||
# configuration à charger par défaut. i.e pdev74 chargera par défaut la
|
||||
# configuration pman74.conf
|
||||
ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh"
|
||||
source "$ConfigFile"
|
||||
else
|
||||
ConfigFile="$NULIBDIR/bash/src/pman.conf.sh"
|
||||
fi
|
||||
|
10
bash/src/pman.tool.pdev.sh
Normal file
10
bash/src/pman.tool.pdev.sh
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
|
||||
PMAN_TOOLS=pdev
|
||||
SRC_TYPE=DEVELOP
|
||||
SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch"
|
||||
DEST_TYPE=MAIN
|
||||
DEST_BRANCH="${DEST_TYPE,,}"; DEST_BRANCH="${DEST_BRANCH^}Branch"
|
||||
ALLOW_MERGE=1
|
||||
MERGE_PREL=1
|
||||
ALLOW_DELETE=
|
10
bash/src/pman.tool.pdist.sh
Normal file
10
bash/src/pman.tool.pdist.sh
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
|
||||
PMAN_TOOL=pdist
|
||||
SRC_TYPE=DIST
|
||||
SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch"
|
||||
DEST_TYPE=
|
||||
DEST_BRANCH=
|
||||
ALLOW_MERGE=
|
||||
MERGE_PREL=
|
||||
ALLOW_DELETE=
|
10
bash/src/pman.tool.pmain.sh
Normal file
10
bash/src/pman.tool.pmain.sh
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
|
||||
PMAN_TOOL=pmain
|
||||
SRC_TYPE=MAIN
|
||||
SRC_BRANCH="${SRC_TYPE,,}"; SRC_BRANCH="${SRC_BRANCH^}Branch"
|
||||
DEST_TYPE=DIST
|
||||
DEST_BRANCH="${DEST_TYPE,,}"; DEST_BRANCH="${DEST_BRANCH^}Branch"
|
||||
ALLOW_MERGE=1
|
||||
MERGE_PREL=
|
||||
ALLOW_DELETE=
|
@ -1,299 +0,0 @@
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
|
||||
git_cleancheckout_DIRTY="\
|
||||
Vous avez des modifications locales.
|
||||
Enregistrez ces modifications avant de fusionner la branche"
|
||||
|
||||
function resolve_unique_branch() {
|
||||
if [ "$PMAN_REF_BRANCH" == FEATURE ]; then
|
||||
if [ $# -gt 0 ]; then
|
||||
UniqueBranch="$FEATURE${1#$FEATURE}"
|
||||
elif [[ "$CurrentBranch" == "$FEATURE"* ]]; then
|
||||
UniqueBranch="$CurrentBranch"
|
||||
elif [ ${#FeatureBranches[*]} -eq 0 ]; then
|
||||
die "Vous devez spécifier la branche de feature"
|
||||
elif [ ${#FeatureBranches[*]} -eq 1 ]; then
|
||||
UniqueBranch="${FeatureBranches[0]}"
|
||||
else
|
||||
simple_menu \
|
||||
UniqueBranch FeatureBranches \
|
||||
-t "Branches de feature" \
|
||||
-m "Veuillez choisir la branche de feature" \
|
||||
-d "${FeatureBranches[0]}"
|
||||
fi
|
||||
PMAN_REF_BRANCH=UniqueBranch
|
||||
PMAN_MERGE_SRC=UniqueBranch
|
||||
else
|
||||
die "resolve_unique_branch: $PMAN_REF_BRANCH: non implémenté"
|
||||
fi
|
||||
}
|
||||
|
||||
function dump_action() {
|
||||
enote "Valeurs des variables:
|
||||
PMAN_TOOL=$PMAN_TOOL
|
||||
PMAN_REF_BRANCH=$PMAN_REF_BRANCH${PmanRefBranch:+ PmanRefBranch=$PmanRefBranch IfRefBranch=$IfRefBranch}
|
||||
PMAN_BASE_BRANCH=$PMAN_BASE_BRANCH${PmanBaseBranch:+ PmanBaseBranch=$PmanBaseBranch IfCreateBase=$IfCreateBase}
|
||||
PMAN_MERGE_FROM=$PMAN_MERGE_FROM
|
||||
PMAN_MERGE_TO=$PMAN_MERGE_TO
|
||||
PMAN_DIR=$PMAN_DIR
|
||||
PMAN_PREL_MERGE=$PMAN_PREL_MERGE
|
||||
PMAN_DELETE_MERGED=$PMAN_DELETE_MERGED
|
||||
PMAN_MERGE_SRC=$PMAN_MERGE_SRC${PmanMergeSrc:+ PmanMergeSrc=$PmanMergeSrc IfMergeSrc=$IfMergeSrc}
|
||||
PMAN_MERGE_DEST=$PMAN_MERGE_DEST${PmanMergeDest:+ PmanMergeDest=$PmanMergeDest IfMergeDest=$IfMergeDest}
|
||||
PMAN_CAN_MERGE=$PMAN_CAN_MERGE IfCanMerge=$IfCanMerge
|
||||
|
||||
CurrentBranch=$CurrentBranch
|
||||
LocalBranches=${LocalBranches[*]}
|
||||
RemoteBranches=${RemoteBranches[*]}
|
||||
AllBranches=${AllBranches[*]}
|
||||
|
||||
UpstreamBranch=$UpstreamBranch
|
||||
FeatureBranches=${FeatureBranches[*]}
|
||||
DevelopBranch=$DevelopBranch
|
||||
ReleaseBranch=$ReleaseBranch
|
||||
HotfixBranch=$HotfixBranch
|
||||
MainBranch=$MainBranch
|
||||
DistBranch=$DistBranch
|
||||
"
|
||||
}
|
||||
|
||||
function _ensure_branch() {
|
||||
[ -n "$PmanRefBranch" ] || die "\
|
||||
La branche $PMAN_REF_BRANCH n'a pas été définie.
|
||||
Veuillez éditer le fichier .pman.conf"
|
||||
[ "$1" == init -o -n "$IfRefBranch" ] || die "$PmanRefBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function _ensure_base_branch() {
|
||||
[ -n "${!PMAN_BASE_BRANCH}" ] || die "\
|
||||
La branche $PMAN_BASE_BRANCH n'a pas été définie.
|
||||
Veuillez éditer le fichier .pman.conf"
|
||||
[ "$1" == init -o -n "$IfCreateBase" ] || die "${!PMAN_BASE_BRANCH}: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function _ensure_merge_src() {
|
||||
[ -n "$PmanMergeSrc" ] || die "\
|
||||
La branche $PMAN_MERGE_SRC n'a pas été définie.
|
||||
Veuillez éditer le fichier .pman.conf"
|
||||
[ "$1" == init -o -n "$IfMergeSrc" ] || die "$PmanMergeSrc: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function _ensure_merge_dest() {
|
||||
[ -n "$PmanMergeDest" ] || die "\
|
||||
La branche $PMAN_MERGE_DEST n'a pas été définie.
|
||||
Veuillez éditer le fichier .pman.conf"
|
||||
[ "$1" == init -o -n "$IfMergeDest" ] || die "$PmanMergeDest: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function checkout_action() {
|
||||
local -a push_branches
|
||||
|
||||
[ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" || die
|
||||
_ensure_branch init
|
||||
|
||||
if [ -n "$IfRefBranch" ]; then
|
||||
git checkout "$IfRefBranch"
|
||||
elif array_contains LocalBranches "$PmanRefBranch"; then
|
||||
git checkout "$PmanRefBranch"
|
||||
elif array_contains AllBranches "$PmanRefBranch"; then
|
||||
enote "$PmanRefBranch: une branche du même nom existe dans l'origine"
|
||||
ask_yesno "Voulez-vous basculer sur cette branche?" O || die
|
||||
git checkout "$PmanRefBranch"
|
||||
elif [ -n "$PMAN_BASE_BRANCH" ]; then
|
||||
_ensure_base_branch
|
||||
|
||||
resolve_should_push
|
||||
|
||||
local SrcBranch="${!PMAN_BASE_BRANCH}" DestBranch="$PmanRefBranch"
|
||||
enote "Vous allez créer la branche ${COULEUR_BLEUE}$DestBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$SrcBranch${COULEUR_NORMALE}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
einfo "Création de la branche $DestBranch"
|
||||
git checkout -b "$DestBranch" "$SrcBranch" || die
|
||||
push_branches+=("$DestBranch")
|
||||
|
||||
_push_branches
|
||||
fi
|
||||
}
|
||||
|
||||
#XXXX
|
||||
#[ -n "$PmanMergeSrc" ] ||
|
||||
# die "Aucune branche définie pour $PMAN_MERGE_SRC. Veuillez éditer le fichier .pman.conf"
|
||||
function ensure_merge_branches() {
|
||||
[ -n "$PMAN_CAN_MERGE" ] ||
|
||||
die "$PmanRefBranch: Aucune configuration de fusion trouvée pour cette branche"
|
||||
|
||||
local branches
|
||||
[ "$1" == -a ] && branches=AllBranches || branches=LocalBranches
|
||||
|
||||
SrcBranch="$PmanMergeSrc"
|
||||
array_contains "$branches" "$SrcBranch" || die "$SrcBranch: branche source introuvable"
|
||||
DestBranch="$PmanMergeDest"
|
||||
array_contains "$branches" "$DestBranch" || die "$DestBranch: branche destination introuvable"
|
||||
}
|
||||
|
||||
function _show_action() {
|
||||
local commits
|
||||
setx commits=_list_commits
|
||||
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"
|
||||
eecho "$commits"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function show_action() {
|
||||
git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY"
|
||||
ensure_merge_branches
|
||||
_show_action "$@"
|
||||
}
|
||||
|
||||
function _merge_action() {
|
||||
enote "\
|
||||
Ce script va
|
||||
- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+
|
||||
- pousser les branches modifiées}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
local script=".git/pman-merge.sh"
|
||||
local -a push_branches delete_branches
|
||||
local hook
|
||||
local comment=
|
||||
local or_die=" || exit 1"
|
||||
|
||||
_mscript_start
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# merge
|
||||
if [ -n "\$merge" ]; then
|
||||
esection "Fusionner la branche"
|
||||
EOF
|
||||
hook="BEFORE_MERGE_$PMAN_MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_mscript_merge_branch
|
||||
hook="AFTER_MERGE_$PMAN_MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
if [ -n "$ShouldDelete" ]; then
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# delete
|
||||
if [ -n "\$delete" ]; then
|
||||
esection "Supprimer la branche"
|
||||
EOF
|
||||
_mscript_delete_branch
|
||||
hook="AFTER_DELETE_$PMAN_MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
fi
|
||||
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# push
|
||||
if [ -n "\$push" ]; then
|
||||
esection "Pousser les branches"
|
||||
EOF
|
||||
hook="BEFORE_PUSH_$PMAN_MERGE_DEST"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_script_push_branches
|
||||
if [ ${#delete_branches[*]} -gt 0 ]; then
|
||||
_scripta <<<"if [ -n \"\$delete\" ]; then"
|
||||
push_branches=("${delete_branches[@]}")
|
||||
_script_push_branches
|
||||
_scripta <<<fi
|
||||
fi
|
||||
hook="AFTER_PUSH_$PMAN_MERGE_DEST"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
[ -n "$Delete" -o -z "$ShouldDelete" ] && Deleted=1 || Deleted=
|
||||
[ -n "$ShouldDelete" -a -n "$Delete" ] && ShouldDelete=
|
||||
[ -n "$ShouldPush" -a -n "$Push" ] && ShouldPush=
|
||||
if [ -n "$_Fake" ]; then
|
||||
cat "$script"
|
||||
elif ! "$script" merge ${Delete:+delete} ${Push:+push}; then
|
||||
eimportant "\
|
||||
Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
||||
En cas d'erreur de merge, veuillez corriger les erreurs puis continuer avec
|
||||
git merge --continue
|
||||
Sinon, veuillez consulter le script et/ou le relancer
|
||||
./$script${Delete:+ delete}${Push:+ push}"
|
||||
die
|
||||
elif [ -n "$Deleted" -a -n "$Push" ]; then
|
||||
[ -n "$_KeepScript" ] || rm "$script"
|
||||
[ -n "$AfterMerge" ] && eval "$AfterMerge"
|
||||
else
|
||||
local msg="\
|
||||
Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
||||
Vous pouvez consulter le script et/ou le relancer
|
||||
./$script${ShouldDelete:+ delete}${ShouldPush:+ push}"
|
||||
[ -n "$AfterMerge" ] && msg="$msg
|
||||
Il y a aussi les commandes supplémentaires suivantes:
|
||||
${AfterMerge//
|
||||
/
|
||||
}"
|
||||
einfo "$msg"
|
||||
fi
|
||||
}
|
||||
|
||||
function merge_action() {
|
||||
[ -n "$PMAN_UNIQUE" ] || resolve_unique_branch "$@" || die
|
||||
ensure_merge_branches -a
|
||||
|
||||
if [ -n "$PMAN_PREL_MERGE" ]; then
|
||||
[ -n "$ForceMerge" ] || die "$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel"
|
||||
fi
|
||||
[ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$SrcBranch"
|
||||
[ -n "$PMAN_DELETE_MERGED" ] || Delete=
|
||||
[ -z "$_Fake" ] && git_ensure_cleancheckout
|
||||
|
||||
if ! array_contains LocalBranches "$SrcBranch" && array_contains AllBranches "$SrcBranch"; then
|
||||
enote "$SrcBranch: une branche du même nom existe dans l'origine"
|
||||
fi
|
||||
if ! array_contains LocalBranches "$DestBranch" && array_contains AllBranches "$DestBranch"; then
|
||||
enote "$DestBranch: une branche du même nom existe dans l'origine"
|
||||
fi
|
||||
array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche locale introuvable"
|
||||
array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche locale introuvable"
|
||||
|
||||
resolve_should_push
|
||||
set -x #XXX
|
||||
_merge_action "$@"
|
||||
}
|
||||
|
||||
function rebase_action() {
|
||||
die "non implémenté"
|
||||
}
|
||||
|
@ -2,215 +2,727 @@
|
||||
namespace nulib\db;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\exceptions;
|
||||
use nulib\cv;
|
||||
use nulib\db\_private\_migration;
|
||||
use nulib\php\func;
|
||||
use nulib\ValueException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class Capacitor: un objet permettant d'attaquer un canal spécifique d'une
|
||||
* instance de {@link CapacitorStorage}
|
||||
* Class Capacitor: objet permettant d'accumuler des données pour les
|
||||
* réutiliser plus tard
|
||||
*/
|
||||
class Capacitor implements ITransactor {
|
||||
function __construct(CapacitorStorage $storage, CapacitorChannel $channel, bool $ensureExists=true) {
|
||||
$this->storage = $storage;
|
||||
$this->channel = $channel;
|
||||
$this->channel->setCapacitor($this);
|
||||
if ($ensureExists) $this->ensureExists();
|
||||
}
|
||||
|
||||
/** @var CapacitorStorage */
|
||||
protected $storage;
|
||||
|
||||
function getStorage(): CapacitorStorage {
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
function db(): IDatabase {
|
||||
return $this->getStorage()->db();
|
||||
}
|
||||
abstract class Capacitor {
|
||||
abstract function db(): IDatabase;
|
||||
|
||||
function ensureLive(): self {
|
||||
$this->getStorage()->ensureLive();
|
||||
$this->db()->ensureLive();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @var CapacitorChannel */
|
||||
protected $channel;
|
||||
|
||||
function getChannel(): CapacitorChannel {
|
||||
return $this->channel;
|
||||
}
|
||||
|
||||
function getTableName(): string {
|
||||
return $this->getChannel()->getTableName();
|
||||
}
|
||||
|
||||
function getCreateSql(): string {
|
||||
$channel = $this->channel;
|
||||
return $this->storage->_getMigration($channel)->getSql(get_class($channel), $this->db());
|
||||
}
|
||||
|
||||
/** @var CapacitorChannel[] */
|
||||
protected ?array $subChannels = null;
|
||||
|
||||
protected ?array $subManageTransactions = null;
|
||||
|
||||
function willUpdate(...$channels): self {
|
||||
if ($this->subChannels === null) {
|
||||
# désactiver la gestion des transaction sur le channel local aussi
|
||||
$this->subChannels[] = $this->channel;
|
||||
function newChannel($channel): CapacitorChannel {
|
||||
if (!($channel instanceof CapacitorChannel)) {
|
||||
if (!is_array($channel)) $channel = ["name" => $channel];
|
||||
$channel = new CapacitorChannel($channel);
|
||||
}
|
||||
if ($channels) {
|
||||
foreach ($channels as $channel) {
|
||||
if ($channel instanceof Capacitor) $channel = $channel->getChannel();
|
||||
if ($channel instanceof CapacitorChannel) {
|
||||
$this->subChannels[] = $channel;
|
||||
} else {
|
||||
throw exceptions::invalid_type($channel, "channel", CapacitorChannel::class);
|
||||
}
|
||||
}
|
||||
return $channel->initCapacitor($this);
|
||||
}
|
||||
|
||||
const CDATA_DEFINITION = null;
|
||||
const CSUM_DEFINITION = null;
|
||||
const CTIMESTAMP_DEFINITION = null;
|
||||
const GSERIAL_DEFINITION = null;
|
||||
const GLIC_DEFINITION = null;
|
||||
const GLIB_DEFINITION = null;
|
||||
const GTEXT_DEFINITION = null;
|
||||
const GBOOL_DEFINITION = null;
|
||||
const GUUID_DEFINITION = null;
|
||||
|
||||
protected static function verifix_col($def): string {
|
||||
if (!is_string($def)) $def = strval($def);
|
||||
$def = trim($def);
|
||||
$parts = preg_split('/\s+/', $def, 2);
|
||||
if (count($parts) == 2) {
|
||||
$def = $parts[0];
|
||||
$rest = " $parts[1]";
|
||||
} else {
|
||||
$rest = null;
|
||||
}
|
||||
return $this;
|
||||
switch ($def) {
|
||||
case "serdata":
|
||||
case "Cdata": $def = static::CDATA_DEFINITION; break;
|
||||
case "sersum":
|
||||
case "Csum": $def = static::CSUM_DEFINITION; break;
|
||||
case "serts":
|
||||
case "Ctimestamp": $def = static::CTIMESTAMP_DEFINITION; break;
|
||||
case "genserial":
|
||||
case "Gserial": $def = static::GSERIAL_DEFINITION; break;
|
||||
case "genlic":
|
||||
case "Glic": $def = static::GLIC_DEFINITION; break;
|
||||
case "genlib":
|
||||
case "Glib": $def = static::GLIB_DEFINITION; break;
|
||||
case "gentext":
|
||||
case "Gtext": $def = static::GTEXT_DEFINITION; break;
|
||||
case "genbool":
|
||||
case "Gbool": $def = static::GBOOL_DEFINITION; break;
|
||||
case "genuuid":
|
||||
case "Guuid": $def = static::GUUID_DEFINITION; break;
|
||||
}
|
||||
return "$def$rest";
|
||||
}
|
||||
|
||||
function inTransaction(): bool {
|
||||
return $this->db()->inTransaction();
|
||||
}
|
||||
const PRIMARY_KEY_DEFINITION = [
|
||||
"id_" => "Gserial",
|
||||
];
|
||||
|
||||
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
||||
$db = $this->db();
|
||||
if ($this->subChannels !== null) {
|
||||
# on gère des subchannels: ne débuter la transaction que si ce n'est déjà fait
|
||||
if ($this->subManageTransactions === null) {
|
||||
foreach ($this->subChannels as $channel) {
|
||||
$name = $channel->getName();
|
||||
$this->subManageTransactions ??= [];
|
||||
if (!array_key_exists($name, $this->subManageTransactions)) {
|
||||
$this->subManageTransactions[$name] = $channel->isManageTransactions();
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"item__" => "Cdata",
|
||||
"item__sum_" => "Csum",
|
||||
"created_" => "Ctimestamp",
|
||||
"modified_" => "Ctimestamp",
|
||||
];
|
||||
|
||||
protected function getColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
|
||||
$definitions = [];
|
||||
if ($channel->getPrimaryKeys() === null) {
|
||||
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
||||
}
|
||||
$definitions[] = $channel->getColumnDefinitions();
|
||||
$definitions[] = static::COLUMN_DEFINITIONS;
|
||||
# forcer les définitions sans clé à la fin (sqlite requière par exemple que
|
||||
# primary key (columns) soit à la fin)
|
||||
$tmp = cl::merge(...$definitions);
|
||||
$definitions = [];
|
||||
$constraints = [];
|
||||
$index = 0;
|
||||
foreach ($tmp as $col => $def) {
|
||||
if ($col === $index) {
|
||||
$index++;
|
||||
if (is_array($def)) {
|
||||
if (!$ignoreMigrations) {
|
||||
$mdefs = $def;
|
||||
$mindex = 0;
|
||||
foreach ($mdefs as $mcol => $mdef) {
|
||||
if ($mcol === $mindex) {
|
||||
$mindex++;
|
||||
} else {
|
||||
if ($mdef) {
|
||||
$definitions[$mcol] = self::verifix_col($mdef);
|
||||
} else {
|
||||
unset($definitions[$mcol]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$channel->setManageTransactions(false);
|
||||
} else {
|
||||
$constraints[] = $def;
|
||||
}
|
||||
if (!$db->inTransaction()) $db->beginTransaction();
|
||||
} else {
|
||||
$definitions[$col] = self::verifix_col($def);
|
||||
}
|
||||
} elseif (!$db->inTransaction()) {
|
||||
}
|
||||
return cl::merge($definitions, $constraints);
|
||||
}
|
||||
|
||||
/** sérialiser les valeurs qui doivent l'être dans $row */
|
||||
protected function serialize(CapacitorChannel $channel, ?array $row): ?array {
|
||||
if ($row === null) return null;
|
||||
$colDefs = $this->getColumnDefinitions($channel);
|
||||
$index = 0;
|
||||
$raw = [];
|
||||
foreach (array_keys($colDefs) as $col) {
|
||||
$key = $col;
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
} elseif ($channel->isSerialCol($key)) {
|
||||
[$serialCol, $sumCol] = $channel->getSumCols($key);
|
||||
if (array_key_exists($key, $row)) {
|
||||
$sum = $channel->getSum($key, $row[$key]);
|
||||
$raw[$serialCol] = $sum[$serialCol];
|
||||
if (array_key_exists($sumCol, $colDefs)) {
|
||||
$raw[$sumCol] = $sum[$sumCol];
|
||||
}
|
||||
}
|
||||
} elseif (array_key_exists($key, $row)) {
|
||||
$raw[$col] = $row[$key];
|
||||
}
|
||||
}
|
||||
return $raw;
|
||||
}
|
||||
|
||||
/** désérialiser les valeurs qui doivent l'être dans $values */
|
||||
protected function unserialize(CapacitorChannel $channel, ?array $raw): ?array {
|
||||
if ($raw === null) return null;
|
||||
$colDefs = $this->getColumnDefinitions($channel);
|
||||
$index = 0;
|
||||
$row = [];
|
||||
foreach (array_keys($colDefs) as $col) {
|
||||
$key = $col;
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
} elseif (!array_key_exists($col, $raw)) {
|
||||
} elseif ($channel->isSerialCol($key)) {
|
||||
$value = $raw[$col];
|
||||
if ($value !== null) $value = $channel->unserialize($value);
|
||||
$row[$key] = $value;
|
||||
} else {
|
||||
$row[$key] = $raw[$col];
|
||||
}
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
function getPrimaryKeys(CapacitorChannel $channel): array {
|
||||
$primaryKeys = $channel->getPrimaryKeys();
|
||||
if ($primaryKeys === null) $primaryKeys = ["id_"];
|
||||
return $primaryKeys;
|
||||
}
|
||||
|
||||
function getRowIds(CapacitorChannel $channel, ?array $row, ?array &$primaryKeys=null): ?array {
|
||||
$primaryKeys = $this->getPrimaryKeys($channel);
|
||||
$rowIds = cl::select($row, $primaryKeys);
|
||||
if (cl::all_n($rowIds)) return null;
|
||||
else return $rowIds;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Migration et metadata
|
||||
|
||||
abstract protected function tableExists(string $tableName): bool;
|
||||
|
||||
const METADATA_TABLE = "_metadata";
|
||||
const METADATA_COLS = [
|
||||
"name" => "varchar not null primary key",
|
||||
"value" => "varchar",
|
||||
];
|
||||
|
||||
protected function prepareMetadata(): void {
|
||||
if (!$this->tableExists(static::METADATA_TABLE)) {
|
||||
$db = $this->db();
|
||||
$db->exec([
|
||||
"create table",
|
||||
"table" => static::METADATA_TABLE,
|
||||
"cols" => static::METADATA_COLS,
|
||||
]);
|
||||
$db->exec([
|
||||
"insert",
|
||||
"into" => static::METADATA_TABLE,
|
||||
"values" => [
|
||||
"name" => "version",
|
||||
"value" => "1",
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCreateChannelSql(CapacitorChannel $channel): array {
|
||||
return [
|
||||
"create table if not exists",
|
||||
"table" => $channel->getTableName(),
|
||||
"cols" => $this->getColumnDefinitions($channel, true),
|
||||
];
|
||||
}
|
||||
|
||||
abstract function getMigration(CapacitorChannel $channel): _migration;
|
||||
|
||||
#############################################################################
|
||||
# Catalogue
|
||||
|
||||
const CATALOG_TABLE = "_channels";
|
||||
const CATALOG_COLS = [
|
||||
"name" => "varchar not null primary key",
|
||||
"table_name" => "varchar",
|
||||
"class_name" => "varchar",
|
||||
];
|
||||
|
||||
protected function getCreateCatalogSql(): array {
|
||||
return [
|
||||
"create table if not exists",
|
||||
"table" => static::CATALOG_TABLE,
|
||||
"cols" => static::CATALOG_COLS,
|
||||
];
|
||||
}
|
||||
|
||||
protected function addToCatalogSql(CapacitorChannel $channel): array {
|
||||
return [
|
||||
"insert",
|
||||
"into" => static::CATALOG_TABLE,
|
||||
"values" => [
|
||||
"name" => $channel->getName(),
|
||||
"table_name" => $channel->getTableName(),
|
||||
"class_name" => get_class($channel),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function getCatalog(): iterable {
|
||||
return $this->db()->all([
|
||||
"select",
|
||||
"from" => static::CATALOG_TABLE,
|
||||
]);
|
||||
}
|
||||
|
||||
function isInCatalog(array $filter, ?array &$raw=null): bool {
|
||||
$raw = $this->db()->one([
|
||||
"select",
|
||||
"from" => static::CATALOG_TABLE,
|
||||
"where" => $filter,
|
||||
]);
|
||||
return $raw !== null;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
protected function afterCreate(CapacitorChannel $channel): void {
|
||||
$db = $this->db();
|
||||
$db->exec($this->getCreateCatalogSql());
|
||||
$db->exec($this->addToCatalogSql($channel));
|
||||
}
|
||||
|
||||
function create(CapacitorChannel $channel): void {
|
||||
$this->prepareMetadata();
|
||||
$this->getMigration($channel)->migrate($this->db());
|
||||
$this->afterCreate($channel);
|
||||
}
|
||||
|
||||
function autocreate(CapacitorChannel $channel, bool $force=false): void {
|
||||
if ($force || !$channel->isCreated()) {
|
||||
$channel->ensureSetup();
|
||||
$this->create($channel);
|
||||
$channel->setCreated();
|
||||
}
|
||||
}
|
||||
|
||||
/** tester si le canal spécifié existe */
|
||||
function exists(CapacitorChannel $channel): bool {
|
||||
return $this->tableExists($channel->getTableName());
|
||||
}
|
||||
|
||||
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::CATALOG_TABLE,
|
||||
"where" => [
|
||||
"name" => $name,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/** supprimer le canal spécifié */
|
||||
function reset(CapacitorChannel $channel, bool $recreate=false): void {
|
||||
$this->beforeReset($channel);
|
||||
$this->db()->exec([
|
||||
"drop table if exists",
|
||||
$channel->getTableName(),
|
||||
]);
|
||||
$channel->setCreated(false);
|
||||
if ($recreate) $this->autocreate($channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* charger une valeur dans le canal
|
||||
*
|
||||
* Après avoir calculé les valeurs des clés supplémentaires
|
||||
* avec {@link CapacitorChannel::getItemValues()}, l'une des deux fonctions
|
||||
* {@link CapacitorChannel::onCreate()} ou {@link CapacitorChannel::onUpdate()}
|
||||
* est appelée en fonction du type d'opération: création ou mise à jour
|
||||
*
|
||||
* Ensuite, si $func !== null, la fonction est appelée avec la signature de
|
||||
* {@link CapacitorChannel::onCreate()} ou {@link CapacitorChannel::onUpdate()}
|
||||
* en fonction du type d'opération: création ou mise à jour
|
||||
*
|
||||
* Dans les deux cas, si la fonction retourne un tableau, il est utilisé pour
|
||||
* modifier les valeurs insérées/mises à jour. De plus, $row obtient la
|
||||
* valeur finale des données insérées/mises à jour
|
||||
*
|
||||
* Si $args est renseigné, il est ajouté aux arguments utilisés pour appeler
|
||||
* les méthodes {@link CapacitorChannel::getItemValues()},
|
||||
* {@link CapacitorChannel::onCreate()} et/ou
|
||||
* {@link CapacitorChannel::onUpdate()}
|
||||
*
|
||||
* @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait
|
||||
* déjà à l'identique dans le canal
|
||||
*/
|
||||
function charge(CapacitorChannel $channel, $item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||
$channel->initCapacitor($this);
|
||||
|
||||
$tableName = $channel->getTableName();
|
||||
$db = $this->db();
|
||||
$args ??= [];
|
||||
|
||||
$row = func::call([$channel, "getItemValues"], $item, ...$args);
|
||||
if ($row === [false]) return 0;
|
||||
|
||||
if ($row !== null && array_key_exists("item", $row)) {
|
||||
$item = A::pop($row, "item");
|
||||
}
|
||||
|
||||
$raw = cl::merge(
|
||||
$channel->getSum("item", $item),
|
||||
$this->serialize($channel, $row));
|
||||
$praw = null;
|
||||
$rowIds = $this->getRowIds($channel, $raw, $primaryKeys);
|
||||
if ($rowIds !== null) {
|
||||
# modification
|
||||
$praw = $db->one([
|
||||
"select",
|
||||
"from" => $tableName,
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
}
|
||||
|
||||
$now = date("Y-m-d H:i:s");
|
||||
$insert = null;
|
||||
if ($praw === null) {
|
||||
# création
|
||||
$raw = cl::merge($raw, [
|
||||
"created_" => $now,
|
||||
"modified_" => $now,
|
||||
]);
|
||||
$insert = true;
|
||||
$initFunc = func::with([$channel, "onCreate"], $args);
|
||||
$row = $this->unserialize($channel, $raw);
|
||||
$prow = null;
|
||||
} else {
|
||||
# modification
|
||||
# intégrer autant que possible les valeurs de praw dans raw, de façon que
|
||||
# l'utilisateur puisse voir clairement ce qui a été modifié
|
||||
if ($channel->_wasSumModified("item", $raw, $praw)) {
|
||||
$insert = false;
|
||||
$raw = cl::merge($praw, $raw, [
|
||||
"modified_" => $now,
|
||||
]);
|
||||
} else {
|
||||
$raw = cl::merge($praw, $raw);
|
||||
}
|
||||
$initFunc = func::with([$channel, "onUpdate"], $args);
|
||||
$row = $this->unserialize($channel, $raw);
|
||||
$prow = $this->unserialize($channel, $praw);
|
||||
}
|
||||
|
||||
$updates = $initFunc->prependArgs([$item, $row, $prow])->invoke();
|
||||
if ($updates === [false]) return 0;
|
||||
if (is_array($updates) && $updates) {
|
||||
if ($insert === null) $insert = false;
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = $now;
|
||||
}
|
||||
$row = cl::merge($row, $updates);
|
||||
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
||||
}
|
||||
|
||||
if ($func !== null) {
|
||||
$updates = func::with($func, $args)
|
||||
->prependArgs([$item, $row, $prow])
|
||||
->bind($channel)
|
||||
->invoke();
|
||||
if ($updates === [false]) return 0;
|
||||
if (is_array($updates) && $updates) {
|
||||
if ($insert === null) $insert = false;
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = $now;
|
||||
}
|
||||
$row = cl::merge($row, $updates);
|
||||
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
||||
}
|
||||
}
|
||||
|
||||
# aucune modification
|
||||
if ($insert === null) return 0;
|
||||
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
if ($manageTransactions) {
|
||||
$commited = false;
|
||||
$db->beginTransaction();
|
||||
}
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
$nbModified = 0;
|
||||
try {
|
||||
if ($insert) {
|
||||
$id = $db->exec([
|
||||
"insert",
|
||||
"into" => $tableName,
|
||||
"values" => $raw,
|
||||
]);
|
||||
if (count($primaryKeys) == 1 && $rowIds === null) {
|
||||
# mettre à jour avec l'id généré
|
||||
$row[$primaryKeys[0]] = $id;
|
||||
}
|
||||
$nbModified = 1;
|
||||
} else {
|
||||
# calculer ce qui a changé pour ne mettre à jour que le nécessaire
|
||||
$updates = [];
|
||||
foreach ($raw as $col => $value) {
|
||||
if (array_key_exists($col, $rowIds)) {
|
||||
# ne jamais mettre à jour la clé primaire
|
||||
continue;
|
||||
}
|
||||
if (!cv::equals($value, $praw[$col] ?? null)) {
|
||||
$updates[$col] = $value;
|
||||
}
|
||||
}
|
||||
if (count($updates) == 1 && array_key_first($updates) == "modified_") {
|
||||
# si l'unique modification porte sur la date de modification, alors
|
||||
# la ligne n'est pas modifiée. ce cas se présente quand on altère la
|
||||
# valeur de $item
|
||||
$updates = null;
|
||||
}
|
||||
if ($updates) {
|
||||
$db->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $updates,
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
$nbModified = 1;
|
||||
}
|
||||
} finally {
|
||||
if ($commit && !$commited) $this->rollback();
|
||||
}
|
||||
if ($manageTransactions) {
|
||||
$db->commit();
|
||||
$commited = true;
|
||||
}
|
||||
return $nbModified;
|
||||
} finally {
|
||||
if ($manageTransactions && !$commited) $db->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
protected function beforeEndTransaction(): void {
|
||||
if ($this->subManageTransactions !== null) {
|
||||
foreach ($this->subChannels as $channel) {
|
||||
$name = $channel->getName();
|
||||
$channel->setManageTransactions($this->subManageTransactions[$name]);
|
||||
/**
|
||||
* décharger les données du canal spécifié. seul la valeur de $item est
|
||||
* fournie
|
||||
*/
|
||||
function discharge(CapacitorChannel $channel, bool $reset=true): Traversable {
|
||||
$channel->initCapacitor($this);
|
||||
|
||||
$raws = $this->db()->all([
|
||||
"select item__",
|
||||
"from" => $channel->getTableName(),
|
||||
]);
|
||||
foreach ($raws as $raw) {
|
||||
yield unserialize($raw['item__']);
|
||||
}
|
||||
if ($reset) $this->reset($channel);
|
||||
}
|
||||
|
||||
protected function convertValue2row(CapacitorChannel $channel, array $filter, array $cols): array {
|
||||
$index = 0;
|
||||
$fixed = [];
|
||||
foreach ($filter as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
if (is_array($value)) {
|
||||
$value = $this->convertValue2row($channel, $value, $cols);
|
||||
}
|
||||
$fixed[] = $value;
|
||||
} else {
|
||||
$col = "${key}__";
|
||||
if (array_key_exists($col, $cols)) {
|
||||
# colonne sérialisée
|
||||
$fixed[$col] = $channel->serialize($value);
|
||||
} else {
|
||||
$fixed[$key] = $value;
|
||||
}
|
||||
}
|
||||
$this->subManageTransactions = null;
|
||||
}
|
||||
return $fixed;
|
||||
}
|
||||
|
||||
protected function verifixFilter(CapacitorChannel $channel, &$filter): void {
|
||||
if ($filter !== null && !is_array($filter)) {
|
||||
$primaryKeys = $this->getPrimaryKeys($channel);
|
||||
$id = $filter;
|
||||
$channel->verifixId($id);
|
||||
$filter = [$primaryKeys[0] => $id];
|
||||
}
|
||||
$cols = $this->getColumnDefinitions($channel);
|
||||
if ($filter !== null) {
|
||||
$filter = $this->convertValue2row($channel, $filter, $cols);
|
||||
}
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
$this->beforeEndTransaction();
|
||||
/** indiquer le nombre d'éléments du canal spécifié */
|
||||
function count(CapacitorChannel $channel, $filter): int {
|
||||
$channel->initCapacitor($this);
|
||||
$this->verifixFilter($channel, $filter);
|
||||
|
||||
return $this->db()->get([
|
||||
"select count(*)",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* obtenir la ligne correspondant au filtre sur le canal spécifié
|
||||
*
|
||||
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
||||
*/
|
||||
function one(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): ?array {
|
||||
$channel->initCapacitor($this);
|
||||
$this->verifixFilter($channel, $filter);
|
||||
|
||||
$raw = $this->db()->one(cl::merge([
|
||||
"select",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
], $mergeQuery));
|
||||
return $this->unserialize($channel, $raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* obtenir les lignes correspondant au filtre sur le canal spécifié
|
||||
*
|
||||
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
||||
*/
|
||||
function all(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable {
|
||||
$channel->initCapacitor($this);
|
||||
$this->verifixFilter($channel, $filter);
|
||||
|
||||
$raws = $this->db()->all(cl::merge([
|
||||
"select",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
], $mergeQuery), null, $this->getPrimaryKeys($channel));
|
||||
foreach ($raws as $key => $raw) {
|
||||
yield $key => $this->unserialize($channel, $raw);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* appeler une fonction pour chaque élément du canal spécifié.
|
||||
*
|
||||
* $filter permet de filtrer parmi les élements chargés
|
||||
*
|
||||
* $func est appelé avec la signature de {@link CapacitorChannel::onEach()}
|
||||
* si la fonction retourne un tableau, il est utilisé pour mettre à jour la
|
||||
* ligne
|
||||
*
|
||||
* @param int $nbUpdated reçoit le nombre de lignes mises à jour
|
||||
* @return int le nombre de lignes parcourues
|
||||
*/
|
||||
function each(CapacitorChannel $channel, $filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||
$channel->initCapacitor($this);
|
||||
|
||||
if ($func === null) $func = CapacitorChannel::onEach;
|
||||
$onEach = func::with($func)->bind($channel);
|
||||
$db = $this->db();
|
||||
if ($db->inTransaction()) $db->commit();
|
||||
}
|
||||
|
||||
function rollback(): void {
|
||||
$this->beforeEndTransaction();
|
||||
$db = $this->db();
|
||||
if ($db->inTransaction()) $db->rollback();
|
||||
}
|
||||
|
||||
function exists(): bool {
|
||||
return $this->storage->_exists($this->channel);
|
||||
}
|
||||
|
||||
function ensureExists(): void {
|
||||
$this->storage->_ensureExists($this->channel);
|
||||
}
|
||||
|
||||
function reset(bool $recreate=false): void {
|
||||
$this->storage->_reset($this->channel, $recreate);
|
||||
}
|
||||
|
||||
function charge($item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||
if ($this->subChannels !== null) $this->beginTransaction();
|
||||
return $this->storage->_charge($this->channel, $item, $func, $args, $row);
|
||||
}
|
||||
|
||||
function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
if ($manageTransactions) {
|
||||
$commited = false;
|
||||
$db->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
$count = 0;
|
||||
if ($items !== null) {
|
||||
if ($func !== null) {
|
||||
$func = func::with($func, $args)->bind($this->channel);
|
||||
$nbUpdated = 0;
|
||||
$tableName = $channel->getTableName();
|
||||
try {
|
||||
$args ??= [];
|
||||
$rows = $this->all($channel, $filter, $mergeQuery);
|
||||
foreach ($rows as $row) {
|
||||
$rowIds = $this->getRowIds($channel, $row);
|
||||
$updates = $onEach->invoke([$row, ...$args]);
|
||||
if ($updates === [false]) {
|
||||
break;
|
||||
} elseif ($updates !== null) {
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||
}
|
||||
$nbUpdated += $db->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $this->serialize($channel, $updates),
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
if ($manageTransactions && $commitThreshold !== null) {
|
||||
$commitThreshold--;
|
||||
if ($commitThreshold <= 0) {
|
||||
$db->commit();
|
||||
$db->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
}
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
$count += $this->charge($item, $func);
|
||||
if ($manageTransactions) {
|
||||
$db->commit();
|
||||
$commited = true;
|
||||
}
|
||||
return $count;
|
||||
} finally {
|
||||
if ($manageTransactions && !$commited) $db->rollback();
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
function discharge(bool $reset=true): Traversable {
|
||||
return $this->storage->_discharge($this->channel, $reset);
|
||||
/**
|
||||
* supprimer tous les éléments correspondant au filtre et pour lesquels la
|
||||
* fonction retourne une valeur vraie si elle est spécifiée
|
||||
*
|
||||
* $filter permet de filtrer parmi les élements chargés
|
||||
*
|
||||
* $func est appelé avec la signature de {@link CapacitorChannel::onDelete()}
|
||||
* si la fonction retourne un tableau, il est utilisé pour mettre à jour la
|
||||
* ligne
|
||||
*
|
||||
* @return int le nombre de lignes parcourues
|
||||
*/
|
||||
function delete(CapacitorChannel $channel, $filter, $func=null, ?array $args=null): int {
|
||||
$channel->initCapacitor($this);
|
||||
|
||||
if ($func === null) $func = CapacitorChannel::onDelete;
|
||||
$onDelete = func::with($func)->bind($channel);
|
||||
$db = $this->db();
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
if ($manageTransactions) {
|
||||
$commited = false;
|
||||
$db->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
$count = 0;
|
||||
$tableName = $channel->getTableName();
|
||||
try {
|
||||
$args ??= [];
|
||||
$rows = $this->all($channel, $filter);
|
||||
foreach ($rows as $row) {
|
||||
$rowIds = $this->getRowIds($channel, $row);
|
||||
$shouldDelete = boolval($onDelete->invoke([$row, ...$args]));
|
||||
if ($shouldDelete) {
|
||||
$db->exec([
|
||||
"delete",
|
||||
"from" => $tableName,
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
if ($manageTransactions && $commitThreshold !== null) {
|
||||
$commitThreshold--;
|
||||
if ($commitThreshold <= 0) {
|
||||
$db->commit();
|
||||
$db->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
}
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
if ($manageTransactions) {
|
||||
$db->commit();
|
||||
$commited = true;
|
||||
}
|
||||
return $count;
|
||||
} finally {
|
||||
if ($manageTransactions && !$commited) $db->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
function count($filter=null): int {
|
||||
return $this->storage->_count($this->channel, $filter);
|
||||
}
|
||||
|
||||
function one($filter, ?array $mergeQuery=null): ?array {
|
||||
return $this->storage->_one($this->channel, $filter, $mergeQuery);
|
||||
}
|
||||
|
||||
function all($filter, ?array $mergeQuery=null): Traversable {
|
||||
return $this->storage->_all($this->channel, $filter, $mergeQuery);
|
||||
}
|
||||
|
||||
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||
if ($this->subChannels !== null) $this->beginTransaction();
|
||||
return $this->storage->_each($this->channel, $filter, $func, $args, $mergeQuery, $nbUpdated);
|
||||
}
|
||||
|
||||
function delete($filter, $func=null, ?array $args=null): int {
|
||||
if ($this->subChannels !== null) $this->beginTransaction();
|
||||
return $this->storage->_delete($this->channel, $filter, $func, $args);
|
||||
}
|
||||
|
||||
function dbAll(array $query, ?array $params=null): iterable {
|
||||
$primaryKeys = $this->channel->getPrimaryKeys();
|
||||
return $this->storage->db()->all(cl::merge([
|
||||
"select",
|
||||
"from" => $this->getTableName(),
|
||||
], $query), $params, $primaryKeys);
|
||||
}
|
||||
|
||||
function dbOne(array $query, ?array $params=null): ?array {
|
||||
return $this->storage->db()->one(cl::merge([
|
||||
"select",
|
||||
"from" => $this->getTableName(),
|
||||
], $query), $params);
|
||||
}
|
||||
|
||||
/** @return int|false */
|
||||
function dbUpdate(array $query, ?array $params=null) {
|
||||
return $this->storage->db()->exec(cl::merge([
|
||||
"update",
|
||||
"table" => $this->getTableName(),
|
||||
], $query), $params);
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
$this->storage->close();
|
||||
}
|
||||
abstract function close(): void;
|
||||
}
|
||||
|
@ -1,18 +1,24 @@
|
||||
<?php
|
||||
namespace nulib\db;
|
||||
|
||||
use nulib\app\app;
|
||||
use nulib\cl;
|
||||
use nulib\exceptions;
|
||||
use nulib\php\func;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
|
||||
* Class CapacitorChannel: un canal de données
|
||||
*/
|
||||
class CapacitorChannel implements ITransactor {
|
||||
const NAME = null;
|
||||
|
||||
const TABLE_NAME = null;
|
||||
|
||||
const AUTOCREATE = null;
|
||||
|
||||
protected function COLUMN_DEFINITIONS(): ?array {
|
||||
return static::COLUMN_DEFINITIONS;
|
||||
} const COLUMN_DEFINITIONS = null;
|
||||
@ -50,16 +56,20 @@ class CapacitorChannel implements ITransactor {
|
||||
return $eachCommitThreshold;
|
||||
}
|
||||
|
||||
function __construct(?string $name=null, ?int $eachCommitThreshold=null, ?bool $manageTransactions=null) {
|
||||
$name ??= static::NAME;
|
||||
$tableName ??= static::TABLE_NAME;
|
||||
function __construct(?array $params=null) {
|
||||
$this->capacitor = null;
|
||||
|
||||
$name = $params["name"] ?? static::NAME;
|
||||
$tableName = $params["tableName"] ?? static::TABLE_NAME;
|
||||
self::verifix_name($name, $tableName);
|
||||
$this->name = $name;
|
||||
$this->tableName = $tableName;
|
||||
$this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS;
|
||||
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
||||
|
||||
$autocreate = $params["autocreate"] ?? null;
|
||||
$autocreate ??= !app::get()->isProductionMode();
|
||||
$this->created = !$autocreate;
|
||||
$this->setup = false;
|
||||
$this->created = false;
|
||||
|
||||
$columnDefinitions = $this->COLUMN_DEFINITIONS();
|
||||
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
|
||||
$migration = cl::withn(static::MIGRATION);
|
||||
@ -117,6 +127,13 @@ class CapacitorChannel implements ITransactor {
|
||||
$this->columnDefinitions = $columnDefinitions;
|
||||
$this->primaryKeys = $primaryKeys;
|
||||
$this->migration = $migration;
|
||||
|
||||
$manageTransactions = $params["manageTransactions"] ?? static::MANAGE_TRANSACTIONS;
|
||||
$this->manageTransactions = $manageTransactions;
|
||||
|
||||
$eachCommitThreshold = $params["eachCommitThreshold"] ?? null;
|
||||
$eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
||||
$this->eachCommitThreshold = $eachCommitThreshold;
|
||||
}
|
||||
|
||||
protected string $name;
|
||||
@ -131,40 +148,6 @@ class CapacitorChannel implements ITransactor {
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool indiquer si les modifications de each doivent être gérées dans
|
||||
* une transaction. si false, l'utilisateur doit lui même gérer la
|
||||
* transaction.
|
||||
*/
|
||||
protected bool $manageTransactions;
|
||||
|
||||
function isManageTransactions(): bool {
|
||||
return $this->manageTransactions;
|
||||
}
|
||||
|
||||
function setManageTransactions(bool $manageTransactions=true): self {
|
||||
$this->manageTransactions = $manageTransactions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ?int nombre maximum de modifications dans une transaction avant un
|
||||
* commit automatique dans {@link Capacitor::each()}. Utiliser null pour
|
||||
* désactiver la fonctionnalité.
|
||||
*
|
||||
* ce paramètre n'a d'effet que si $manageTransactions==true
|
||||
*/
|
||||
protected ?int $eachCommitThreshold;
|
||||
|
||||
function getEachCommitThreshold(): ?int {
|
||||
return $this->eachCommitThreshold;
|
||||
}
|
||||
|
||||
function setEachCommitThreshold(?int $eachCommitThreshold=null): self {
|
||||
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* initialiser ce channel avant sa première utilisation.
|
||||
*/
|
||||
@ -319,6 +302,40 @@ class CapacitorChannel implements ITransactor {
|
||||
return $sum !== $psum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool indiquer si les modifications de each doivent être gérées dans
|
||||
* une transaction. si false, l'utilisateur doit lui même gérer la
|
||||
* transaction.
|
||||
*/
|
||||
protected bool $manageTransactions;
|
||||
|
||||
function isManageTransactions(): bool {
|
||||
return $this->manageTransactions;
|
||||
}
|
||||
|
||||
function setManageTransactions(bool $manageTransactions=true): self {
|
||||
$this->manageTransactions = $manageTransactions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var ?int nombre maximum de modifications dans une transaction avant un
|
||||
* commit automatique dans {@link Capacitor::each()}. Utiliser null pour
|
||||
* désactiver la fonctionnalité.
|
||||
*
|
||||
* ce paramètre n'a d'effet que si $manageTransactions==true
|
||||
*/
|
||||
protected ?int $eachCommitThreshold;
|
||||
|
||||
function getEachCommitThreshold(): ?int {
|
||||
return $this->eachCommitThreshold;
|
||||
}
|
||||
|
||||
function setEachCommitThreshold(?int $eachCommitThreshold=null): self {
|
||||
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* méthode appelée lors du chargement avec {@link Capacitor::charge()} pour
|
||||
* créer un nouvel élément
|
||||
@ -400,24 +417,20 @@ class CapacitorChannel implements ITransactor {
|
||||
#############################################################################
|
||||
# Méthodes déléguées pour des workflows centrés sur le channel
|
||||
|
||||
/**
|
||||
* @var Capacitor|null instance de Capacitor par laquelle cette instance est
|
||||
* utilisée
|
||||
*/
|
||||
protected ?Capacitor $capacitor;
|
||||
|
||||
function getCapacitor(): ?Capacitor {
|
||||
return $this->capacitor;
|
||||
}
|
||||
|
||||
function setCapacitor(Capacitor $capacitor): self {
|
||||
$this->capacitor = $capacitor;
|
||||
function initCapacitor(Capacitor $capacitor, bool $autocreate=true): self {
|
||||
if ($this->capacitor === null) $this->capacitor = $capacitor;
|
||||
if ($autocreate) $this->capacitor->autocreate($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
function initStorage(CapacitorStorage $storage): self {
|
||||
new Capacitor($storage, $this);
|
||||
return $this;
|
||||
function db(): IDatabase {
|
||||
return $this->capacitor->db();
|
||||
}
|
||||
|
||||
function ensureLive(): self {
|
||||
@ -425,52 +438,117 @@ class CapacitorChannel implements ITransactor {
|
||||
return $this;
|
||||
}
|
||||
|
||||
function willUpdate(...$transactors): ITransactor {
|
||||
return $this->capacitor->willUpdate(...$transactors);
|
||||
function getCreateSql(): string {
|
||||
return $this->capacitor->getMigration($this)->getSql(get_class($this), $this->db());
|
||||
}
|
||||
|
||||
/** @var CapacitorChannel[] */
|
||||
protected ?array $subChannels = null;
|
||||
|
||||
protected ?array $subManageTransactions = null;
|
||||
|
||||
function willUpdate(...$channels): self {
|
||||
if ($this->subChannels === null) {
|
||||
# désactiver la gestion des transaction sur le channel local aussi
|
||||
$this->subChannels[] = $this;
|
||||
}
|
||||
if ($channels) {
|
||||
foreach ($channels as $channel) {
|
||||
if ($channel instanceof CapacitorChannel) {
|
||||
$this->subChannels[] = $channel;
|
||||
} else {
|
||||
throw exceptions::invalid_type($channel, "channel", CapacitorChannel::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
function inTransaction(): bool {
|
||||
return $this->capacitor->inTransaction();
|
||||
return $this->db()->inTransaction();
|
||||
}
|
||||
|
||||
function beginTransaction(?callable $func=null, bool $commit=true): void {
|
||||
$this->capacitor->beginTransaction($func, $commit);
|
||||
$db = $this->db();
|
||||
if ($this->subChannels !== null) {
|
||||
# on gère des subchannels: ne débuter la transaction que si ce n'est déjà fait
|
||||
if ($this->subManageTransactions === null) {
|
||||
foreach ($this->subChannels as $channel) {
|
||||
$name = $channel->getName();
|
||||
$this->subManageTransactions ??= [];
|
||||
if (!array_key_exists($name, $this->subManageTransactions)) {
|
||||
$this->subManageTransactions[$name] = $channel->isManageTransactions();
|
||||
}
|
||||
$channel->setManageTransactions(false);
|
||||
}
|
||||
if (!$db->inTransaction()) $db->beginTransaction();
|
||||
}
|
||||
} elseif (!$db->inTransaction()) {
|
||||
$db->beginTransaction();
|
||||
}
|
||||
if ($func !== null) {
|
||||
$commited = false;
|
||||
try {
|
||||
func::call($func, $this);
|
||||
if ($commit) {
|
||||
$this->commit();
|
||||
$commited = true;
|
||||
}
|
||||
} finally {
|
||||
if ($commit && !$commited) $this->rollback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function beforeEndTransaction(): void {
|
||||
if ($this->subManageTransactions !== null) {
|
||||
foreach ($this->subChannels as $channel) {
|
||||
$name = $channel->getName();
|
||||
$channel->setManageTransactions($this->subManageTransactions[$name]);
|
||||
}
|
||||
$this->subManageTransactions = null;
|
||||
}
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
$this->capacitor->commit();
|
||||
$this->beforeEndTransaction();
|
||||
$db = $this->db();
|
||||
if ($db->inTransaction()) $db->commit();
|
||||
}
|
||||
|
||||
function rollback(): void {
|
||||
$this->capacitor->rollback();
|
||||
}
|
||||
|
||||
function db(): IDatabase {
|
||||
return $this->capacitor->getStorage()->db();
|
||||
$this->beforeEndTransaction();
|
||||
$db = $this->db();
|
||||
if ($db->inTransaction()) $db->rollback();
|
||||
}
|
||||
|
||||
function exists(): bool {
|
||||
return $this->capacitor->exists();
|
||||
}
|
||||
|
||||
function ensureExists(): void {
|
||||
$this->capacitor->ensureExists();
|
||||
return $this->capacitor->exists($this);
|
||||
}
|
||||
|
||||
function reset(bool $recreate=false): void {
|
||||
$this->capacitor->reset($recreate);
|
||||
$this->capacitor->reset($this, $recreate);
|
||||
}
|
||||
|
||||
function charge($item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||
return $this->capacitor->charge($item, $func, $args, $row);
|
||||
return $this->capacitor->charge($this, $item, $func, $args, $row);
|
||||
}
|
||||
|
||||
function chargeAll(?iterable $items, $func=null, ?array $args=null): int {
|
||||
return $this->capacitor->chargeAll($items, $func, $args);
|
||||
$count = 0;
|
||||
if ($items !== null) {
|
||||
if ($func !== null) {
|
||||
$func = func::with($func, $args)->bind($this);
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
$count += $this->charge($item, $func);
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
function discharge(bool $reset=true): Traversable {
|
||||
return $this->capacitor->discharge($reset);
|
||||
return $this->capacitor->discharge($this, $reset);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -496,40 +574,50 @@ class CapacitorChannel implements ITransactor {
|
||||
|
||||
function count($filter=null): int {
|
||||
$this->verifixFilter($filter);
|
||||
return $this->capacitor->count($filter);
|
||||
return $this->capacitor->count($this, $filter);
|
||||
}
|
||||
|
||||
function one($filter, ?array $mergeQuery=null): ?array {
|
||||
$this->verifixFilter($filter);
|
||||
return $this->capacitor->one($filter, $mergeQuery);
|
||||
return $this->capacitor->one($this, $filter, $mergeQuery);
|
||||
}
|
||||
|
||||
function all($filter, ?array $mergeQuery=null): Traversable {
|
||||
$this->verifixFilter($filter);
|
||||
return $this->capacitor->all($filter, $mergeQuery);
|
||||
return $this->capacitor->all($this, $filter, $mergeQuery);
|
||||
}
|
||||
|
||||
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||
$this->verifixFilter($filter);
|
||||
return $this->capacitor->each($filter, $func, $args, $mergeQuery, $nbUpdated);
|
||||
return $this->capacitor->each($this, $filter, $func, $args, $mergeQuery, $nbUpdated);
|
||||
}
|
||||
|
||||
function delete($filter, $func=null, ?array $args=null): int {
|
||||
$this->verifixFilter($filter);
|
||||
return $this->capacitor->delete($filter, $func, $args);
|
||||
return $this->capacitor->delete($this, $filter, $func, $args);
|
||||
}
|
||||
|
||||
function dbAll(array $query, ?array $params=null): iterable {
|
||||
return $this->capacitor->dbAll($query, $params);
|
||||
$primaryKeys = $this->getPrimaryKeys();
|
||||
return $this->capacitor->db()->all(cl::merge([
|
||||
"select",
|
||||
"from" => $this->getTableName(),
|
||||
], $query), $params, $primaryKeys);
|
||||
}
|
||||
|
||||
function dbOne(array $query, ?array $params=null): ?array {
|
||||
return $this->capacitor->dbOne($query, $params);
|
||||
return $this->capacitor->db()->one(cl::merge([
|
||||
"select",
|
||||
"from" => $this->getTableName(),
|
||||
], $query), $params);
|
||||
}
|
||||
|
||||
/** @return int|false */
|
||||
function dbUpdate(array $query, ?array $params=null) {
|
||||
return $this->capacitor->dbUpdate($query, $params);
|
||||
return $this->capacitor->db()->exec(cl::merge([
|
||||
"update",
|
||||
"table" => $this->getTableName(),
|
||||
], $query), $params);
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
|
@ -1,770 +0,0 @@
|
||||
<?php
|
||||
namespace nulib\db;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\cl;
|
||||
use nulib\cv;
|
||||
use nulib\db\_private\_migration;
|
||||
use nulib\exceptions;
|
||||
use nulib\php\func;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Class CapacitorStorage: objet permettant d'accumuler des données pour les
|
||||
* réutiliser plus tard
|
||||
*/
|
||||
abstract class CapacitorStorage {
|
||||
abstract function db(): IDatabase;
|
||||
|
||||
function ensureLive(): self {
|
||||
$this->db()->ensureLive();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @var CapacitorChannel[] */
|
||||
protected $channels;
|
||||
|
||||
function addChannel(CapacitorChannel $channel): CapacitorChannel {
|
||||
$this->_create($channel);
|
||||
$this->channels[$channel->getName()] = $channel;
|
||||
return $channel;
|
||||
}
|
||||
|
||||
protected function getChannel(?string $name): CapacitorChannel {
|
||||
CapacitorChannel::verifix_name($name);
|
||||
$channel = $this->channels[$name] ?? null;
|
||||
if ($channel === null) {
|
||||
$channel = $this->addChannel(new CapacitorChannel($name));
|
||||
}
|
||||
return $channel;
|
||||
}
|
||||
|
||||
const PRIMARY_KEY_DEFINITION = [
|
||||
"id_" => "genserial",
|
||||
];
|
||||
|
||||
# les définitions sont par défaut pour MariaDB/MySQL
|
||||
const SERDATA_DEFINITION = "mediumtext";
|
||||
const SERSUM_DEFINITION = "varchar(40)";
|
||||
const SERTS_DEFINITION = "datetime";
|
||||
const GENSERIAL_DEFINITION = "integer primary key auto_increment";
|
||||
const GENLIC_DEFINITION = "varchar(80)";
|
||||
const GENLIB_DEFINITION = "varchar(255)";
|
||||
const GENTEXT_DEFINITION = "mediumtext";
|
||||
const GENBOOL_DEFINITION = "integer(1) default 0";
|
||||
const GENUUID_DEFINITION = "varchar(36)";
|
||||
|
||||
protected static function gencol($def): string {
|
||||
if (!is_string($def)) $def = strval($def);
|
||||
$def = trim($def);
|
||||
$parts = preg_split('/\s+/', $def, 2);
|
||||
if (count($parts) == 2) {
|
||||
$def = $parts[0];
|
||||
$rest = " $parts[1]";
|
||||
} else {
|
||||
$rest = null;
|
||||
}
|
||||
switch ($def) {
|
||||
case "serdata": $def = static::SERDATA_DEFINITION; break;
|
||||
case "sersum": $def = static::SERSUM_DEFINITION; break;
|
||||
case "serts": $def = static::SERTS_DEFINITION; break;
|
||||
case "genserial": $def = static::GENSERIAL_DEFINITION; break;
|
||||
case "genlic": $def = static::GENLIC_DEFINITION; break;
|
||||
case "genlib": $def = static::GENLIB_DEFINITION; break;
|
||||
case "gentext": $def = static::GENTEXT_DEFINITION; break;
|
||||
case "genbool": $def = static::GENBOOL_DEFINITION; break;
|
||||
case "genuuid": $def = static::GENUUID_DEFINITION; break;
|
||||
}
|
||||
return "$def$rest";
|
||||
}
|
||||
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"item__" => "serdata",
|
||||
"item__sum_" => "sersum",
|
||||
"created_" => "serts",
|
||||
"modified_" => "serts",
|
||||
];
|
||||
|
||||
protected function ColumnDefinitions(CapacitorChannel $channel, bool $ignoreMigrations=false): array {
|
||||
$definitions = [];
|
||||
if ($channel->getPrimaryKeys() === null) {
|
||||
$definitions[] = static::PRIMARY_KEY_DEFINITION;
|
||||
}
|
||||
$definitions[] = $channel->getColumnDefinitions();
|
||||
$definitions[] = static::COLUMN_DEFINITIONS;
|
||||
# forcer les définitions sans clé à la fin (sqlite requière par exemple que
|
||||
# primary key (columns) soit à la fin)
|
||||
$tmp = cl::merge(...$definitions);
|
||||
$definitions = [];
|
||||
$constraints = [];
|
||||
$index = 0;
|
||||
foreach ($tmp as $col => $def) {
|
||||
if ($col === $index) {
|
||||
$index++;
|
||||
if (is_array($def)) {
|
||||
if (!$ignoreMigrations) {
|
||||
$mdefs = $def;
|
||||
$mindex = 0;
|
||||
foreach ($mdefs as $mcol => $mdef) {
|
||||
if ($mcol === $mindex) {
|
||||
$mindex++;
|
||||
} else {
|
||||
if ($mdef) {
|
||||
$definitions[$mcol] = self::gencol($mdef);
|
||||
} else {
|
||||
unset($definitions[$mcol]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$constraints[] = $def;
|
||||
}
|
||||
} else {
|
||||
$definitions[$col] = self::gencol($def);
|
||||
}
|
||||
}
|
||||
return cl::merge($definitions, $constraints);
|
||||
}
|
||||
|
||||
protected function getMigration(CapacitorChannel $channel): ?array {
|
||||
return $channel->getMigration($this->db()->getPrefix());
|
||||
}
|
||||
|
||||
/** sérialiser les valeurs qui doivent l'être dans $row */
|
||||
protected function serialize(CapacitorChannel $channel, ?array $row): ?array {
|
||||
if ($row === null) return null;
|
||||
$cols = $this->ColumnDefinitions($channel);
|
||||
$index = 0;
|
||||
$raw = [];
|
||||
foreach (array_keys($cols) as $col) {
|
||||
$key = $col;
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
} elseif ($channel->isSerialCol($key)) {
|
||||
[$serialCol, $sumCol] = $channel->getSumCols($key);
|
||||
if (array_key_exists($key, $row)) {
|
||||
$sum = $channel->getSum($key, $row[$key]);
|
||||
$raw[$serialCol] = $sum[$serialCol];
|
||||
if (array_key_exists($sumCol, $cols)) {
|
||||
$raw[$sumCol] = $sum[$sumCol];
|
||||
}
|
||||
}
|
||||
} elseif (array_key_exists($key, $row)) {
|
||||
$raw[$col] = $row[$key];
|
||||
}
|
||||
}
|
||||
return $raw;
|
||||
}
|
||||
|
||||
/** désérialiser les valeurs qui doivent l'être dans $values */
|
||||
protected function unserialize(CapacitorChannel $channel, ?array $raw): ?array {
|
||||
if ($raw === null) return null;
|
||||
$cols = $this->ColumnDefinitions($channel);
|
||||
$index = 0;
|
||||
$row = [];
|
||||
foreach (array_keys($cols) as $col) {
|
||||
$key = $col;
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
} elseif (!array_key_exists($col, $raw)) {
|
||||
} elseif ($channel->isSerialCol($key)) {
|
||||
$value = $raw[$col];
|
||||
if ($value !== null) $value = $channel->unserialize($value);
|
||||
$row[$key] = $value;
|
||||
} else {
|
||||
$row[$key] = $raw[$col];
|
||||
}
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
function getPrimaryKeys(CapacitorChannel $channel): array {
|
||||
$primaryKeys = $channel->getPrimaryKeys();
|
||||
if ($primaryKeys === null) $primaryKeys = ["id_"];
|
||||
return $primaryKeys;
|
||||
}
|
||||
|
||||
function getRowIds(CapacitorChannel $channel, ?array $row, ?array &$primaryKeys=null): ?array {
|
||||
$primaryKeys = $this->getPrimaryKeys($channel);
|
||||
$rowIds = cl::select($row, $primaryKeys);
|
||||
if (cl::all_n($rowIds)) return null;
|
||||
else return $rowIds;
|
||||
}
|
||||
|
||||
protected function _createSql(CapacitorChannel $channel): array {
|
||||
return [
|
||||
"create table if not exists",
|
||||
"table" => $channel->getTableName(),
|
||||
"cols" => $this->ColumnDefinitions($channel, true),
|
||||
];
|
||||
}
|
||||
|
||||
abstract protected function tableExists(string $tableName): bool;
|
||||
|
||||
const METADATA_TABLE = "_metadata";
|
||||
const METADATA_COLS = [
|
||||
"name" => "varchar not null primary key",
|
||||
"value" => "varchar",
|
||||
];
|
||||
|
||||
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 _getMigration(CapacitorChannel $channel): _migration;
|
||||
|
||||
const CHANNELS_TABLE = "_channels";
|
||||
const CHANNELS_COLS = [
|
||||
"name" => "varchar not null primary key",
|
||||
"table_name" => "varchar",
|
||||
"class_name" => "varchar",
|
||||
];
|
||||
|
||||
function channelExists(string $name, ?array &$raw=null): bool {
|
||||
$raw = $this->db()->one([
|
||||
"select",
|
||||
"from" => static::CHANNELS_TABLE,
|
||||
"where" => ["name" => $name],
|
||||
]);
|
||||
return $raw !== null;
|
||||
}
|
||||
|
||||
function getChannels(): iterable {
|
||||
return $this->db()->all([
|
||||
"select",
|
||||
"from" => static::CHANNELS_TABLE,
|
||||
]);
|
||||
}
|
||||
|
||||
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 {
|
||||
$db = $this->db();
|
||||
$db->exec($this->_createChannelsSql());
|
||||
$db->exec($this->_addToChannelsSql($channel));
|
||||
}
|
||||
|
||||
protected function _create(CapacitorChannel $channel): void {
|
||||
$channel->ensureSetup();
|
||||
if (!$channel->isCreated()) {
|
||||
$this->_prepareMetadata();
|
||||
$this->_getMigration($channel)->migrate($this->db());
|
||||
$this->_afterCreate($channel);
|
||||
$channel->setCreated();
|
||||
}
|
||||
}
|
||||
|
||||
/** tester si le canal spécifié existe */
|
||||
function _exists(CapacitorChannel $channel): bool {
|
||||
return $this->tableExists($channel->getTableName());
|
||||
}
|
||||
|
||||
function exists(?string $channel): bool {
|
||||
return $this->_exists($this->getChannel($channel));
|
||||
}
|
||||
|
||||
/** s'assurer que le canal spécifié existe */
|
||||
function _ensureExists(CapacitorChannel $channel): void {
|
||||
$this->_create($channel);
|
||||
}
|
||||
|
||||
function ensureExists(?string $channel): void {
|
||||
$this->_ensureExists($this->getChannel($channel));
|
||||
}
|
||||
|
||||
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é */
|
||||
function _reset(CapacitorChannel $channel, bool $recreate=false): void {
|
||||
$this->_beforeReset($channel);
|
||||
$this->db()->exec([
|
||||
"drop table if exists",
|
||||
$channel->getTableName(),
|
||||
]);
|
||||
$channel->setCreated(false);
|
||||
if ($recreate) $this->_ensureExists($channel);
|
||||
}
|
||||
|
||||
function reset(?string $channel, bool $recreate=false): void {
|
||||
$this->_reset($this->getChannel($channel), $recreate);
|
||||
}
|
||||
|
||||
/**
|
||||
* charger une valeur dans le canal
|
||||
*
|
||||
* Après avoir calculé les valeurs des clés supplémentaires
|
||||
* avec {@link CapacitorChannel::getItemValues()}, l'une des deux fonctions
|
||||
* {@link CapacitorChannel::onCreate()} ou {@link CapacitorChannel::onUpdate()}
|
||||
* est appelée en fonction du type d'opération: création ou mise à jour
|
||||
*
|
||||
* Ensuite, si $func !== null, la fonction est appelée avec la signature de
|
||||
* {@link CapacitorChannel::onCreate()} ou {@link CapacitorChannel::onUpdate()}
|
||||
* en fonction du type d'opération: création ou mise à jour
|
||||
*
|
||||
* Dans les deux cas, si la fonction retourne un tableau, il est utilisé pour
|
||||
* modifier les valeurs insérées/mises à jour. De plus, $row obtient la
|
||||
* valeur finale des données insérées/mises à jour
|
||||
*
|
||||
* Si $args est renseigné, il est ajouté aux arguments utilisés pour appeler
|
||||
* les méthodes {@link CapacitorChannel::getItemValues()},
|
||||
* {@link CapacitorChannel::onCreate()} et/ou
|
||||
* {@link CapacitorChannel::onUpdate()}
|
||||
*
|
||||
* @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait
|
||||
* déjà à l'identique dans le canal
|
||||
*/
|
||||
function _charge(CapacitorChannel $channel, $item, $func, ?array $args, ?array &$row=null): int {
|
||||
$this->_create($channel);
|
||||
$tableName = $channel->getTableName();
|
||||
$db = $this->db();
|
||||
$args ??= [];
|
||||
|
||||
$row = func::call([$channel, "getItemValues"], $item, ...$args);
|
||||
if ($row === [false]) return 0;
|
||||
|
||||
if ($row !== null && array_key_exists("item", $row)) {
|
||||
$item = A::pop($row, "item");
|
||||
}
|
||||
|
||||
$raw = cl::merge(
|
||||
$channel->getSum("item", $item),
|
||||
$this->serialize($channel, $row));
|
||||
$praw = null;
|
||||
$rowIds = $this->getRowIds($channel, $raw, $primaryKeys);
|
||||
if ($rowIds !== null) {
|
||||
# modification
|
||||
$praw = $db->one([
|
||||
"select",
|
||||
"from" => $tableName,
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
}
|
||||
|
||||
$now = date("Y-m-d H:i:s");
|
||||
$insert = null;
|
||||
if ($praw === null) {
|
||||
# création
|
||||
$raw = cl::merge($raw, [
|
||||
"created_" => $now,
|
||||
"modified_" => $now,
|
||||
]);
|
||||
$insert = true;
|
||||
$initFunc = func::with([$channel, "onCreate"], $args);
|
||||
$row = $this->unserialize($channel, $raw);
|
||||
$prow = null;
|
||||
} else {
|
||||
# modification
|
||||
# intégrer autant que possible les valeurs de praw dans raw, de façon que
|
||||
# l'utilisateur puisse voir clairement ce qui a été modifié
|
||||
if ($channel->_wasSumModified("item", $raw, $praw)) {
|
||||
$insert = false;
|
||||
$raw = cl::merge($praw, $raw, [
|
||||
"modified_" => $now,
|
||||
]);
|
||||
} else {
|
||||
$raw = cl::merge($praw, $raw);
|
||||
}
|
||||
$initFunc = func::with([$channel, "onUpdate"], $args);
|
||||
$row = $this->unserialize($channel, $raw);
|
||||
$prow = $this->unserialize($channel, $praw);
|
||||
}
|
||||
|
||||
$updates = $initFunc->prependArgs([$item, $row, $prow])->invoke();
|
||||
if ($updates === [false]) return 0;
|
||||
if (is_array($updates) && $updates) {
|
||||
if ($insert === null) $insert = false;
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = $now;
|
||||
}
|
||||
$row = cl::merge($row, $updates);
|
||||
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
||||
}
|
||||
|
||||
if ($func !== null) {
|
||||
$updates = func::with($func, $args)
|
||||
->prependArgs([$item, $row, $prow])
|
||||
->bind($channel)
|
||||
->invoke();
|
||||
if ($updates === [false]) return 0;
|
||||
if (is_array($updates) && $updates) {
|
||||
if ($insert === null) $insert = false;
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = $now;
|
||||
}
|
||||
$row = cl::merge($row, $updates);
|
||||
$raw = cl::merge($raw, $this->serialize($channel, $updates));
|
||||
}
|
||||
}
|
||||
|
||||
# aucune modification
|
||||
if ($insert === null) return 0;
|
||||
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
if ($manageTransactions) {
|
||||
$commited = false;
|
||||
$db->beginTransaction();
|
||||
}
|
||||
$nbModified = 0;
|
||||
try {
|
||||
if ($insert) {
|
||||
$id = $db->exec([
|
||||
"insert",
|
||||
"into" => $tableName,
|
||||
"values" => $raw,
|
||||
]);
|
||||
if (count($primaryKeys) == 1 && $rowIds === null) {
|
||||
# mettre à jour avec l'id généré
|
||||
$row[$primaryKeys[0]] = $id;
|
||||
}
|
||||
$nbModified = 1;
|
||||
} else {
|
||||
# calculer ce qui a changé pour ne mettre à jour que le nécessaire
|
||||
$updates = [];
|
||||
foreach ($raw as $col => $value) {
|
||||
if (array_key_exists($col, $rowIds)) {
|
||||
# ne jamais mettre à jour la clé primaire
|
||||
continue;
|
||||
}
|
||||
if (!cv::equals($value, $praw[$col] ?? null)) {
|
||||
$updates[$col] = $value;
|
||||
}
|
||||
}
|
||||
if (count($updates) == 1 && array_key_first($updates) == "modified_") {
|
||||
# si l'unique modification porte sur la date de modification, alors
|
||||
# la ligne n'est pas modifiée. ce cas se présente quand on altère la
|
||||
# valeur de $item
|
||||
$updates = null;
|
||||
}
|
||||
if ($updates) {
|
||||
$db->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $updates,
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
$nbModified = 1;
|
||||
}
|
||||
}
|
||||
if ($manageTransactions) {
|
||||
$db->commit();
|
||||
$commited = true;
|
||||
}
|
||||
return $nbModified;
|
||||
} finally {
|
||||
if ($manageTransactions && !$commited) $db->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
function charge(?string $channel, $item, $func=null, ?array $args=null, ?array &$row=null): int {
|
||||
return $this->_charge($this->getChannel($channel), $item, $func, $args, $row);
|
||||
}
|
||||
|
||||
/**
|
||||
* décharger les données du canal spécifié. seul la valeur de $item est
|
||||
* fournie
|
||||
*/
|
||||
function _discharge(CapacitorChannel $channel, bool $reset=true): Traversable {
|
||||
$this->_create($channel);
|
||||
$raws = $this->db()->all([
|
||||
"select item__",
|
||||
"from" => $channel->getTableName(),
|
||||
]);
|
||||
foreach ($raws as $raw) {
|
||||
yield unserialize($raw['item__']);
|
||||
}
|
||||
if ($reset) $this->_reset($channel);
|
||||
}
|
||||
|
||||
function discharge(?string $channel, bool $reset=true): Traversable {
|
||||
return $this->_discharge($this->getChannel($channel), $reset);
|
||||
}
|
||||
|
||||
protected function _convertValue2row(CapacitorChannel $channel, array $filter, array $cols): array {
|
||||
$index = 0;
|
||||
$fixed = [];
|
||||
foreach ($filter as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
if (is_array($value)) {
|
||||
$value = $this->_convertValue2row($channel, $value, $cols);
|
||||
}
|
||||
$fixed[] = $value;
|
||||
} else {
|
||||
$col = "${key}__";
|
||||
if (array_key_exists($col, $cols)) {
|
||||
# colonne sérialisée
|
||||
$fixed[$col] = $channel->serialize($value);
|
||||
} else {
|
||||
$fixed[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $fixed;
|
||||
}
|
||||
|
||||
protected function verifixFilter(CapacitorChannel $channel, &$filter): void {
|
||||
if ($filter !== null && !is_array($filter)) {
|
||||
$primaryKeys = $this->getPrimaryKeys($channel);
|
||||
$id = $filter;
|
||||
$channel->verifixId($id);
|
||||
$filter = [$primaryKeys[0] => $id];
|
||||
}
|
||||
$cols = $this->ColumnDefinitions($channel);
|
||||
if ($filter !== null) {
|
||||
$filter = $this->_convertValue2row($channel, $filter, $cols);
|
||||
}
|
||||
}
|
||||
|
||||
/** indiquer le nombre d'éléments du canal spécifié */
|
||||
function _count(CapacitorChannel $channel, $filter): int {
|
||||
$this->_create($channel);
|
||||
$this->verifixFilter($channel, $filter);
|
||||
return $this->db()->get([
|
||||
"select count(*)",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
]);
|
||||
}
|
||||
|
||||
function count(?string $channel, $filter=null): int {
|
||||
return $this->_count($this->getChannel($channel), $filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* obtenir la ligne correspondant au filtre sur le canal spécifié
|
||||
*
|
||||
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
||||
*/
|
||||
function _one(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): ?array {
|
||||
if ($filter === null) throw exceptions::null_value("filter");
|
||||
$this->_create($channel);
|
||||
$this->verifixFilter($channel, $filter);
|
||||
$raw = $this->db()->one(cl::merge([
|
||||
"select",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
], $mergeQuery));
|
||||
return $this->unserialize($channel, $raw);
|
||||
}
|
||||
|
||||
function one(?string $channel, $filter, ?array $mergeQuery=null): ?array {
|
||||
return $this->_one($this->getChannel($channel), $filter, $mergeQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* obtenir les lignes correspondant au filtre sur le canal spécifié
|
||||
*
|
||||
* si $filter n'est pas un tableau, il est transformé en ["id_" => $filter]
|
||||
*/
|
||||
function _all(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable {
|
||||
$this->_create($channel);
|
||||
$this->verifixFilter($channel, $filter);
|
||||
$raws = $this->db()->all(cl::merge([
|
||||
"select",
|
||||
"from" => $channel->getTableName(),
|
||||
"where" => $filter,
|
||||
], $mergeQuery), null, $this->getPrimaryKeys($channel));
|
||||
foreach ($raws as $key => $raw) {
|
||||
yield $key => $this->unserialize($channel, $raw);
|
||||
}
|
||||
}
|
||||
|
||||
function all(?string $channel, $filter, $mergeQuery=null): Traversable {
|
||||
return $this->_all($this->getChannel($channel), $filter, $mergeQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* appeler une fonction pour chaque élément du canal spécifié.
|
||||
*
|
||||
* $filter permet de filtrer parmi les élements chargés
|
||||
*
|
||||
* $func est appelé avec la signature de {@link CapacitorChannel::onEach()}
|
||||
* si la fonction retourne un tableau, il est utilisé pour mettre à jour la
|
||||
* ligne
|
||||
*
|
||||
* @param int $nbUpdated reçoit le nombre de lignes mises à jour
|
||||
* @return int le nombre de lignes parcourues
|
||||
*/
|
||||
function _each(CapacitorChannel $channel, $filter, $func, ?array $args, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||
$this->_create($channel);
|
||||
if ($func === null) $func = CapacitorChannel::onEach;
|
||||
$onEach = func::with($func)->bind($channel);
|
||||
$db = $this->db();
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
if ($manageTransactions) {
|
||||
$commited = false;
|
||||
$db->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
$count = 0;
|
||||
$nbUpdated = 0;
|
||||
$tableName = $channel->getTableName();
|
||||
try {
|
||||
$args ??= [];
|
||||
$rows = $this->_all($channel, $filter, $mergeQuery);
|
||||
foreach ($rows as $row) {
|
||||
$rowIds = $this->getRowIds($channel, $row);
|
||||
$updates = $onEach->invoke([$row, ...$args]);
|
||||
if ($updates === [false]) {
|
||||
break;
|
||||
} elseif ($updates !== null) {
|
||||
if (!array_key_exists("modified_", $updates)) {
|
||||
$updates["modified_"] = date("Y-m-d H:i:s");
|
||||
}
|
||||
$nbUpdated += $db->exec([
|
||||
"update",
|
||||
"table" => $tableName,
|
||||
"values" => $this->serialize($channel, $updates),
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
if ($manageTransactions && $commitThreshold !== null) {
|
||||
$commitThreshold--;
|
||||
if ($commitThreshold <= 0) {
|
||||
$db->commit();
|
||||
$db->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
}
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
if ($manageTransactions) {
|
||||
$db->commit();
|
||||
$commited = true;
|
||||
}
|
||||
return $count;
|
||||
} finally {
|
||||
if ($manageTransactions && !$commited) $db->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
function each(?string $channel, $filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
|
||||
return $this->_each($this->getChannel($channel), $filter, $func, $args, $mergeQuery, $nbUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* supprimer tous les éléments correspondant au filtre et pour lesquels la
|
||||
* fonction retourne une valeur vraie si elle est spécifiée
|
||||
*
|
||||
* $filter permet de filtrer parmi les élements chargés
|
||||
*
|
||||
* $func est appelé avec la signature de {@link CapacitorChannel::onDelete()}
|
||||
* si la fonction retourne un tableau, il est utilisé pour mettre à jour la
|
||||
* ligne
|
||||
*
|
||||
* @return int le nombre de lignes parcourues
|
||||
*/
|
||||
function _delete(CapacitorChannel $channel, $filter, $func, ?array $args): int {
|
||||
$this->_create($channel);
|
||||
if ($func === null) $func = CapacitorChannel::onDelete;
|
||||
$onDelete = func::with($func)->bind($channel);
|
||||
$db = $this->db();
|
||||
# si on est déjà dans une transaction, désactiver la gestion des transactions
|
||||
$manageTransactions = $channel->isManageTransactions() && !$db->inTransaction();
|
||||
if ($manageTransactions) {
|
||||
$commited = false;
|
||||
$db->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
$count = 0;
|
||||
$tableName = $channel->getTableName();
|
||||
try {
|
||||
$args ??= [];
|
||||
$rows = $this->_all($channel, $filter);
|
||||
foreach ($rows as $row) {
|
||||
$rowIds = $this->getRowIds($channel, $row);
|
||||
$shouldDelete = boolval($onDelete->invoke([$row, ...$args]));
|
||||
if ($shouldDelete) {
|
||||
$db->exec([
|
||||
"delete",
|
||||
"from" => $tableName,
|
||||
"where" => $rowIds,
|
||||
]);
|
||||
if ($manageTransactions && $commitThreshold !== null) {
|
||||
$commitThreshold--;
|
||||
if ($commitThreshold <= 0) {
|
||||
$db->commit();
|
||||
$db->beginTransaction();
|
||||
$commitThreshold = $channel->getEachCommitThreshold();
|
||||
}
|
||||
}
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
if ($manageTransactions) {
|
||||
$db->commit();
|
||||
$commited = true;
|
||||
}
|
||||
return $count;
|
||||
} finally {
|
||||
if ($manageTransactions && !$commited) $db->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
function delete(?string $channel, $filter, $func=null, ?array $args=null): int {
|
||||
return $this->_delete($this->getChannel($channel), $filter, $func, $args);
|
||||
}
|
||||
|
||||
abstract function close(): void;
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
# db
|
||||
|
||||
# db/Capacitor
|
||||
|
||||
La source peut être un iterable
|
||||
|
||||
---
|
||||
|
||||
charge() permet de spécifier la clé associée avec la valeur chargée, et
|
||||
discharge() retourne les valeurs avec la clé primaire
|
||||
|
||||
|
@ -25,14 +25,11 @@ class conds {
|
||||
|
||||
/**
|
||||
* retourner une condition "like" si la valeur s'y prête
|
||||
*
|
||||
* - si la valeur fait moins de $likeThreshold caractères, faire une recherche
|
||||
* exacte en retournant ["=", $value]
|
||||
*
|
||||
*
|
||||
* - les espaces sont remplacés par %
|
||||
*
|
||||
* si $partial
|
||||
* - si $partial et que $value ne contient pas d'espaces, rajouter un % à la
|
||||
* fin
|
||||
*/
|
||||
static function like($value, bool $partial=false, ?int $likeThreshold=null) {
|
||||
if ($value === false || $value === null) return $value;
|
||||
|
@ -3,12 +3,22 @@ namespace nulib\db\mysql;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\CapacitorStorage;
|
||||
use nulib\db\Capacitor;
|
||||
|
||||
/**
|
||||
* Class MysqlStorage
|
||||
*/
|
||||
class MysqlStorage extends CapacitorStorage {
|
||||
class MysqlCapacitor extends Capacitor {
|
||||
const CDATA_DEFINITION = "mediumtext";
|
||||
const CSUM_DEFINITION = "varchar(40)";
|
||||
const CTIMESTAMP_DEFINITION = "datetime";
|
||||
const GSERIAL_DEFINITION = "integer primary key auto_increment";
|
||||
const GLIC_DEFINITION = "varchar(80)";
|
||||
const GLIB_DEFINITION = "varchar(255)";
|
||||
const GTEXT_DEFINITION = "mediumtext";
|
||||
const GBOOL_DEFINITION = "integer(1) default 0";
|
||||
const GUUID_DEFINITION = "varchar(36)";
|
||||
|
||||
function __construct($mysql) {
|
||||
$this->db = Mysql::with($mysql);
|
||||
}
|
||||
@ -36,21 +46,21 @@ class MysqlStorage extends CapacitorStorage {
|
||||
"value" => "varchar(255)",
|
||||
];
|
||||
|
||||
function _getMigration(CapacitorChannel $channel): _mysqlMigration {
|
||||
function getMigration(CapacitorChannel $channel): _mysqlMigration {
|
||||
$migrations = cl::merge([
|
||||
"0init" => [$this->_createSql($channel)],
|
||||
"0init" => [$this->getCreateChannelSql($channel)],
|
||||
], $channel->getMigration($this->db->getPrefix()));
|
||||
return new _mysqlMigration($migrations, $channel->getName());
|
||||
}
|
||||
|
||||
const CHANNELS_COLS = [
|
||||
const CATALOG_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), [
|
||||
protected function addToCatalogSql(CapacitorChannel $channel): array {
|
||||
return cl::merge(parent::addToCatalogSql($channel), [
|
||||
"suffix" => "on duplicate key update name = name",
|
||||
]);
|
||||
}
|
@ -3,16 +3,18 @@ namespace nulib\db\pgsql;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\CapacitorStorage;
|
||||
use nulib\db\Capacitor;
|
||||
|
||||
class PgsqlStorage extends CapacitorStorage {
|
||||
const SERDATA_DEFINITION = "text";
|
||||
const SERSUM_DEFINITION = "varchar(40)";
|
||||
const SERTS_DEFINITION = "timestamp";
|
||||
const GENSERIAL_DEFINITION = "serial primary key";
|
||||
const GENTEXT_DEFINITION = "text";
|
||||
const GENBOOL_DEFINITION = "boolean default false";
|
||||
const GENUUID_DEFINITION = "uuid";
|
||||
class PgsqlCapacitor extends Capacitor {
|
||||
const CDATA_DEFINITION = "text";
|
||||
const CSUM_DEFINITION = "varchar(40)";
|
||||
const CTIMESTAMP_DEFINITION = "timestamp";
|
||||
const GSERIAL_DEFINITION = "serial primary key";
|
||||
const GLIC_DEFINITION = "varchar(80)";
|
||||
const GLIB_DEFINITION = "varchar(255)";
|
||||
const GTEXT_DEFINITION = "text";
|
||||
const GBOOL_DEFINITION = "boolean default false";
|
||||
const GUUID_DEFINITION = "uuid";
|
||||
|
||||
function __construct($pgsql) {
|
||||
$this->db = Pgsql::with($pgsql);
|
||||
@ -41,15 +43,15 @@ class PgsqlStorage extends CapacitorStorage {
|
||||
return $found !== null;
|
||||
}
|
||||
|
||||
function _getMigration(CapacitorChannel $channel): _pgsqlMigration {
|
||||
function getMigration(CapacitorChannel $channel): _pgsqlMigration {
|
||||
$migrations = cl::merge([
|
||||
"0init" => [$this->_createSql($channel)],
|
||||
"0init" => [$this->getCreateChannelSql($channel)],
|
||||
], $channel->getMigration($this->db->getPrefix()));
|
||||
return new _pgsqlMigration($migrations, $channel->getName());
|
||||
}
|
||||
|
||||
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||
return cl::merge(parent::_addToChannelsSql($channel), [
|
||||
protected function addToCatalogSql(CapacitorChannel $channel): array {
|
||||
return cl::merge(parent::addToCatalogSql($channel), [
|
||||
"suffix" => "on conflict (name) do nothing",
|
||||
]);
|
||||
}
|
@ -3,13 +3,21 @@ namespace nulib\db\sqlite;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\CapacitorStorage;
|
||||
use nulib\db\Capacitor;
|
||||
|
||||
/**
|
||||
* Class SqliteStorage
|
||||
*/
|
||||
class SqliteStorage extends CapacitorStorage {
|
||||
const GENSERIAL_DEFINITION = "integer primary key autoincrement";
|
||||
class SqliteCapacitor extends Capacitor {
|
||||
const CDATA_DEFINITION = "mediumtext";
|
||||
const CSUM_DEFINITION = "varchar(40)";
|
||||
const CTIMESTAMP_DEFINITION = "datetime";
|
||||
const GSERIAL_DEFINITION = "integer primary key autoincrement";
|
||||
const GLIC_DEFINITION = "varchar(80)";
|
||||
const GLIB_DEFINITION = "varchar(255)";
|
||||
const GTEXT_DEFINITION = "mediumtext";
|
||||
const GBOOL_DEFINITION = "integer(1) default 0";
|
||||
const GUUID_DEFINITION = "varchar(36)";
|
||||
|
||||
function __construct($sqlite) {
|
||||
$this->db = Sqlite::with($sqlite);
|
||||
@ -31,30 +39,30 @@ class SqliteStorage extends CapacitorStorage {
|
||||
return $found !== null;
|
||||
}
|
||||
|
||||
function _getMigration(CapacitorChannel $channel): _sqliteMigration {
|
||||
function getMigration(CapacitorChannel $channel): _sqliteMigration {
|
||||
$migrations = cl::merge([
|
||||
"0init" => [$this->_createSql($channel)],
|
||||
"0init" => [$this->getCreateChannelSql($channel)],
|
||||
], $channel->getMigration($this->db->getPrefix()));
|
||||
return new _sqliteMigration($migrations, $channel->getName());
|
||||
}
|
||||
|
||||
protected function _addToChannelsSql(CapacitorChannel $channel): array {
|
||||
$sql = parent::_addToChannelsSql($channel);
|
||||
protected function addToCatalogSql(CapacitorChannel $channel): array {
|
||||
$sql = parent::addToCatalogSql($channel);
|
||||
$sql[0] = "insert or ignore";
|
||||
return $sql;
|
||||
}
|
||||
|
||||
protected function _afterCreate(CapacitorChannel $channel): void {
|
||||
protected function afterCreate(CapacitorChannel $channel): void {
|
||||
$db = $this->db;
|
||||
if (!$this->tableExists(static::CHANNELS_TABLE)) {
|
||||
if (!$this->tableExists(static::CATALOG_TABLE)) {
|
||||
# ne pas créer si la table existe déjà, pour éviter d'avoir besoin d'un
|
||||
# verrou en écriture
|
||||
$db->exec($this->_createChannelsSql());
|
||||
$db->exec($this->getCreateCatalogSql());
|
||||
}
|
||||
if (!$this->channelExists($channel->getName())) {
|
||||
if (!$this->isInCatalog(["name" => $channel->getName()])) {
|
||||
# ne pas insérer si la ligne existe déjà, pour éviter d'avoir besoin d'un
|
||||
# verrou en écriture
|
||||
$db->exec($this->_addToChannelsSql($channel));
|
||||
$db->exec($this->addToCatalogSql($channel));
|
||||
}
|
||||
}
|
||||
|
@ -2,27 +2,15 @@
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\Capacitor;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\mysql\Mysql;
|
||||
use nulib\db\mysql\MysqlStorage;
|
||||
use nulib\output\msg;
|
||||
use nulib\output\std\ConsoleMessenger;
|
||||
|
||||
msg::set_messenger_class(ConsoleMessenger::class);
|
||||
|
||||
$db = new Mysql([
|
||||
"type" => "mysql",
|
||||
"name" => "mysql:host=mysql.devel.self;dbname=jclain;charset=utf8",
|
||||
"user" => "jclain",
|
||||
]);
|
||||
use nulib\db\mysql\MysqlCapacitor;
|
||||
|
||||
class MyChannel extends CapacitorChannel {
|
||||
const TABLE_NAME = "my";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"name" => "varchar(64) not null",
|
||||
"age" => "integer",
|
||||
"num" => ["integer"],
|
||||
"num" => "integer",
|
||||
];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
@ -35,7 +23,12 @@ class MyChannel extends CapacitorChannel {
|
||||
}
|
||||
}
|
||||
|
||||
new Capacitor(new MysqlStorage($db), $channel = new MyChannel());
|
||||
$capacitor = new MysqlCapacitor([
|
||||
"type" => "mysql",
|
||||
"name" => "mysql:host=mysql.devel.self;dbname=jclain;charset=utf8",
|
||||
"user" => "jclain",
|
||||
]);
|
||||
$channel = $capacitor->newChannel(new MyChannel());
|
||||
|
||||
$channel->charge("hello world");
|
||||
$channel->charge(["bonjour monde"]);
|
||||
|
@ -5,23 +5,15 @@ use nulib\cl;
|
||||
use nulib\db\Capacitor;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\pgsql\Pgsql;
|
||||
use nulib\db\pgsql\PgsqlStorage;
|
||||
|
||||
$db = new Pgsql([
|
||||
"host" => "pegase-dre.self",
|
||||
"dbname" => "dre",
|
||||
"user" => "root",
|
||||
"password" => "admin",
|
||||
#"user" => "reader",
|
||||
#"password" => "reader",
|
||||
]);
|
||||
use nulib\db\pgsql\PgsqlCapacitor;
|
||||
use nulib\db\sqlite\SqliteCapacitor;
|
||||
|
||||
class MyChannel extends CapacitorChannel {
|
||||
const TABLE_NAME = "my";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"name" => "varchar not null",
|
||||
"age" => "integer",
|
||||
"num" => ["integer"],
|
||||
"num" => "integer",
|
||||
];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
@ -34,7 +26,15 @@ class MyChannel extends CapacitorChannel {
|
||||
}
|
||||
}
|
||||
|
||||
new Capacitor(new PgsqlStorage($db), $channel = new MyChannel());
|
||||
$capacitor = new PgsqlCapacitor([
|
||||
"host" => "pegase-dre.self",
|
||||
"dbname" => "prod_dre",
|
||||
"user" => "postgres",
|
||||
"password" => "admin",
|
||||
#"user" => "reader",
|
||||
#"password" => "reader",
|
||||
]);
|
||||
$channel = $capacitor->newChannel(new MyChannel());
|
||||
|
||||
$channel->charge("hello world");
|
||||
$channel->charge(["bonjour monde"]);
|
||||
|
@ -2,19 +2,15 @@
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\db\Capacitor;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\db\sqlite\Sqlite;
|
||||
use nulib\db\sqlite\SqliteStorage;
|
||||
|
||||
$db = new Sqlite(__DIR__.'/test_sqlite.db');
|
||||
use nulib\db\sqlite\SqliteCapacitor;
|
||||
|
||||
class MyChannel extends CapacitorChannel {
|
||||
const TABLE_NAME = "my";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"name" => "varchar not null",
|
||||
"age" => "integer",
|
||||
"num" => ["integer"],
|
||||
"num" => "integer",
|
||||
];
|
||||
|
||||
function getItemValues($item): ?array {
|
||||
@ -27,7 +23,8 @@ class MyChannel extends CapacitorChannel {
|
||||
}
|
||||
}
|
||||
|
||||
new Capacitor(new SqliteStorage($db), $channel = new MyChannel());
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/test-sqlite.db');
|
||||
$channel = $capacitor->newChannel(new MyChannel());
|
||||
|
||||
$channel->charge("hello world");
|
||||
$channel->charge(["bonjour monde"]);
|
||||
|
@ -1,22 +1,14 @@
|
||||
<?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\db\sqlite\impl\MyIndexChannel;
|
||||
use nulib\output\msg;
|
||||
use nulib\output\std\ConsoleMessenger;
|
||||
use nulib\php\time\DateTime;
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class ChannelMigrationTest extends TestCase {
|
||||
static function setUpBeforeClass(): void {
|
||||
parent::setUpBeforeClass();
|
||||
msg::set_messenger_class(ConsoleMessenger::class);
|
||||
}
|
||||
|
||||
protected function addData(MyChannel $channel, array $data): void {
|
||||
foreach ($data as [$name, $value, $dateCre, $dateMod, $age]) {
|
||||
$channel->charge([
|
||||
@ -30,23 +22,23 @@ class ChannelMigrationTest extends TestCase {
|
||||
}
|
||||
|
||||
function testMigration() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$data = [
|
||||
["first", "premier", new DateTime(), new DateTime(), 15],
|
||||
["second", "deuxieme", new DateTime(), new DateTime(), 15],
|
||||
];
|
||||
|
||||
new Capacitor($storage, $channel = new MyChannel());
|
||||
$capacitor->newChannel($channel = new MyChannel());
|
||||
$channel->reset(true);
|
||||
$this->addData($channel, $data);
|
||||
|
||||
new Capacitor($storage, $channel = new MyChannelV2());
|
||||
$capacitor->newChannel($channel = new MyChannelV2());
|
||||
$this->addData($channel, $data);
|
||||
|
||||
new Capacitor($storage, $channel = new MyChannelV3());
|
||||
$capacitor->newChannel($channel = new MyChannelV3());
|
||||
$this->addData($channel, $data);
|
||||
|
||||
$sql = $channel->getCapacitor()->getCreateSql();
|
||||
$sql = $channel->getCreateSql();
|
||||
$class = MyChannelV3::class;
|
||||
$expected = <<<EOT
|
||||
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
@ -75,17 +67,17 @@ EOT;
|
||||
}
|
||||
|
||||
function testMigrationIndex() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$data = [
|
||||
["un", "premier", "first"],
|
||||
["deux", "deuxieme", "second"],
|
||||
];
|
||||
|
||||
new Capacitor($storage, $channel = new MyIndexChannel());
|
||||
$capacitor->newChannel($channel = new MyIndexChannel());
|
||||
$channel->reset(true);
|
||||
$channel->chargeAll($data);
|
||||
|
||||
$sql = $channel->getCapacitor()->getCreateSql();
|
||||
$sql = $channel->getCreateSql();
|
||||
$class = MyIndexChannel::class;
|
||||
$expected = <<<EOT
|
||||
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
|
@ -6,38 +6,48 @@ use nulib\db\Capacitor;
|
||||
use nulib\db\CapacitorChannel;
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class SqliteStorageTest extends TestCase {
|
||||
class SqliteCapacitorTest extends TestCase {
|
||||
static function Txx(...$values): void {
|
||||
foreach ($values as $value) {
|
||||
var_export($value);
|
||||
}
|
||||
}
|
||||
|
||||
function _testChargeStrings(SqliteStorage $storage, ?string $channel) {
|
||||
$storage->reset($channel);
|
||||
$storage->charge($channel, "first");
|
||||
$storage->charge($channel, "second");
|
||||
$storage->charge($channel, "third");
|
||||
$items = cl::all($storage->discharge($channel, false));
|
||||
function _testChargeStrings(SqliteCapacitor $capacitor, CapacitorChannel $channel) {
|
||||
$capacitor->reset($channel);
|
||||
$capacitor->charge($channel, "first");
|
||||
$capacitor->charge($channel, "second");
|
||||
$capacitor->charge($channel, "third");
|
||||
$items = cl::all($capacitor->discharge($channel, false));
|
||||
self::assertSame(["first", "second", "third"], $items);
|
||||
}
|
||||
|
||||
function _testChargeArrays(SqliteStorage $storage, ?string $channel) {
|
||||
$storage->reset($channel);
|
||||
$storage->charge($channel, ["id" => 10, "name" => "first"]);
|
||||
$storage->charge($channel, ["name" => "second", "id" => 20]);
|
||||
$storage->charge($channel, ["name" => "third", "id" => "30"]);
|
||||
function _testChargeArrays(SqliteCapacitor $capacitor, CapacitorChannel $channel) {
|
||||
$capacitor->reset($channel);
|
||||
$capacitor->charge($channel, ["id" => 10, "name" => "first"]);
|
||||
$capacitor->charge($channel, ["name" => "second", "id" => 20]);
|
||||
$capacitor->charge($channel, ["name" => "third", "id" => "30"]);
|
||||
}
|
||||
|
||||
function testChargeStrings() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$this->_testChargeStrings($storage, null);
|
||||
$storage->close();
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
|
||||
$channel = $capacitor->newChannel(null);
|
||||
$this->_testChargeStrings($capacitor, $channel);
|
||||
self::Txx(cl::all($capacitor->discharge($channel, false)));
|
||||
|
||||
$channel = $capacitor->newChannel("strings");
|
||||
$this->_testChargeStrings($capacitor, $channel);
|
||||
self::Txx(cl::all($capacitor->discharge($channel, false)));
|
||||
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testChargeArrays() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$storage->addChannel(new class extends CapacitorChannel {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
|
||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
||||
const NAME = "arrays";
|
||||
const COLUMN_DEFINITIONS = ["id" => "integer"];
|
||||
|
||||
@ -45,15 +55,16 @@ class SqliteStorageTest extends TestCase {
|
||||
return ["id" => $item["id"] ?? null];
|
||||
}
|
||||
});
|
||||
$this->_testChargeArrays($capacitor, $channel);
|
||||
self::Txx(cl::all($capacitor->discharge($channel, false)));
|
||||
|
||||
$this->_testChargeStrings($storage, "strings");
|
||||
$this->_testChargeArrays($storage, "arrays");
|
||||
$storage->close();
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testEach() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$each = $capacitor->newChannel(new class extends CapacitorChannel {
|
||||
const NAME = "each";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"age" => "integer",
|
||||
@ -67,11 +78,11 @@ class SqliteStorageTest extends TestCase {
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->reset();
|
||||
$capacitor->charge(["name" => "first", "age" => 5]);
|
||||
$capacitor->charge(["name" => "second", "age" => 10]);
|
||||
$capacitor->charge(["name" => "third", "age" => 15]);
|
||||
$capacitor->charge(["name" => "fourth", "age" => 20]);
|
||||
$capacitor->reset($each);
|
||||
$capacitor->charge($each, ["name" => "first", "age" => 5]);
|
||||
$capacitor->charge($each, ["name" => "second", "age" => 10]);
|
||||
$capacitor->charge($each, ["name" => "third", "age" => 15]);
|
||||
$capacitor->charge($each, ["name" => "fourth", "age" => 20]);
|
||||
|
||||
$setDone = function ($row, $suffix=null) {
|
||||
$item = $row["item"];
|
||||
@ -82,17 +93,18 @@ class SqliteStorageTest extends TestCase {
|
||||
}
|
||||
return $updates;
|
||||
};
|
||||
$capacitor->each(["age" => [">", 10]], $setDone, ["++"]);
|
||||
$capacitor->each(["done" => 0], $setDone);
|
||||
$capacitor->each($each, ["age" => [">", 10]], $setDone, ["++"]);
|
||||
$capacitor->each($each, ["done" => 0], $setDone);
|
||||
|
||||
self::Txx(cl::all($capacitor->discharge($each, false)));
|
||||
|
||||
self::Txx(cl::all($capacitor->discharge(false)));
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testPrimayKey() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
||||
const NAME = "pk";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"id_" => "varchar primary key",
|
||||
@ -106,21 +118,23 @@ class SqliteStorageTest extends TestCase {
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->charge(["numero" => "a", "name" => "first", "age" => 5]);
|
||||
$capacitor->charge(["numero" => "b", "name" => "second", "age" => 10]);
|
||||
$capacitor->charge(["numero" => "c", "name" => "third", "age" => 15]);
|
||||
$capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 20]);
|
||||
$capacitor->charge($channel, ["numero" => "a", "name" => "first", "age" => 5]);
|
||||
$capacitor->charge($channel, ["numero" => "b", "name" => "second", "age" => 10]);
|
||||
$capacitor->charge($channel, ["numero" => "c", "name" => "third", "age" => 15]);
|
||||
$capacitor->charge($channel, ["numero" => "d", "name" => "fourth", "age" => 20]);
|
||||
sleep(2);
|
||||
$capacitor->charge(["numero" => "b", "name" => "second", "age" => 100]);
|
||||
$capacitor->charge(["numero" => "d", "name" => "fourth", "age" => 200]);
|
||||
$capacitor->charge($channel, ["numero" => "b", "name" => "second", "age" => 100]);
|
||||
$capacitor->charge($channel, ["numero" => "d", "name" => "fourth", "age" => 200]);
|
||||
|
||||
self::Txx(cl::all($capacitor->discharge($channel, false)));
|
||||
|
||||
$capacitor->close();
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
function testSum() {
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
||||
const NAME = "sum";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"a__" => "varchar",
|
||||
@ -136,19 +150,17 @@ class SqliteStorageTest extends TestCase {
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->reset();
|
||||
$capacitor->charge(["a" => null, "b" => null]);
|
||||
$capacitor->charge(["a" => "first", "b" => "second"]);
|
||||
$capacitor->reset($channel);
|
||||
$capacitor->charge($channel, ["a" => null, "b" => null]);
|
||||
$capacitor->charge($channel, ["a" => "first", "b" => "second"]);
|
||||
|
||||
self::Txx("=== all");
|
||||
/** @var Sqlite $sqlite */
|
||||
$sqlite = $capacitor->getStorage()->db();
|
||||
self::Txx(cl::all($sqlite->all([
|
||||
self::Txx(cl::all($capacitor->db()->all([
|
||||
"select",
|
||||
"from" => $capacitor->getChannel()->getTableName(),
|
||||
"from" => $channel->getTableName(),
|
||||
])));
|
||||
self::Txx("=== each");
|
||||
$capacitor->each(null, function ($row) {
|
||||
$capacitor->each($channel, null, function ($row) {
|
||||
self::Txx($row);
|
||||
});
|
||||
|
||||
@ -158,8 +170,8 @@ class SqliteStorageTest extends TestCase {
|
||||
|
||||
function testEachValues() {
|
||||
# tester que values contient bien toutes les valeurs de la ligne
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
||||
const NAME = "each_values";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"name" => "varchar primary key",
|
||||
@ -176,8 +188,8 @@ class SqliteStorageTest extends TestCase {
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->reset();
|
||||
$capacitor->charge(["name" => "first", "age" => 5], function($item, ?array $row, ?array $prow) {
|
||||
$capacitor->reset($channel);
|
||||
$capacitor->charge($channel, ["name" => "first", "age" => 5], function($item, ?array $row, ?array $prow) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(5, $item["age"]);
|
||||
self::assertnotnull($row);
|
||||
@ -189,7 +201,7 @@ class SqliteStorageTest extends TestCase {
|
||||
], cl::select($row, ["name", "age", "item"]));
|
||||
self::assertNull($prow);
|
||||
});
|
||||
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
||||
$capacitor->charge($channel, ["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(10, $item["age"]);
|
||||
self::assertnotnull($row);
|
||||
@ -211,7 +223,7 @@ class SqliteStorageTest extends TestCase {
|
||||
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
||||
});
|
||||
|
||||
$capacitor->each(null, function(array $row) {
|
||||
$capacitor->each($channel, null, function(array $row) {
|
||||
$item = $row["item"];
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(10, $item["age"]);
|
||||
@ -229,7 +241,7 @@ class SqliteStorageTest extends TestCase {
|
||||
"notes" => "modified",
|
||||
];
|
||||
});
|
||||
$capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
||||
$capacitor->charge($channel, ["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(10, $item["age"]);
|
||||
self::assertnotnull($row);
|
||||
@ -251,7 +263,7 @@ class SqliteStorageTest extends TestCase {
|
||||
], cl::select($prow, ["name", "age", "done", "notes", "item"]));
|
||||
});
|
||||
|
||||
$capacitor->charge(["name" => "first", "age" => 20], function($item, ?array $row, ?array $prow) {
|
||||
$capacitor->charge($channel, ["name" => "first", "age" => 20], function($item, ?array $row, ?array $prow) {
|
||||
self::assertSame("first", $item["name"]);
|
||||
self::assertSame(20, $item["age"]);
|
||||
self::assertnotnull($row);
|
||||
@ -276,8 +288,8 @@ class SqliteStorageTest extends TestCase {
|
||||
|
||||
function testSetItemNull() {
|
||||
# tester le forçage de $îtem à null pour économiser la place
|
||||
$storage = new SqliteStorage(__DIR__.'/capacitor.db');
|
||||
$capacitor = new Capacitor($storage, new class extends CapacitorChannel {
|
||||
$capacitor = new SqliteCapacitor(__DIR__.'/capacitor.db');
|
||||
$channel = $capacitor->newChannel(new class extends CapacitorChannel {
|
||||
const NAME = "set_item_null";
|
||||
const COLUMN_DEFINITIONS = [
|
||||
"name" => "varchar primary key",
|
||||
@ -294,8 +306,8 @@ class SqliteStorageTest extends TestCase {
|
||||
}
|
||||
});
|
||||
|
||||
$capacitor->reset();
|
||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 5], function ($item, ?array $row, ?array $prow) {
|
||||
$capacitor->reset($channel);
|
||||
$nbModified = $capacitor->charge($channel, ["name" => "first", "age" => 5], function ($item, ?array $row, ?array $prow) {
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 5,
|
||||
"item" => $item,
|
||||
@ -306,7 +318,7 @@ class SqliteStorageTest extends TestCase {
|
||||
sleep(1);
|
||||
# nb: on met des sleep() pour que la date de modification soit systématiquement différente
|
||||
|
||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
||||
$nbModified = $capacitor->charge($channel, ["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 10,
|
||||
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
||||
@ -321,7 +333,7 @@ class SqliteStorageTest extends TestCase {
|
||||
sleep(1);
|
||||
|
||||
# pas de modification ici
|
||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
||||
$nbModified = $capacitor->charge($channel, ["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) {
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 10,
|
||||
"item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc",
|
||||
@ -335,7 +347,7 @@ class SqliteStorageTest extends TestCase {
|
||||
self::assertSame(0, $nbModified);
|
||||
sleep(1);
|
||||
|
||||
$nbModified = $capacitor->charge(["name" => "first", "age" => 20], function ($item, ?array $row, ?array $prow) {
|
||||
$nbModified = $capacitor->charge($channel, ["name" => "first", "age" => 20], function ($item, ?array $row, ?array $prow) {
|
||||
self::assertSame([
|
||||
"name" => "first", "age" => 20,
|
||||
"item" => $item, "item__sum_" => "001b91982b4e0883b75428c0eb28573a5dc5f7a5",
|
381
wip/_pman.tool
381
wip/_pman.tool
@ -1,117 +1,298 @@
|
||||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
source "$(dirname -- "$0")/../load.sh" || exit 1
|
||||
require: git pman pman.tool pman.conf
|
||||
require: git pman pman.conf "pman.tool.$MYNAME"
|
||||
|
||||
git_cleancheckout_DIRTY="\
|
||||
Vous avez des modifications locales.
|
||||
Enregistrez ces modifications avant de fusionner la branche"
|
||||
|
||||
function dump_action() {
|
||||
echo -n "\
|
||||
SRC_TYPE=$SRC_TYPE
|
||||
SRC_BRANCH=$SRC_BRANCH
|
||||
DEST_TYPE=$DEST_TYPE
|
||||
DEST_BRANCH=$DEST_BRANCH
|
||||
|
||||
CurrentBranch=$CurrentBranch
|
||||
LocalBranches=${LocalBranches[*]}
|
||||
RemoteBranches=${RemoteBranches[*]}
|
||||
AllBranches=${AllBranches[*]}
|
||||
|
||||
SrcType=$SrcType
|
||||
SrcBranch=$SrcBranch
|
||||
DestType=$DestType
|
||||
DestBranch=$DestBranch
|
||||
|
||||
UpstreamBranch=$UpstreamBranch
|
||||
FeatureBranches=${FeatureBranches[*]}
|
||||
DevelopBranch=$DevelopBranch
|
||||
ReleaseBranch=$ReleaseBranch
|
||||
HotfixBranch=$HotfixBranch
|
||||
MainBranch=$MainBranch
|
||||
DistBranch=$DistBranch
|
||||
"
|
||||
}
|
||||
|
||||
function _ensure_src_branch() {
|
||||
[ -n "$SrcBranch" ] || die "La branche $SRC_TYPE n'a pas été définie"
|
||||
[ "$1" == init -o -n "${!SRC_BRANCH}" ] || die "$SrcBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function _ensure_dest_branch() {
|
||||
[ -n "$DestBranch" ] || die "La branche $DEST_TYPE n'a pas été définie"
|
||||
[ "$1" == init -o -n "${!DEST_BRANCH}" ] || die "$DestBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
|
||||
}
|
||||
|
||||
function checkout_action() {
|
||||
local -a push_branches
|
||||
|
||||
if [ -z "${!SRC_BRANCH}" ]; then
|
||||
array_contains AllBranches "$SrcBranch" && exit_with enote "\
|
||||
$SrcBranch: une branche du même nom existe dans l'origine
|
||||
git checkout $SrcBranch"
|
||||
_ensure_dest_branch
|
||||
_ensure_src_branch init
|
||||
|
||||
resolve_should_push
|
||||
|
||||
enote "Vous allez créer la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
einfo "Création de la branche $SrcBranch"
|
||||
git checkout -b "$SrcBranch" "$DestBranch" || die
|
||||
push_branches+=("$SrcBranch")
|
||||
|
||||
_push_branches
|
||||
fi
|
||||
git checkout "$SrcBranch"
|
||||
}
|
||||
|
||||
function ensure_branches() {
|
||||
[ -n "${!SRC_BRANCH}" -a -n "${!DEST_BRANCH}" ] ||
|
||||
die "${!SRC_BRANCH}: Aucune configuration de fusion trouvée pour cette branche"
|
||||
|
||||
array_contains LocalBranches "${!SRC_BRANCH}" || die "${!SRC_BRANCH}: branche source introuvable"
|
||||
array_contains LocalBranches "${!DEST_BRANCH}" || die "${!DEST_BRANCH}: branche destination introuvable"
|
||||
}
|
||||
|
||||
function _show_action() {
|
||||
local commits
|
||||
setx commits=_list_commits
|
||||
if [ -n "$commits" ]; then
|
||||
if [ $ShowLevel -ge 2 ]; then
|
||||
{
|
||||
echo "\
|
||||
# Commits à fusionner ${!SRC_BRANCH} --> ${!DEST_BRANCH}
|
||||
|
||||
$commits
|
||||
"
|
||||
_sd_COLOR=always _show_diff
|
||||
} | less -eRF
|
||||
else
|
||||
einfo "Commits à fusionner ${!SRC_BRANCH} --> ${!DEST_BRANCH}"
|
||||
eecho "$commits"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
function show_action() {
|
||||
git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY"
|
||||
ensure_branches
|
||||
_show_action "$@"
|
||||
}
|
||||
|
||||
function _merge_action() {
|
||||
enote "\
|
||||
Ce script va
|
||||
- fusionner la branche ${COULEUR_BLEUE}${!SRC_BRANCH}${COULEUR_NORMALE} dans ${COULEUR_ROUGE}${!DEST_BRANCH}${COULEUR_NORMALE}${Push:+
|
||||
- pousser les branches modifiées}"
|
||||
ask_yesno "Voulez-vous continuer?" O || die
|
||||
|
||||
local script=".git/pman-merge.sh"
|
||||
local -a push_branches delete_branches
|
||||
local hook
|
||||
local comment=
|
||||
local or_die=" || exit 1"
|
||||
|
||||
_mscript_start
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# merge
|
||||
if [ -n "\$merge" ]; then
|
||||
esection "Fusionner la branche"
|
||||
EOF
|
||||
hook="BEFORE_MERGE_$SRC_TYPE"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_mscript_merge_branch
|
||||
hook="AFTER_MERGE_$SRC_TYPE"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
if [ -n "$ShouldDelete" ]; then
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# delete
|
||||
if [ -n "\$delete" ]; then
|
||||
esection "Supprimer la branche"
|
||||
EOF
|
||||
_mscript_delete_branch
|
||||
hook="AFTER_DELETE_$SRC_TYPE"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
fi
|
||||
|
||||
_scripta <<EOF
|
||||
################################################################################
|
||||
# push
|
||||
if [ -n "\$push" ]; then
|
||||
esection "Pousser les branches"
|
||||
EOF
|
||||
hook="BEFORE_PUSH_$DEST_TYPE"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_script_push_branches
|
||||
if [ ${#delete_branches[*]} -gt 0 ]; then
|
||||
_scripta <<<"if [ -n \"\$delete\" ]; then"
|
||||
push_branches=("${delete_branches[@]}")
|
||||
_script_push_branches
|
||||
_scripta <<<fi
|
||||
fi
|
||||
hook="AFTER_PUSH_$DEST_TYPE"; [ -n "${!hook}" ] && _scripta <<EOF
|
||||
(
|
||||
${!hook}
|
||||
)$or_die
|
||||
EOF
|
||||
_scripta <<EOF
|
||||
fi
|
||||
EOF
|
||||
|
||||
[ -n "$Delete" -o -z "$ShouldDelete" ] && Deleted=1 || Deleted=
|
||||
[ -n "$ShouldDelete" -a -n "$Delete" ] && ShouldDelete=
|
||||
[ -n "$ShouldPush" -a -n "$Push" ] && ShouldPush=
|
||||
if [ -n "$_Fake" ]; then
|
||||
cat "$script"
|
||||
elif ! "$script" merge ${Delete:+delete} ${Push:+push}; then
|
||||
eimportant "\
|
||||
Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
||||
En cas d'erreur de merge, veuillez corriger les erreurs puis continuer avec
|
||||
git merge --continue
|
||||
Sinon, veuillez consulter le script et/ou le relancer
|
||||
./$script${Delete:+ delete}${Push:+ push}"
|
||||
die
|
||||
elif [ -n "$Deleted" -a -n "$Push" ]; then
|
||||
[ -n "$_KeepScript" ] || rm "$script"
|
||||
[ -n "$AfterMerge" ] && eval "$AfterMerge"
|
||||
else
|
||||
local msg="\
|
||||
Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
|
||||
Vous pouvez consulter le script et/ou le relancer
|
||||
./$script${ShouldDelete:+ delete}${ShouldPush:+ push}"
|
||||
[ -n "$AfterMerge" ] && msg="$msg
|
||||
Il y a aussi les commandes supplémentaires suivantes:
|
||||
${AfterMerge//
|
||||
/
|
||||
}"
|
||||
einfo "$msg"
|
||||
fi
|
||||
}
|
||||
function merge_action() {
|
||||
if [ -n "$MERGE_PREL" ]; then
|
||||
[ -n "$ForceMerge" ] || die "$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel"
|
||||
fi
|
||||
[ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$SrcBranch"
|
||||
[ -n "$ALLOW_DELETE" ] || Delete=
|
||||
[ -z "$_Fake" ] && git_ensure_cleancheckout
|
||||
if array_contains LocalBranches "$SrcBranch"; then
|
||||
ensure_branches
|
||||
resolve_should_push
|
||||
_merge_action "$@"
|
||||
elif array_contains AllBranches "$SrcBranch"; then
|
||||
enote "$SrcBranch: une branche du même nom existe dans l'origine"
|
||||
die "$SrcBranch: branche locale introuvable"
|
||||
else
|
||||
die "$SrcBranch: branche introuvable"
|
||||
fi
|
||||
}
|
||||
|
||||
function rebase_action() {
|
||||
die "non implémenté"
|
||||
}
|
||||
|
||||
|
||||
################################################################################
|
||||
# Programme principal
|
||||
################################################################################
|
||||
|
||||
loaded_config=
|
||||
if check_gitdir; then
|
||||
load_branches all
|
||||
load_config
|
||||
set_pman_vars
|
||||
if [ -n "$PmanMergeSrc" ]; then
|
||||
load_branches current "$PmanMergeSrc"
|
||||
loaded_config=1
|
||||
elif [ -n "$PmanRefBranch" ]; then
|
||||
load_branches current "$PmanRefBranch"
|
||||
load_config "$PMAN_TOOL"
|
||||
if [ -n "${!SRC_TYPE}" ]; then
|
||||
load_branches current "${!SRC_TYPE}"
|
||||
loaded_config=1
|
||||
fi
|
||||
else
|
||||
set_pman_vars
|
||||
fi
|
||||
[ -n "${!SRC_TYPE}" ] && SrcDesc="${!SRC_TYPE}" || SrcDesc="<$SRC_TYPE>"
|
||||
SrcDesc="${COULEUR_BLEUE}$SrcDesc${COULEUR_NORMALE}"
|
||||
DestDesc=
|
||||
|
||||
BranchDesc=
|
||||
MergeSrcDesc=
|
||||
MergeDestDesc=
|
||||
if [ -n "$PMAN_REF_BRANCH" ]; then
|
||||
BranchDesc="${COULEUR_BLANCHE}<$PMAN_REF_BRANCH>"
|
||||
[ -n "$PmanRefBranch" -a -n "$PMAN_UNIQUE" ] && BranchDesc="$BranchDesc ($PmanRefBranch)"
|
||||
BranchDesc="$BranchDesc${COULEUR_NORMALE}"
|
||||
purpose="gérer la branche $SrcDesc"
|
||||
if [ -n "$DEST_TYPE" ]; then
|
||||
[ -n "${!DEST_TYPE}" ] && DestDesc="${!DEST_TYPE}" || DestDesc="<$DEST_TYPE>"
|
||||
DestDesc="${COULEUR_ROUGE}$DestDesc${COULEUR_NORMALE}"
|
||||
purpose="${purpose} et sa fusion dans $DestDesc"
|
||||
fi
|
||||
if [ -n "$PMAN_MERGE_SRC" ]; then
|
||||
MergeSrcDesc="${COULEUR_BLEUE}<$PMAN_MERGE_SRC>"
|
||||
[ -n "$PmanMergeSrc" -a -n "$PMAN_UNIQUE" ] && MergeSrcDesc="$MergeSrcDesc ($PmanMergeSrc)"
|
||||
MergeSrcDesc="$MergeSrcDesc${COULEUR_NORMALE}"
|
||||
fi
|
||||
if [ -n "$PMAN_MERGE_DEST" ]; then
|
||||
MergeDestDesc="${COULEUR_ROUGE}<$PMAN_MERGE_DEST>"
|
||||
[ -n "$PmanMergeDest" -a -n "$PMAN_UNIQUE" ] && MergeDestDesc="$MergeDestDesc ($PmanMergeDest)"
|
||||
MergeDestDesc="$MergeDestDesc${COULEUR_NORMALE}"
|
||||
fi
|
||||
|
||||
[ -n "$PMAN_UNIQUE" ] &&
|
||||
purpose="gérer la branche $BranchDesc" ||
|
||||
purpose="gérer les branches $BranchDesc"
|
||||
usage="--checkout"
|
||||
variables=
|
||||
args=(
|
||||
-d:,--chdir:BASEDIR chdir= "\
|
||||
répertoire dans lequel se placer avant de lancer les opérations"
|
||||
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||
-O:,--origin Origin= "++\
|
||||
origine à partir de laquelle les branches distantes sont considérées"
|
||||
-B:,--config-branch ConfigBranch= "++\
|
||||
branche à partir de laquelle charger la configuration"
|
||||
-c:,--config-file:CONFIG ConfigFile= "++\
|
||||
fichier de configuration des branches. le fichier .pman.conf dans le répertoire
|
||||
du dépôt est utilisé par défaut s'il existe. cette option est prioritaire sur
|
||||
--config-branch"
|
||||
fichier de configuration des branches. cette option est prioritaire sur --config-branch
|
||||
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"
|
||||
--keep-script _KeepScript=1 "++option non documentée"
|
||||
--dump action=dump "++afficher les noms des branches"
|
||||
)
|
||||
if [ -n "$PmanRefBranch" -a -n "$PMAN_UNIQUE" ]; then
|
||||
args+=(
|
||||
--checkout action=checkout "++\
|
||||
créer le cas échéant la branche $BranchDesc et basculer vers elle.
|
||||
c'est l'option par défaut"
|
||||
)
|
||||
elif [ -z "$PMAN_UNIQUE" ]; then
|
||||
args+=(
|
||||
--checkout action=checkout "\
|
||||
créer le cas échéant la branche $BranchDesc et basculer vers elle.
|
||||
c'est l'option par défaut"
|
||||
)
|
||||
else
|
||||
args+=(
|
||||
--checkout action=checkout "\
|
||||
créer la branche $MergeDestDesc et basculer vers elle.
|
||||
c'est l'option par défaut"
|
||||
)
|
||||
fi
|
||||
|
||||
if [ -n "$PMAN_CAN_MERGE" ]; then
|
||||
if [ -n "$PMAN_UNIQUE" ]; then
|
||||
usage="${usage}|--show|--merge"
|
||||
else
|
||||
usage="${usage} $PMAN_REF_BRANCH
|
||||
--show|--merge"
|
||||
fi
|
||||
if [ "$PMAN_REF_BRANCH" != "$PMAN_MERGE_SRC" ]; then
|
||||
usage="$usage
|
||||
|
||||
NB: du fait de la configuration des branches, la fusion se fait dans le sens
|
||||
inverse $MergeSrcDesc --> $MergeDestDesc"
|
||||
else
|
||||
usage="$usage
|
||||
|
||||
NB: La fusion se fait dans le sens $MergeSrcDesc --> $MergeDestDesc"
|
||||
fi
|
||||
--checkout action=checkout "++\
|
||||
basculer sur la branche $SrcDesc. c'est l'action par défaut"
|
||||
)
|
||||
if [ -n "$ALLOW_MERGE" ]; then
|
||||
usage="${usage}|--show|--merge"
|
||||
variables="\
|
||||
Les variables supplémentaires suivantes peuvent être définies:
|
||||
BEFORE_MERGE_${PMAN_MERGE_SRC}
|
||||
AFTER_MERGE_${PMAN_MERGE_SRC}"
|
||||
BEFORE_MERGE_${SRC_TYPE}
|
||||
AFTER_MERGE_${SRC_TYPE}"
|
||||
args+=(
|
||||
-w,--show '$action=show; inc@ ShowLevel' "\
|
||||
lister ce qui serait fusionné dans la branche $MergeDestDesc"
|
||||
# -b,--rebase action=rebase "\
|
||||
#lancer git rebase -i sur la branche $MergeSrcDesc. cela permet de réordonner
|
||||
#les commits pour nettoyer l'historique avant la fusion"
|
||||
-m,--merge action=merge "\
|
||||
fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc"
|
||||
lister les modifications qui seraient fusionnées dans la branche $DestDesc"
|
||||
-b,--rebase action=rebase "\
|
||||
lancer git rebase -i sur la branche $SrcDesc. cela permet de réordonner les
|
||||
commits pour nettoyer l'historique avant la fusion"
|
||||
-m,--merge action=merge "++\
|
||||
fusionner la branche $SrcDesc dans la branche $DestDesc"
|
||||
--tech-merge TechMerge=1 "++option non documentée"
|
||||
-s:,--squash:COMMIT_MSG SquashMsg= "\
|
||||
fusionner les modifications de la branche comme un seul commit"
|
||||
)
|
||||
if [ -n "$PMAN_PREL_MERGE" ]; then
|
||||
if [ -n "$MERGE_PREL" ]; then
|
||||
args+=(
|
||||
-f,--force-merge ForceMerge=1 "++\
|
||||
forcer la fusion pour une branche qui devrait être traitée par prel"
|
||||
@ -124,23 +305,22 @@ ne pas pousser les branches vers leur origine après la fusion"
|
||||
pousser les branches vers leur origine après la fusion.
|
||||
c'est l'option par défaut"
|
||||
)
|
||||
if [ -n "$PMAN_DELETE_MERGED" ]; then
|
||||
if [ -n "$ALLOW_DELETE" ]; then
|
||||
variables="${variables}
|
||||
AFTER_DELETE_${PMAN_MERGE_SRC}"
|
||||
AFTER_DELETE_${SRC_TYPE}}"
|
||||
args+=(
|
||||
-k,--no-delete Delete= "\
|
||||
ne pas supprimer la branche $MergeSrcDesc après la fusion dans la branche
|
||||
$MergeDestDesc. cette option ne devrait pas être utilisée avec --squash"
|
||||
ne pas supprimer la branche $SrcDesc après la fusion dans la branche $DestDesc
|
||||
cette option ne devrait pas être utilisée avec --squash"
|
||||
--delete Delete=1 "++\
|
||||
supprimer la branche $MergeSrcDesc après la fusion dans la branche
|
||||
$MergeDestDesc.
|
||||
supprimer la branche $SrcDesc après la fusion dans la branche $DestDesc
|
||||
c'est l'option par défaut"
|
||||
)
|
||||
fi
|
||||
if [ -n "$PMAN_MERGE_DEST" ]; then
|
||||
if [ -n "$DEST_TYPE" ]; then
|
||||
variables="${variables}
|
||||
BEFORE_PUSH_${PMAN_MERGE_DEST}
|
||||
AFTER_PUSH_${PMAN_MERGE_DEST}"
|
||||
BEFORE_PUSH_${DEST_TYPE}
|
||||
AFTER_PUSH_${DEST_TYPE}"
|
||||
fi
|
||||
args+=(
|
||||
-a:,--after-merge AfterMerge= "\
|
||||
@ -148,6 +328,7 @@ c'est l'option par défaut"
|
||||
)
|
||||
fi
|
||||
|
||||
|
||||
chdir=
|
||||
Origin=
|
||||
ConfigBranch=
|
||||
@ -158,8 +339,8 @@ action=checkout
|
||||
ShowLevel=0
|
||||
TechMerge=
|
||||
SquashMsg=
|
||||
Push=1
|
||||
Delete=1
|
||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||
[ -z "$PMAN_NO_DELETE" ] && Delete=1 || Delete=
|
||||
AfterMerge=
|
||||
args=(
|
||||
"$purpose"
|
||||
@ -178,14 +359,14 @@ if [ -z "$loaded_config" -o -n "$chdir" -o -n "$ConfigFile" -o -n "$ConfigBranch
|
||||
# charger la configuration
|
||||
ensure_gitdir "$chdir"
|
||||
load_branches all
|
||||
load_config
|
||||
set_pman_vars
|
||||
if [ -n "$PmanMergeSrc" ]; then
|
||||
load_branches current "$PmanMergeSrc"
|
||||
elif [ -n "$PmanRefBranch" ]; then
|
||||
load_branches current "$PmanRefBranch"
|
||||
load_config "$PMAN_TOOL"
|
||||
if [ -n "${!SRC_TYPE}" ]; then
|
||||
load_branches current "${!SRC_TYPE}"
|
||||
fi
|
||||
fi
|
||||
resolve_should_push quiet
|
||||
|
||||
[ -n "${!SRC_TYPE}" ] ||
|
||||
die "Aucune branche définie pour $SRC_TYPE. Veuillez éditer le fichier .pman.conf"
|
||||
|
||||
"${action}_action" "$@"
|
||||
|
191
wip/pinit
191
wip/pinit
@ -1,191 +0,0 @@
|
||||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
source "$(dirname -- "$0")/../load.sh" || exit 1
|
||||
require: git pman pman.conf
|
||||
|
||||
################################################################################
|
||||
# Informations
|
||||
################################################################################
|
||||
|
||||
SHOW_VARS=(
|
||||
--Configuration
|
||||
"${CONFIG_VARS[@]}"
|
||||
--Paramètres
|
||||
CurrentBranch
|
||||
CurrentType=SrcType
|
||||
)
|
||||
|
||||
function show_action() {
|
||||
local var src
|
||||
echo_setv ConfigBranch="$ConfigBranch"
|
||||
echo_setv ConfigFile="$(ppath "$ConfigFile")"
|
||||
for var in "${SHOW_VARS[@]}"; do
|
||||
if [ "${var#--}" != "$var" ]; then
|
||||
estep "${var#--}"
|
||||
else
|
||||
splitfsep "$var" = var src
|
||||
[ -n "$src" ] || src="$var"
|
||||
echo_setv "$var=${!src}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Initialisation
|
||||
################################################################################
|
||||
|
||||
function _init_config() {
|
||||
if [ ! -f .pman.conf -o -n "$ForceCreate" ]; then
|
||||
ac_set_tmpfile config
|
||||
cp "$ConfigFile" "$config"
|
||||
"${EDITOR:-nano}" "$config"
|
||||
[ -s "$config" ] || return 1
|
||||
|
||||
cp "$config" .pman.conf
|
||||
if testdiff .pman.conf "$ConfigFile"; then
|
||||
ConfigFile="$(pwd)/.pman.conf"
|
||||
load_config
|
||||
load_branches current "$SrcBranch"
|
||||
fi
|
||||
git add .pman.conf
|
||||
fi
|
||||
if [ ! -f ".gitignore" ]; then
|
||||
echo >.gitignore "\
|
||||
.~lock*#
|
||||
.*.swp"
|
||||
git add .gitignore
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function init_repo_action() {
|
||||
local -a push_branches; local config
|
||||
|
||||
[ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé"
|
||||
|
||||
_init_config || exit_with ewarn "Initialisation du dépôt annulée"
|
||||
|
||||
einfo "Création de la branche $MAIN"
|
||||
git symbolic-ref HEAD "refs/heads/$MAIN"
|
||||
git commit -m "commit initial"
|
||||
push_branches+=("$MAIN")
|
||||
|
||||
einfo "Création de la branche $DEVELOP"
|
||||
git checkout -b "$DEVELOP"
|
||||
push_branches+=("$DEVELOP")
|
||||
|
||||
_push_branches
|
||||
}
|
||||
|
||||
function init_config_action() {
|
||||
local -a push_branches; local config
|
||||
|
||||
[ -f .pman.conf -a -z "$ForceCreate" ] && die "La configuration pman a déjà été initialisée"
|
||||
|
||||
resolve_should_push
|
||||
|
||||
_init_config || exit_with ewarn "Initialisation de la configuration annulée"
|
||||
git commit -m "configuration pman"
|
||||
push_branches+=("$CurrentBranch")
|
||||
|
||||
_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 init_action() {
|
||||
local what="${1:-repo}"; shift
|
||||
case "$what" in
|
||||
repo|r) init_repo_action "$@";;
|
||||
config|c) init_config_action "$@";;
|
||||
composer|o) init_composer_action "$@";;
|
||||
*) die "$what: destination non implémentée"
|
||||
esac
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Programme principal
|
||||
################################################################################
|
||||
|
||||
chdir=
|
||||
ConfigFile=
|
||||
action=init
|
||||
Origin=
|
||||
Push=1
|
||||
ForceCreate=
|
||||
args=(
|
||||
"initialiser un dépôt git"
|
||||
"repo|config|composer|all"
|
||||
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||
-c:,--config-file:CONFIG ConfigFile= "++\
|
||||
fichier de configuration des branches. cette option est prioritaire sur --config-branch
|
||||
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||
-w,--show-config action=show "++\
|
||||
afficher la configuration chargée"
|
||||
-O:,--origin Origin= "++\
|
||||
origine vers laquelle pousser les branches"
|
||||
-n,--no-push Push= "\
|
||||
ne pas pousser les branches vers leur origine après leur création"
|
||||
--push Push=1 "++\
|
||||
pousser les branches vers leur origine après leur création.
|
||||
c'est l'option par défaut"
|
||||
-f,--force-create ForceCreate=1 "\
|
||||
Avec config, forcer la (re)création du fichier .pman.conf"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
|
||||
# charger la configuration
|
||||
ensure_gitdir "$chdir"
|
||||
load_branches all
|
||||
load_config
|
||||
load_branches current
|
||||
|
||||
# puis faire l'action que l'on nous demande
|
||||
case "$action" in
|
||||
show) show_action "$@";;
|
||||
init)
|
||||
git_ensure_cleancheckout
|
||||
init_action "$@"
|
||||
;;
|
||||
*) die "$action: action non implémentée";;
|
||||
esac
|
60
wip/pwip
Executable file
60
wip/pwip
Executable file
@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
source "$(dirname -- "$0")/../load.sh" || exit 1
|
||||
require: git pman pman.conf
|
||||
|
||||
git_cleancheckout_DIRTY="\
|
||||
Vous avez des modifications locales.
|
||||
Enregistrez ces modifications avant de créer une nouvelle branche"
|
||||
|
||||
chdir=
|
||||
Origin=
|
||||
ConfigBranch=
|
||||
ConfigFile=
|
||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||
args=(
|
||||
"créer une branche de feature"
|
||||
"<feature>"
|
||||
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
|
||||
-O:,--origin Origin= "++\
|
||||
origine à partir de laquelle les branches distantes sont considérées"
|
||||
-B:,--config-branch ConfigBranch= "++\
|
||||
branche à partir de laquelle charger la configuration"
|
||||
-c:,--config-file:CONFIG ConfigFile= "++\
|
||||
fichier de configuration des branches. cette option est prioritaire sur --config-branch
|
||||
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
|
||||
-n,--no-push Push= "\
|
||||
ne pas pousser les branches vers leur origine après la fusion"
|
||||
--push Push=1 "++\
|
||||
pousser les branches vers leur origine après la fusion.
|
||||
c'est l'option par défaut"
|
||||
)
|
||||
parse_args "$@"; set -- "${args[@]}"
|
||||
|
||||
# charger la configuration
|
||||
ensure_gitdir "$chdir"
|
||||
load_branches all
|
||||
load_config "$MYNAME"
|
||||
load_branches current
|
||||
|
||||
branch="$1"
|
||||
if [ -z "$branch" -a ${#FeatureBranches[*]} -eq 1 ]; then
|
||||
branch="${FeatureBranches[0]}"
|
||||
fi
|
||||
[ -n "$branch" ] || die "Vous devez spécifier la branche à créer"
|
||||
branch="$FEATURE${branch#$FEATURE}"
|
||||
|
||||
resolve_should_push
|
||||
git_ensure_cleancheckout
|
||||
|
||||
if array_contains AllBranches "$branch"; then
|
||||
git checkout -q "$branch"
|
||||
else
|
||||
# si la branche source n'existe pas, la créer
|
||||
args=(--origin "$Origin")
|
||||
if [ -n "$ConfigFile" ]; then args+=(--config-file "$ConfigFile")
|
||||
elif [ -n "$ConfigBranch" ]; then args+=(--config-branch "$ConfigBranch")
|
||||
fi
|
||||
[ -z "$Push" ] && args+=(--no-push)
|
||||
exec "$MYDIR/pman" "${args[@]}" "$branch"
|
||||
fi
|
Loading…
x
Reference in New Issue
Block a user