Skip to content
Snippets Groups Projects
Commit 57cfe806 authored by Florentin L'Homme's avatar Florentin L'Homme
Browse files

Début refonte de la documentation (en (r)accord avec le nouveau fonctionnement d'UnicaenDbImport)

parent a29d19e2
Branches version_2
No related tags found
No related merge requests found
UnicaenDbImport
===============
Ce module a pour but de réaliser l'import de données d'une table (ou d'un select) d'une base de données source vers une
table d'une base de données destination.
Concrètement :
- Si la source contient un enregistrement qui n'existe pas dans la destination, il est ajouté dans cette dernière.
- Si la source contient un enregistrement qui existe aussi dans la destination avec les mêmes valeurs de colonnes,
rien n'est fait.
- Si la source contient un enregistrement qui existe aussi dans la destination mais avec des valeurs de colonnes
différentes, les colonnes de la destination sont mises à jour en conséquence.
- Si la source ne contient pas un enregistrement qui existe dans la destination, l'enregistrement destination est historisé
(i.e. marqué "supprimé").
- Si la source contient un enregistrement qui existe aussi dans la destination mais marqué "supprimé", ce dernier est
dé-historisé.
Les enregistrements source et les enregistrements destination doivent avoir un identifiant unique en commun permettant
de les rapprocher : on l'appellera "code source" (cf. paramètre de config `source_code_column`).
**Cet identifiant doit être de type chaîne de caractères.**
La différence avec le module UnicaenImport ?
--------------------------------------------
Comme son nom ne l'indique pas, UnicaenImport fonctionne uniquement entre
2 bases de données **Oracle** ; et la synchronisation est faite entièrement par le SGBD (ou presque).
UnicaenDbImport fonctionne en majeure partie en PHP tout en déléguant au maximum au SGBD ce qu'il sait bien faire
(`select FULL OUTER JOIN` pour le différentiel entre données source et destination, fonction pour l'alimentation du registre
d'import, etc.) Par conséquent, il est possible et souhaitable de l'enrichir pour implémenter une synchronisation vers
différentes plateformes de base de données destination. Les plateformes de base de données destination suivantes sont
implémentées :
- PostgreSQL
*NB: En revanche toutes les plateformes de base de données source sont supportées car on se contente d'y faire un select
à l'aide de l'ORM Doctrine 2 pour alimenter une table intermédiaire.*
Installation
------------
composer require unicaen/unicaen-db-import
Configuration
-------------
- Récupérer les fichiers config de distribution :
```bash
cp -n vendor/unicaen/db-import/config/unicaen-db-import.global.php.dist config/autoload/unicaen-db-import.global.php
cp -n vendor/unicaen/db-import/config/unicaen-db-import.local.php.dist config/autoload/unicaen-db-import.local.php
```
- Adapter leur contenu à vos besoins.
Utilisation
-----------
Le module fournit une ligne de commande pour :
- lancer un import par son nom, exemple :
```bash
php public/index.php run import --name "IMPORT_PFI_SIFAC"
```
- lancer tous les imports :
```bash
php public/index.php run import --all
```
*NB: Cette commande exécute la synchronisation une fois. Pour synchroniser en permanence la base de données destination,
il faut programmer le lancement périodique de cette commande à l'aide de CRON (cf. plus bas pour un exemple).*
Contraintes
-----------
### Identifiant commun
L'identifiant unique commun des enregistrements source et destination doit être de type **chaîne de caractères**.
### Source de type `'select'`
Dans le cas d'une *source* de type `'select'`, il est nécessaire de convertir chaque colonne en
**chaîne de caractères** dans le `select`.
- Pour une colonne de type "date", `TO_CHAR(nom_colonne,'YYYY-MM-DD')` permet de convertir
en chaînes de caractères dans un format quasi universel ;
- Pour une colonne de type numérique, `nom_colonne||''` permet par exemple de convertir
entiers et décimaux en chaînes de caractères.
Il est également nécessaire de spécifier dans la config de la *destination* la fonction de conversion à appliquer
à chaque colonne pour la convertir en chaîne de caractères (cf. paramètre de config destination `columns_to_char`).
### Source de type `'table'`
Dans le cas d'une *source* de type `'table'`, il est nécessaire de spécifier dans la config de la
*destination* la fonction de conversion à appliquer à chaque colonne pour la convertir en chaîne de caractères
(cf. paramètre de config destination `columns_to_char`).
### Table destination
La *table destination* doit obligatoirement posséder les colonnes d'historique `created_on`, `updated_on`
et `deleted_on`. Exemple avec PostgreSQL :
```sql
ALTER TABLE TABLE_DESTINATION ADD COLUMN created_on TIMESTAMP(0) WITH TIME ZONE DEFAULT LOCALTIMESTAMP(0) NOT NULL;
ALTER TABLE TABLE_DESTINATION ADD COLUMN updated_on TIMESTAMP(0) WITH TIME ZONE;
ALTER TABLE TABLE_DESTINATION ADD COLUMN deleted_on TIMESTAMP(0) WITH TIME ZONE;
```
Exemple de mise en oeuvre
-------------------------
Import des programmes de financement SIFAC depuis une base Oracle vers une table PostgreSQL. Les données sources sont
"extraites" grâce à un select.
- Configuration globale du module :
`unicaen-db-import.global.php`
```php
return [
/**
* Configuration du module UnicaenDbImport.
*/
'import' => [
/**
* Liste des imports.
*/
'imports' => [
[
/**
* Petit nom (unique) de l'import.
*/
'name' => 'Import_PFI_SIFAC',
/**
* Configuration de la source de données à importer :
* - 'name' : petit nom (unique) de la source
* - 'table' : nom de la table source contenant les données à importer
* - 'select' : select SQL de mise en forme des données source à importer (NB: antinomique avec 'table')
* - 'connection' : identifiant de la connexion Doctrine à la base de données source
* - 'source_code_column' : nom de la colonne dans la table/vue source contenant l'identifiant unique
* - 'columns' : liste ordonnée des noms des colonnes à prendre en compte dans la table/vue source
*/
'source' => [
'name' => 'PFI SIFAC',
'connection' => 'doctrine.connection.orm_oracle',
'source_code_column' => 'CODE',
'columns' => ['LIBELLE', 'FLECHE', 'DEBUT_VALIDITE', 'FIN_VALIDITE'],
'select' => <<<'EOT'
select distinct
MEASURE CODE,
SHORT_DESC LIBELLE,
ZFLECHE FLECHE,
to_char(TO_DATE(VALID_FROM,'YYYYMMDD'),'YYYY-MM-DD') DEBUT_VALIDITE,
to_char(TO_DATE(VALID_TO,'YYYYMMDD'),'YYYY-MM-DD') FIN_VALIDITE
from (
SELECT
SAPSR3.FMMEASURE.MEASURE,
SAPSR3.FMMEASURET.SHORT_DESC,
SAPSR3.FMMEASURE.ZFLECHE,
SAPSR3.FMMEASURE.VALID_FROM,
SAPSR3.FMMEASURE.VALID_TO
FROM
SAPSR3.FMMEASURE,
SAPSR3.FMMEASURET
WHERE
( (SAPSR3.FMMEASURE.CLIENT in (select A.MANDT from SAPSR3.FM01H A where A.MANDT = '500') OR SAPSR3.FMMEASURE.CLIENT is null)
and (SAPSR3.FMMEASURE.FMAREA in (select A.FIKRS from SAPSR3.FM01H A where A.FIKRS = '1010') or SAPSR3.FMMEASURE.FMAREA is null) )
AND ( SAPSR3.FMMEASURE.CLIENT=SAPSR3.FMMEASURET.CLIENT(+) )
AND ( SAPSR3.FMMEASURE.FMAREA=SAPSR3.FMMEASURET.FMAREA(+) )
AND ( SAPSR3.FMMEASURE.MEASURE=SAPSR3.FMMEASURET.MEASURE(+) )
)
EOT
,
],
/**
* Forçage éventuel du nom de la table intermédiaire utilisée lorsque source et destination
* ne partagent pas la même connexion. NB: cette table intermédiaire est créée/peuplée/supprimée
* dans la base de données destination à chaque import.
* En l'absence de ce forçage, le nom de la table intermédiaire sera celui de la table destination
* préfixé par "src_".
*/
'intermediate_table' => 'src_progfin',
/**
* Suppression automatique de la table intermédiaire "src_" au début de l'import.
* Si la suppression automatique est désactivée, l'existence de la table intermédiaire au démarrage
* de l'import fera échouer l'import.
*/
'intermediate_table_auto_drop' => false,
/**
* Configuration de la destination des données importées :
* - 'name' : petit nom (unique) de la destination
* - 'table' : nom de la table destination vers laquelle les données sont importées
* - 'connection' : identifiant de la connexion Doctrine à la base de données destination
* - 'source_code_column' : nom de la colonne dans la table destination contenant l'identifiant unique
* - 'columns' : liste ordonnée des noms des colonnes importées dans la table destination
* - 'columns_to_char' : format sprintf nécessaire pour mettre des colonnes au format requis (string)
*/
'destination' => [
'name' => 'PFI Zébu',
'table' => 'progfin',
'connection' => 'doctrine.connection.orm_default',
'source_code_column' => 'code',
'columns' => ['libelle', 'fleche', 'debut_validite', 'fin_validite'],
'columns_to_char' => [
'debut_validite' => "TO_CHAR(%s,'YYYY-MM-DD')", // car colonne destination de type TIMESTAMP
'fin_validite' => "TO_CHAR(%s,'YYYY-MM-DD')", // idem
],
],
],
],
],
];
```
*On doit renseigner le paramètre `intermediate_table` car les données source proviennent d'un select.*
- Configuration locale du module :
`unicaen-db-import.local.php`
```php
return [
/**
* Configuration Doctrine minimum requise.
*/
'doctrine' => [
'connection' => [
'orm_oracle' => [
'driverClass' => 'Doctrine\\DBAL\\Driver\\OCI8\\Driver',
'params' => [
'host' => 'host.domain.fr',
'port' => '1525',
'user' => 'x',
'password' => 'y',
'dbname' => 'z',
'charset' => 'AL32UTF8',
],
'eventmanager' => 'orm_oracle',
],
'orm_default' => [
'driverClass' => 'Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver',
'params' => [
'host' => 'host.domain.fr',
'port' => '5432',
'charset' => 'utf8',
'dbname' => 'a',
'user' => 'b',
'password' => 'c',
],
],
],
'eventmanager' => [
'orm_sifac' => [
'subscribers' => [
\Doctrine\DBAL\Event\Listeners\OracleSessionInit::class,
],
],
],
],
];
```
*NB: les paramètres de config de la connexion `orm_default` existent sans doute déjà dans la configuration de votre
application, il est bien-sûr inutile de les répéter dans la config du module unicaen/db-import.*
- Configuration CRON pour lancer périodiquement la synchro :
```cron
# Du lundi au vendredi, entre 6h00 et 19h45, toutes les 15 minutes
*/15 6-19 * * 1-5 root /usr/bin/php /var/www/zebu-back/public/index.php run import --all 1> /tmp/zebu-cron.log 2>&1
```
- Exemple de logs affichés par la commande :
```
######################## IMPORTS ########################
### Import des PFI SIFAC ###
05/09/2017 16:22:08
# delete :
UPDATE progfin SET deleted_on = LOCALTIMESTAMP(0) WHERE CODE = 'fddsfsd' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'fddsfsd-99c4b652-9245-11e7-bddd-0242f725575b' ;
(1 instructions exécutées)
# insert :
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('971UP009', 'Of Wisco', ' ', '2014-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '971UP009-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('977CA025', 'Screening de 5', ' ', '2016-07-21', '2018-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '977CA025-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('F950MEC1', 'MDE CAEN FONCTIONNEM', ' ', '2015-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'F950MEC1-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('F971CF03', 'UNIVERSITE DU HAVRE', ' ', '2015-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'F971CF03-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('014DU009', 'DU RDCM', ' ', '2016-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '014DU009-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('012RE001', 'COLLOQUE 50 ANS SC E', ' ', '2017-01-01', '2017-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '012RE001-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('012CH021', 'Typo-chronologie', ' ', '2017-01-01', '2017-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '012CH021-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('013PL002', 'CMABIO', ' ', '2016-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '013PL002-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('999CM003', '2016-AGRI-133', ' ', '2016-03-30', '2018-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '999CM003-99c4b652-9245-11e7-bddd-0242f725575b' ;
(9 instructions exécutées)
# undelete :
UPDATE progfin SET LIBELLE = 'TA 2017', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = '913TA017' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '913TA017-99c4b652-9245-11e7-bddd-0242f725575b' ;
UPDATE progfin SET LIBELLE = 'TA 2017', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = '920TA017' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '920TA017-99c4b652-9245-11e7-bddd-0242f725575b' ;
UPDATE progfin SET LIBELLE = 'PAIE DIU ADOLESCENTS', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = 'P14DU910' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'P14DU910-99c4b652-9245-11e7-bddd-0242f725575b' ;
(3 instructions exécutées)
# update :
UPDATE progfin SET LIBELLE = 'PLATIN''', updated_on = LOCALTIMESTAMP(0) WHERE CODE = '925PL005' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '925PL005-PLATINvxcvxc-99c4b652-9245-11e7-bddd-0242f725575b' ;
(1 instructions exécutées)
Import réalisé avec succès.
```
Dans le moteur
--------------
### Table intermédiaire `SRC_*`
Lorsque les données source sont issues d'un select, une table intermédiaire `SRC_*` est créée dans la base de données
destination à partir de ces données source. La synchronisation est ensuite réalisée entre cette table intermdiaire et
la table destination. À la fin du processus de synchronisation, cette table intermdiaire est supprimée.
*NB: attention, si au lancement de la commande d'import la table intermédiaire `SRC_*` existe déjà dans la base de
données ete que le paramètre de config `intermediate_table_auto_drop` est à `false`, l'import échouera.*
### Différentiel entre source et destination
La requête utilisée pour comparer les données sources et destination pour une table destination `progfin` ressemble
à cela (syntaxe PostgreSQL) :
```sql
SELECT create_import_metarequest_for_progfin(
src.code, src.libelle, TO_CHAR(src.debut_validite,'YYYY-MM-DD'),
dest.code, dest.libelle, TO_CHAR(dest.debut_validite,'YYYY-MM-DD'), dest.deleted_on,
'eb1ab85c-916a-11e7-aba7-0242f725575b'
) AS operation
FROM src_progfin src
FULL OUTER JOIN progfin dest ON src.code = dest.code
;
```
### Fonction `create_import_metarequest_for_*`
Le module crée automatiquement dans le SGBD pour chaque import configuré une fonction `create_import_metarequest_for_*`
chargée de :
- déterminer l'opération de mise à jour nécessaire à partir du résultat du différentiel entre données source et
destination ;
- inscrire dans un "registre d'import" (table `IMPORT_REG`) les instructions SQL nécessaires à la mise à jour de
la table destination.
Vous trouverez dans [ce fichier](test/UnicaenDbImportUnitTest/CodeGenerator/PostgreSQL/Helper/expected_function_creation_sql.sql)
un exemple de fonction `create_import_metarequest_for_*` pour une table destination `ztemptable` dans une base PostgreSQL.
*NB: Pour un enregistrement existant à la fois dans la source et la destination (opération "update"), la détection de
différence et donc sa mise à jour se fait colonne par colonne.*
### Table `IMPORT_REG`, registre d'import
Une table de travail `import_reg` est utilisée par le module pour y inscrire les opérations nécessaires à chaque
synchronisation.
Ce registre comporte les colonnes suivantes :
- le type d'opération nécessaire : insert, update, delete, undelete
- le "source code" de l'enregistrement concerné
- le nom de la table concernée
- le nom de la colonne concernée (si c'est pertinent pour l'opération)
- la valeur de la colonne avant synchronisation (si c'est pertinent pour l'opération)
- la valeur de la colonne après synchronisation (si c'est pertinent pour l'opération)
- l'instruction SQL à exécuter pour mettre à jour la table destination
- la date d'exécution de l'instruction SQL de mise à jour de la table destination
- la date de création de la ligne de registre
- un hash permettant d'identifier l'ensemble de lignes auquel appartient cette ligne de registre
(utile pour mettre à jour la date d'exécution)
Exemple de contenu de la table `import_reg` :
| operation | source_code | table_name | field_name | from_value | to_value | sql | created_on | executed_on | import_hash |
|-----------|-------------|------------|------------|--------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|----------------------------|--------------------------------------|
| insert | 013CC021 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$013CC021$$, $$Caract. verres trait$$, $$ $$, $$2017-05-01$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 920CA050 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$920CA050$$, $$Tthèse CIFRE2015/119$$, $$ $$, $$2016-01-01$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 909CC189 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$909CC189$$, $$THESE CIFRE$$, $$ $$, $$2016-02-01$$, $$2019-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 909EA002 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$909EA002$$, $$ED SIMEM FONCTIONNEM$$, $$ $$, $$2015-01-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 913CA052 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$913CA052$$, $$ECO CORAIL$$, $$ $$, $$2014-09-26$$, $$2017-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 917CA955 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$917CA955$$, $$CAPACITE$$, $$ $$, $$2013-11-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | F971UP03 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$F971UP03$$, $$DELAWARE$$, $$ $$, $$2015-01-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 925CD271 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$925CD271$$, $$CONV.2015PCM51$$, $$ $$, $$2016-01-01$$, $$2017-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 925CD276 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$925CD276$$, $$CONV.1060284$$, $$ $$, $$2015-12-10$$, $$2019-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 920CA047 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$920CA047$$, $$Projet THYMOTHE$$, $$ $$, $$2015-12-21$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| undelete | 013GR014 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$SUBVENTION COTENTIN$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2017-01-01$$, FIN_VALIDITE = $$2047-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$013GR014$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| undelete | 012CB018 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$Bilan sur les nouv.$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2016-12-15$$, FIN_VALIDITE = $$2018-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$012CB018$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| undelete | 012RF001 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$COLLOQUE CONSTITUTIO$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2017-01-01$$, FIN_VALIDITE = $$2017-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$012RF001$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| update | 971UP002 | progfin | LIBELLE | Etablissement api | Etablissement API | UPDATE progfin SET LIBELLE = $$Etablissement API$$, updated_on = now() WHERE CODE = $$971UP002$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc |
| update | 971UP004 | progfin | LIBELLE | ETAB GIORGIA north | ETAB GIORGIA NORTH | UPDATE progfin SET LIBELLE = $$ETAB GIORGIA NORTH$$, updated_on = now() WHERE CODE = $$971UP004$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc |
| update | 950ME003 | progfin | LIBELLE | REGIE DE REC | REGIE DE RECETTES-EN | UPDATE progfin SET LIBELLE = $$REGIE DE RECETTES-EN$$, updated_on = now() WHERE CODE = $$950ME003$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc |
| ``` | | | | | | | | | |
Remarques :
- Le "hash" (`eb1ab85c-916a-11e7-aba7-0242f725575b` par exemple) dans la colonne `import_hash` permet d'identifier
un ensemble d'instructions SQL à exécuter au sein d'une même transaction dans la base de données destination.
- Chaque instruction SQL de mise à jour de la table destination
(ex: `UPDATE progfin SET LIBELLE = $$Etablissement API$$, updated_on = now() WHERE CODE = $$971UP002$$ ;`)
est suivie d'une autre instruction SQL permettant d'inscrire dans le registre d'import la date d'exécution
de la fameuse instruction de mise à jour de la table destination
(ex: `UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ;`).
- La colonne `executed_on` accueille la date d'exécution de l'instruction de mise à jour de la table destination ;
si cette date est NULL, l'instruction n'a pas été exécutée.
UnicaenDbImport
===============
![fonctionnement_import.png](documentation/fonctionnement_import.png)
Ce module a pour but de réaliser l'import de données d'une table (ou d'un select) d'une base de données source vers une
table d'une base de données destination.
......@@ -22,12 +20,23 @@ Les enregistrements source et les enregistrements destination doivent avoir un i
de les rapprocher : on l'appellera "code source" (cf. paramètre de config `source_code_column`).
**Cet identifiant doit être de type chaîne de caractères.**
__**Table des matières**__
* [La différence avec le module UnicaenImport ?](#la-différence-avec-le-module-unicaenimport-)
* [Installation](#installation)
* [Configuration](#configuration)
* [Utilisation](#utilisation)
* [Contraintes](#contraintes)
* [Fonctionnement](#fonctionnement)
* [Exemples](#exemples)
La différence avec le module UnicaenImport ?
--------------------------------------------
Comme son nom de l'indique pas, UnicaenImport fonctionne uniquement entre
2 bases de données Oracle ; et la synchronisation est faite entièrement par le SGBD (ou presque).
Comme son nom ne l'indique pas, UnicaenImport fonctionne uniquement entre
2 bases de données **Oracle** ; et la synchronisation est faite entièrement par le SGBD (ou presque).
UnicaenDbImport fonctionne en majeure partie en PHP tout en déléguant au maximum au SGBD ce qu'il sait bien faire
(`select FULL OUTER JOIN` pour le différentiel entre données source et destination, fonction pour l'alimentation du registre
......@@ -61,21 +70,37 @@ cp -n vendor/unicaen/db-import/config/unicaen-db-import.local.php.dist config/au
Utilisation
-----------
Le module fournit une ligne de commande pour :
Le module possède deux mécanismes distincts :
1. import : réalise un import "brut" des données, autrement dit une copie, d'une source vers une destination;
2. synchro : réalise une synchronisation entre une source et une destination en gérant une historisation (created_on, updated_on, deleted_on).
- lancer un import par son nom, exemple :
Le module fournit donc une ligne de commande pour lancer :
- un import par son nom :
```bash
php public/index.php run import --name "IMPORT_PFI_SIFAC"
php public/index.php run import --name "NOM_ETABLI_DANS_LA_CONFIG"
```
- lancer tous les imports :
- tous les imports :
```bash
php public/index.php run import --all
```
*NB: Cette commande exécute la synchronisation une fois. Pour synchroniser en permanence la base de données destination,
- une synchro par son nom :
```bash
php public/index.php run synchro --name "NOM_ETABLI_DANS_LA_CONFIG"
```
- toutes les synchros :
```bash
public/index.php run synchro --all
```
*NB: L'exécution d'une de ces commandes n'est effectif qu'une fois. Pour importer/synchroniser en permanence (dans) la base de données destination,
il faut programmer le lancement périodique de cette commande à l'aide de CRON (cf. plus bas pour un exemple).*
......@@ -84,7 +109,7 @@ Contraintes
### Identifiant commun
L'identifiant unique commun des enregistrements source et destination doit être de type **chaîne de caractères**.
L'identifiant unique commun des enregistrements (source_code_column) source et destination doit être de type **chaîne de caractères**.
### Source de type `'select'`
......@@ -99,6 +124,8 @@ Dans le cas d'une *source* de type `'select'`, il est nécessaire de convertir c
Il est également nécessaire de spécifier dans la config de la *destination* la fonction de conversion à appliquer
à chaque colonne pour la convertir en chaîne de caractères (cf. paramètre de config destination `columns_to_char`).
_Note : Peut être pas obligé de faire la convertion TO_CHAR dans le SELECT (mais quand même le faire dans le columns_to_char)._
### Source de type `'table'`
Dans le cas d'une *source* de type `'table'`, il est nécessaire de spécifier dans la config de la
......@@ -116,107 +143,102 @@ ALTER TABLE TABLE_DESTINATION ADD COLUMN deleted_on TIMESTAMP(0) WITH TIME ZONE;
```
Exemple de mise en oeuvre
-------------------------
Import des programmes de financement SIFAC depuis une base Oracle vers une table PostgreSQL. Les données sources sont
"extraites" grâce à un select.
Fonctionnement
--------------
### Import de données
Afin de s'adapter au plus grand nombre de SGBD, UnicaenDbImport s'appuie sur l'_ORM Doctrine 2_.
Ainsi nous effectuons le déroulement suivant :
<!--Voir le dossier documentation/ pour toutes modifications-->
![fonctionnement_import.png](documentation/fonctionnement_import.png)
*Schématisation du déroulement d'un import*
1. Suppression des données existantes dans _Destination_
2. Récupération des données de la _Source_ vers _Doctrine_
3. Génération des requêtes SQL (insert, update, ...) depuis PHP
4. Exécution des requêtes / Récupération des données vers _Destination_
### Synchronisation de données locales
L'un des mécanismes d'UnicaenDbImport est d'appliquer un système de synchronisation avec gestion d'une historisation.
Avec une table _Source_ et une table _Destination_ toutes deux dans une base locale, le déroulement est le suivant :
<!--Voir le dossier documentation/ pour toutes modifications-->
![fonctionnement_synchro_locale.png](documentation/fonctionnement_synchro_locale.png)
*Schématisation du déroulement d'une synchro (locale)*
1. Création d'une vue différentielle
2. Préparation/détection des actions à appliquer (INSERT, UPDATE, ...)
3. Application des mises à jour (avec historisation : created_on, updated_on, ...)
### Synchronisation de données distantes
Concernant le mécanisme de synchronisation, il est possible de spécifier une table _Source_ provenant d'une base distante.
Dans ce cas, les mécanismes d'[import](#import-de-données) et de [synchro (locale)](synchronisation-de-données-locales) seront réalisés successivement.
<!--Voir le dossier documentation/ pour toutes modifications-->
![fonctionnement_synchro_distant.png](documentation/fonctionnement_synchro_distant.png)
*Schématisation du déroulement d'une synchro (distant)*
1. Déclenchement du mécanisme d'import entre la _Source_ et une table _Temporaire_
2. Déclenchement du mécanisme de synchro entre la table _Temporaire_ et la table _Destination
3. Suppression de la table _Temporaire_
Exemple
-------
Pour les exemples suivants, on suppose posséder :
* 1 Base de donnée **A** locale (celle de notre application) contenant les tables :
* **UTILISATEUR**(int ID, str CODE, str NOM, str PRENOM, date NAISSANCE)
* **COMPOSANTE**(int ID, str CODE, str NOM)
* **PAIN_AU_CHOCOLAT**(int ID, str NOM, str BOULANGERIE)
* 1 Base de donnée **B** distante (par exemple Apogée) contenant les tables :
* **UTILISATEUR**(int ID, str CODE, str NOM, str PRENOM, date NAISSANCE)
* **COMPOSANTE**(int ID, str CODE, str NOM, int DIRECTEUR_ID, str ADRESSE)
* **CHOCOLATINE**(int ID, str NOM, str BOULANGERIE, int POURCENT_GRAS, int NOTE)
En outre, on suppose avoir déclaré les configurations _Doctrine_ 'orm_A' et 'orm_B' respectivement pour les bases **A** et **B** ci-dessus (soit dans le fichier [unicaen-db-import.local.php](config/unicaen-db-import.local.php.dist) soit dans un autre fichier de config.).
### Exemple 1 : Importation d'une table de données distantes
- Configuration globale du module :
Dans de nombreux cas, on peut souhaiter importer une table de données provenant d'une base distante.
Ce peut être le cas notamment lorsque l'on souhaite s'assurer de la constante disponibilité des données.
`unicaen-db-import.global.php`
```php
return [
/**
* Configuration du module UnicaenDbImport.
*/
'import' => [
/**
* Liste des imports.
*/
'imports' => [
[
/**
* Petit nom (unique) de l'import.
*/
'name' => 'Import_PFI_SIFAC',
/**
* Configuration de la source de données à importer :
* - 'name' : petit nom (unique) de la source
* - 'table' : nom de la table source contenant les données à importer
* - 'select' : select SQL de mise en forme des données source à importer (NB: antinomique avec 'table')
* - 'connection' : identifiant de la connexion Doctrine à la base de données source
* - 'source_code_column' : nom de la colonne dans la table/vue source contenant l'identifiant unique
* - 'columns' : liste ordonnée des noms des colonnes à prendre en compte dans la table/vue source
*/
'name' => "IMPORTATION DES DONNÉES B VERS A",
'source' => [
'name' => 'PFI SIFAC',
'connection' => 'doctrine.connection.orm_oracle',
'name' => 'TABLE UTILISATEUR DE MA BASE B',
'table' => 'UTILISATEUR',
'connection' => 'doctrine.connection.orm_B',
'source_code_column' => 'CODE',
'columns' => ['LIBELLE', 'FLECHE', 'DEBUT_VALIDITE', 'FIN_VALIDITE'],
'select' => <<<'EOT'
select distinct
MEASURE CODE,
SHORT_DESC LIBELLE,
ZFLECHE FLECHE,
to_char(TO_DATE(VALID_FROM,'YYYYMMDD'),'YYYY-MM-DD') DEBUT_VALIDITE,
to_char(TO_DATE(VALID_TO,'YYYYMMDD'),'YYYY-MM-DD') FIN_VALIDITE
from (
SELECT
SAPSR3.FMMEASURE.MEASURE,
SAPSR3.FMMEASURET.SHORT_DESC,
SAPSR3.FMMEASURE.ZFLECHE,
SAPSR3.FMMEASURE.VALID_FROM,
SAPSR3.FMMEASURE.VALID_TO
FROM
SAPSR3.FMMEASURE,
SAPSR3.FMMEASURET
WHERE
( (SAPSR3.FMMEASURE.CLIENT in (select A.MANDT from SAPSR3.FM01H A where A.MANDT = '500') OR SAPSR3.FMMEASURE.CLIENT is null)
and (SAPSR3.FMMEASURE.FMAREA in (select A.FIKRS from SAPSR3.FM01H A where A.FIKRS = '1010') or SAPSR3.FMMEASURE.FMAREA is null) )
AND ( SAPSR3.FMMEASURE.CLIENT=SAPSR3.FMMEASURET.CLIENT(+) )
AND ( SAPSR3.FMMEASURE.FMAREA=SAPSR3.FMMEASURET.FMAREA(+) )
AND ( SAPSR3.FMMEASURE.MEASURE=SAPSR3.FMMEASURET.MEASURE(+) )
)
EOT
,
'columns' => ['ID', 'CODE', 'NOM', 'PRENOM', 'NAISSANCE'],
],
/**
* Forçage éventuel du nom de la table intermédiaire utilisée lorsque source et destination
* ne partagent pas la même connexion. NB: cette table intermédiaire est créée/peuplée/supprimée
* dans la base de données destination à chaque import.
* En l'absence de ce forçage, le nom de la table intermédiaire sera celui de la table destination
* préfixé par "src_".
*/
'intermediate_table' => 'src_progfin',
/**
* Suppression automatique de la table intermédiaire "src_" au début de l'import.
* Si la suppression automatique est désactivée, l'existence de la table intermédiaire au démarrage
* de l'import fera échouer l'import.
*/
'intermediate_table_auto_drop' => false,
/**
* Configuration de la destination des données importées :
* - 'name' : petit nom (unique) de la destination
* - 'table' : nom de la table destination vers laquelle les données sont importées
* - 'connection' : identifiant de la connexion Doctrine à la base de données destination
* - 'source_code_column' : nom de la colonne dans la table destination contenant l'identifiant unique
* - 'columns' : liste ordonnée des noms des colonnes importées dans la table destination
* - 'columns_to_char' : format sprintf nécessaire pour mettre des colonnes au format requis (string)
*/
'destination' => [
'name' => 'PFI Zébu',
'table' => 'progfin',
'connection' => 'doctrine.connection.orm_default',
'source_code_column' => 'code',
'columns' => ['libelle', 'fleche', 'debut_validite', 'fin_validite'],
'name' => 'TABLE UTILISATEUR DE MA BASE A',
'table' => 'UTILISATEUR',
'connection' => 'doctrine.connection.orm_A',
'source_code_column' => 'CODE',
'columns' => ['ID', 'CODE', 'NOM', 'PRENOM', 'NAISSANCE'],
'columns_to_char' => [
'debut_validite' => "TO_CHAR(%s,'YYYY-MM-DD')", // car colonne destination de type TIMESTAMP
'fin_validite' => "TO_CHAR(%s,'YYYY-MM-DD')", // idem
'ID' => "%s||''",
'NAISSANCE' => "TO_CHAR(%s,'YYYY-MM-DD')",
],
],
],
......@@ -225,190 +247,72 @@ EOT
];
```
*On doit renseigner le paramètre `intermediate_table` car les données source proviennent d'un select.*
`Dans le terminal du serveur`
```bash
php public/index.php run import --name "IMPORTATION DES DONNÉES B VERS A"
```
- Configuration locale du module :
`unicaen-db-import.local.php`
### Exemple 2 : Importation d'un select de données distantes
Les bases distantes requêtées sont généralement bien garnies.
Pourtant il est fréquent de ne vouloir importer qu'une partie de ces données.
`unicaen-db-import.global.php`
```php
return [
/**
* Configuration Doctrine minimum requise.
*/
'doctrine' => [
'connection' => [
'orm_oracle' => [
'driverClass' => 'Doctrine\\DBAL\\Driver\\OCI8\\Driver',
'params' => [
'host' => 'host.domain.fr',
'port' => '1525',
'user' => 'x',
'password' => 'y',
'dbname' => 'z',
'charset' => 'AL32UTF8',
],
'eventmanager' => 'orm_oracle',
],
'orm_default' => [
'driverClass' => 'Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver',
'params' => [
'host' => 'host.domain.fr',
'port' => '5432',
'charset' => 'utf8',
'dbname' => 'a',
'user' => 'b',
'password' => 'c',
'import' => [
'imports' => [
[
'name' => "IMPORTATION PARTIELLE DES DONNÉES B VERS A",
'source' => [
'name' => 'TABLE CHOCOLATINE DE MA BASE B',
'select' => <<<'EOT'
SELECT
ID,
NOM,
BOULANGERIE
FROM CHOCOLATINE
EOT
,
'connection' => 'doctrine.connection.orm_B',
'source_code_column' => 'ID',
'columns' => ['ID', 'NOM', 'BOULANGERIE'],
],
'destination' => [
'name' => 'TABLE PAIN_AU_CHOCOLAT DE MA BASE A',
'table' => 'PAIN_AU_CHOCOLAT',
'connection' => 'doctrine.connection.orm_A',
'source_code_column' => 'ID',
'columns' => ['ID', 'NOM', 'BOULANGERIE'],
'columns_to_char' => [
'ID' => "%s||''",
],
],
'eventmanager' => [
'orm_sifac' => [
'subscribers' => [
\Doctrine\DBAL\Event\Listeners\OracleSessionInit::class,
],
],
],
],
];
```
*NB: les paramètres de config de la connexion `orm_default` existent sans doute déjà dans la configuration de votre
application, il est bien-sûr inutile de les répéter dans la config du module unicaen/db-import.*
- Configuration CRON pour lancer périodiquement la synchro :
```cron
# Du lundi au vendredi, entre 6h00 et 19h45, toutes les 15 minutes
*/15 6-19 * * 1-5 root /usr/bin/php /var/www/zebu-back/public/index.php run import --all 1> /tmp/zebu-cron.log 2>&1
```
- Exemple de logs affichés par la commande :
```
######################## IMPORTS ########################
### Import des PFI SIFAC ###
05/09/2017 16:22:08
# delete :
UPDATE progfin SET deleted_on = LOCALTIMESTAMP(0) WHERE CODE = 'fddsfsd' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'fddsfsd-99c4b652-9245-11e7-bddd-0242f725575b' ;
(1 instructions exécutées)
# insert :
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('971UP009', 'Of Wisco', ' ', '2014-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '971UP009-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('977CA025', 'Screening de 5', ' ', '2016-07-21', '2018-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '977CA025-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('F950MEC1', 'MDE CAEN FONCTIONNEM', ' ', '2015-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'F950MEC1-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('F971CF03', 'UNIVERSITE DU HAVRE', ' ', '2015-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'F971CF03-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('014DU009', 'DU RDCM', ' ', '2016-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '014DU009-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('012RE001', 'COLLOQUE 50 ANS SC E', ' ', '2017-01-01', '2017-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '012RE001-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('012CH021', 'Typo-chronologie', ' ', '2017-01-01', '2017-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '012CH021-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('013PL002', 'CMABIO', ' ', '2016-01-01', '2047-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '013PL002-99c4b652-9245-11e7-bddd-0242f725575b' ;
INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE, created_on) VALUES ('999CM003', '2016-AGRI-133', ' ', '2016-03-30', '2018-12-31', LOCALTIMESTAMP(0)) ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '999CM003-99c4b652-9245-11e7-bddd-0242f725575b' ;
(9 instructions exécutées)
# undelete :
UPDATE progfin SET LIBELLE = 'TA 2017', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = '913TA017' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '913TA017-99c4b652-9245-11e7-bddd-0242f725575b' ;
UPDATE progfin SET LIBELLE = 'TA 2017', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = '920TA017' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '920TA017-99c4b652-9245-11e7-bddd-0242f725575b' ;
UPDATE progfin SET LIBELLE = 'PAIE DIU ADOLESCENTS', FLECHE = ' ', DEBUT_VALIDITE = '2017-01-01', FIN_VALIDITE = '2047-12-31', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE CODE = 'P14DU910' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = 'P14DU910-99c4b652-9245-11e7-bddd-0242f725575b' ;
(3 instructions exécutées)
# update :
UPDATE progfin SET LIBELLE = 'PLATIN''', updated_on = LOCALTIMESTAMP(0) WHERE CODE = '925PL005' ; UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = '925PL005-PLATINvxcvxc-99c4b652-9245-11e7-bddd-0242f725575b' ;
(1 instructions exécutées)
Import réalisé avec succès.
`Dans le terminal du serveur`
```bash
php public/index.php run import --name "IMPORTATION PARTIELLE DES DONNÉES B VERS A"
```
### Exemple 3 : Synchronisation de données locales
Dans le moteur
--------------
### Table intermédiaire `SRC_*`
Lorsque les données source sont issues d'un select, une table intermédiaire `SRC_*` est créée dans la base de données
destination à partir de ces données source. La synchronisation est ensuite réalisée entre cette table intermdiaire et
la table destination. À la fin du processus de synchronisation, cette table intermdiaire est supprimée.
*NB: attention, si au lancement de la commande d'import la table intermédiaire `SRC_*` existe déjà dans la base de
données ete que le paramètre de config `intermediate_table_auto_drop` est à `false`, l'import échouera.*
Ex: SRC_XXX vers XXX
### Différentiel entre source et destination
### Exemple 4 : Synchronisation de données distantes
La requête utilisée pour comparer les données sources et destination pour une table destination `progfin` ressemble
à cela (syntaxe PostgreSQL) :
Ex: WELCOME_CATALOGUE vers CATALOGUE (-> Utilisation du mécanisme d'import, etc, donc potentiellement des paramètres 'intermediate_table' et 'intermediate_table_auto_drop', ...)
```sql
SELECT create_import_metarequest_for_progfin(
src.code, src.libelle, TO_CHAR(src.debut_validite,'YYYY-MM-DD'),
dest.code, dest.libelle, TO_CHAR(dest.debut_validite,'YYYY-MM-DD'), dest.deleted_on,
'eb1ab85c-916a-11e7-aba7-0242f725575b'
) AS operation
FROM src_progfin src
FULL OUTER JOIN progfin dest ON src.code = dest.code
;
```
### Exemple 5 : Synchronisation de données distantes avec récupération des clés étrangères locales
### Fonction `create_import_metarequest_for_*`
Le module crée automatiquement dans le SGBD pour chaque import configuré une fonction `create_import_metarequest_for_*`
chargée de :
- déterminer l'opération de mise à jour nécessaire à partir du résultat du différentiel entre données source et
destination ;
- inscrire dans un "registre d'import" (table `IMPORT_REG`) les instructions SQL nécessaires à la mise à jour de
la table destination.
Vous trouverez dans [ce fichier](test/UnicaenDbImportUnitTest/CodeGenerator/PostgreSQL/Helper/expected_function_creation_sql.sql)
un exemple de fonction `create_import_metarequest_for_*` pour une table destination `ztemptable` dans une base PostgreSQL.
*NB: Pour un enregistrement existant à la fois dans la source et la destination (opération "update"), la détection de
différence et donc sa mise à jour se fait colonne par colonne.*
### Table `IMPORT_REG`, registre d'import
Une table de travail `import_reg` est utilisée par le module pour y inscrire les opérations nécessaires à chaque
synchronisation.
Ce registre comporte les colonnes suivantes :
- le type d'opération nécessaire : insert, update, delete, undelete
- le "source code" de l'enregistrement concerné
- le nom de la table concernée
- le nom de la colonne concernée (si c'est pertinent pour l'opération)
- la valeur de la colonne avant synchronisation (si c'est pertinent pour l'opération)
- la valeur de la colonne après synchronisation (si c'est pertinent pour l'opération)
- l'instruction SQL à exécuter pour mettre à jour la table destination
- la date d'exécution de l'instruction SQL de mise à jour de la table destination
- la date de création de la ligne de registre
- un hash permettant d'identifier l'ensemble de lignes auquel appartient cette ligne de registre
(utile pour mettre à jour la date d'exécution)
Exemple de contenu de la table `import_reg` :
| operation | source_code | table_name | field_name | from_value | to_value | sql | created_on | executed_on | import_hash |
|-----------|-------------|------------|------------|--------------------|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------|----------------------------|--------------------------------------|
| insert | 013CC021 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$013CC021$$, $$Caract. verres trait$$, $$ $$, $$2017-05-01$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 920CA050 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$920CA050$$, $$Tthèse CIFRE2015/119$$, $$ $$, $$2016-01-01$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 909CC189 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$909CC189$$, $$THESE CIFRE$$, $$ $$, $$2016-02-01$$, $$2019-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 909EA002 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$909EA002$$, $$ED SIMEM FONCTIONNEM$$, $$ $$, $$2015-01-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 913CA052 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$913CA052$$, $$ECO CORAIL$$, $$ $$, $$2014-09-26$$, $$2017-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 917CA955 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$917CA955$$, $$CAPACITE$$, $$ $$, $$2013-11-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | F971UP03 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$F971UP03$$, $$DELAWARE$$, $$ $$, $$2015-01-01$$, $$2047-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 925CD271 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$925CD271$$, $$CONV.2015PCM51$$, $$ $$, $$2016-01-01$$, $$2017-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 925CD276 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$925CD276$$, $$CONV.1060284$$, $$ $$, $$2015-12-10$$, $$2019-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| insert | 920CA047 | progfin | NULL | NULL | NULL | INSERT INTO progfin(CODE, LIBELLE, FLECHE, DEBUT_VALIDITE, FIN_VALIDITE) VALUES ($$920CA047$$, $$Projet THYMOTHE$$, $$ $$, $$2015-12-21$$, $$2018-12-31$$) ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| undelete | 013GR014 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$SUBVENTION COTENTIN$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2017-01-01$$, FIN_VALIDITE = $$2047-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$013GR014$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| undelete | 012CB018 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$Bilan sur les nouv.$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2016-12-15$$, FIN_VALIDITE = $$2018-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$012CB018$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| undelete | 012RF001 | progfin | NULL | NULL | NULL | UPDATE progfin SET LIBELLE = $$COLLOQUE CONSTITUTIO$$, FLECHE = $$ $$, DEBUT_VALIDITE = $$2017-01-01$$, FIN_VALIDITE = $$2017-12-31$$, updated_on = now(), deleted_on = null WHERE CODE = $$012RF001$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$eb1ab85c-916a-11e7-aba7-0242f725575b$$ ; | 2017-09-04 14:17:41.501375 | 2017-09-04 14:17:41.549583 | eb1ab85c-916a-11e7-aba7-0242f725575b |
| update | 971UP002 | progfin | LIBELLE | Etablissement api | Etablissement API | UPDATE progfin SET LIBELLE = $$Etablissement API$$, updated_on = now() WHERE CODE = $$971UP002$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc |
| update | 971UP004 | progfin | LIBELLE | ETAB GIORGIA north | ETAB GIORGIA NORTH | UPDATE progfin SET LIBELLE = $$ETAB GIORGIA NORTH$$, updated_on = now() WHERE CODE = $$971UP004$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc |
| update | 950ME003 | progfin | LIBELLE | REGIE DE REC | REGIE DE RECETTES-EN | UPDATE progfin SET LIBELLE = $$REGIE DE RECETTES-EN$$, updated_on = now() WHERE CODE = $$950ME003$$ ; UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ; | 2017-09-04 14:17:41.501375 | NULL | cd384e78-2588-4d99-ad9a-5364adac41fc |
| ``` | | | | | | | | | |
Remarques :
- Le "hash" (`eb1ab85c-916a-11e7-aba7-0242f725575b` par exemple) dans la colonne `import_hash` permet d'identifier
un ensemble d'instructions SQL à exécuter au sein d'une même transaction dans la base de données destination.
- Chaque instruction SQL de mise à jour de la table destination
(ex: `UPDATE progfin SET LIBELLE = $$Etablissement API$$, updated_on = now() WHERE CODE = $$971UP002$$ ;`)
est suivie d'une autre instruction SQL permettant d'inscrire dans le registre d'import la date d'exécution
de la fameuse instruction de mise à jour de la table destination
(ex: `UPDATE import_reg SET executed_on = now() WHERE import_hash = $$cd384e78-2588-4d99-ad9a-5364adac41fc$$ ;`).
- La colonne `executed_on` accueille la date d'exécution de l'instruction de mise à jour de la table destination ;
si cette date est NULL, l'instruction n'a pas été exécutée.
Cas d'une table "Composante" où des composantes sont en clé étrangère avec une table "Formation" via son ID. -> Passage par une vue SRC (déclaration d'un import et d'une synchro)...
Documentation
=============
Ce répertoire contient les diverses ressources (images...) nécessaire à la documentation générale de l'application.
*file* **.png** : image utilisée directement dans la documentation
*file* **.drawio** : source d'une image permettant sa modification sur https://www.draw.io
\ No newline at end of file
<mxfile modified="2019-05-15T14:18:26.824Z" host="www.draw.io" agent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" etag="Jr5_HcIyoOzdWaDvR-2I" version="10.6.7" type="device"><diagram id="mWMuuFM-TLnJckGO8D4I" name="Page-1">7Vxbe9rIsv01fpx8utrwiBHGcCRh7og33QISEuIggZB+/a6qbnExxPbsk0xy9sb5ZiL1pbp6rVXVjcvOg9yMD+2tvVkaiedHD5LgHR5k7UGSFFmB/2NDwRpqgswaFtvAY03iqWEYlD5vFHjrLvD89GJgliRRFmwuG91kvfbd7KLN3m6T/HLY9yS6XHVjL/yrhqFrR9et08DLlnwXqnBqf/WDxbJaWRR4T2xXg3lDurS9JD9rklsPcnObJBl7ig9NP0LsKlzYvJcf9B4d2/rr7CsTJDZhb0c7vrcH6TGCqc9esIfHBT4Ok93W9asOMHbWV7V+T2BB2E9WcJAe/3eXVB1/pURhAwaIwuZw6qyseEGa2evsfA1msFpEurAtAWwbfHSLKFh7/hbG5Msg84cb28X2HGQHbcssjuBNhEcn2cFAT3eODba7WmyxtbfLwIrP278HUdRMomRLK8kv9HX04RxZDvbe32b+4ayJI932k9jPtgUMOVQyZzO46sVKBPlJQ4+8aXkmn2qazVW7OBo+EQsPnNvbPMtf4flvcynd4lLz0yz464dMfiSin+7L2s6CZP03ffmP0Jr8G8Wm3hDbO1D9tdfARIy4RnaaBu4lhLDxbTGDF6F6sfDlm1q9aofzTq2o3g5BNqtswPPZLHg7TcKXas4P0U5Z3qOmySTbrruBGuSBVEuLfTcZfv9LeuSHj71d+NkHA0V+4PnexcFyzd4ZO+oNdqq2rR+BsveXx9EtyvgKb0lAYVRJp34pDkl4xzrbOZ91fny8N/T0zpAsfFNqcu1JqD8+PSliXbq0y4C6skuCOqLwJY19CPPHaW7kx5vkry9noq0dbI8Hk7P9c7LHYmt7AWi2yiDrBLuOzRq47VL6g1UgJ3+ab66Sy42g+GG+kaSnb+pvyzi31fB0pQbtoSk/NOpu5K/dpR/7pOUA5LDNrpgDLLJLZtJsm6z8d3CfY8qb7ChYIOou2CfGEdkAro8N3hEHnofL3JTClvF/kZz+T9SIgnhBTP2aF1FRromRfhkxtStixCv4wQ5c538E0hkrdrphd/zvwQFhe6/y7yr+uaIPeh7pi98lztrZ188B/90xfAN76UZM/DLoqxPro7O5SllBTB+DjurVbceP3pI04EnFSbIsiWFAhB3Pxyx1jj193YiALNncoo6WbFStQtUCz56d2XDTY6/SS7pfPEjPBxCB1Hx7NaV58aw408POLTcrqxQC+3UguFqy1+Vn0Y3znSN317o0CHVpks6nYuSsB6VetnbGsBZ0XpeZ01bLXmyGb8Nu4r0O8l5Q21tyN7Jmg40XT0JHEjNHUks9rhfzor5zC+M0b91dzcPzNT3ZK1TZKNS9G7t7Y7RSe8NabgQ1mCUW87aVuXK089ovij5Vy07RWfhtMXXWxqMrz9fnPoAlWV+7fF2YrzVyXcb9HufUO/FS8F4bj3pRh9HuzisNtt+yk8P4PdrsBEd8SkcabNx2fWWPzn029/N2lGOfvjb33qwbzqdz8N+L9FiNvGa9NWn1964E82bPMHaVGU115bX7Z/hFO1s2Q2v2HF3t4ayvwtACHlwIZUualLp06gdfpfmsW9rT+u5t2DnoYSvotKMV2NSgfzkr4f11Ue+Ez7nfBBxgH8brs4Lz7OlE7sd1BeblHa2xMDVX0UM3N4qGYJaLnVk2ShhXeq9ROod586m6tOJDpE+74Ee2duO66MT9RyeeyDjfaJ7N0/pyr6nIhmY8MTzzR+b/uN5ZPxfzmRm56zn6f4bxZuVIJu0V/F/Ppy+gAbX0Yhf3dNxzD+a5a3PjSArsq7HrjTo78Dkxwn5ujsaCGeQLI2wcek0B9iIcjOEqeAsP2Xw2WM7bL4I1fN7MNSFwphPBmg6WXrslmMMOxMjbK6pzMxy0LHxrm2Ev8oDXdOHGg7gXvwSOPBEAL7QHsbOJQTuZNevDaHsqLufSmBT0PzKwrOXAvPE4Bwad6YvAZwg+ssbWQRSYekYfReEgdKYRzdNFU7Cnh3TI1gr0UqnhGl47Epz2eP8W5ntLMh4nUr2woU+XL/bTrJ8wGOHMSewWDAt6LvvAd41wOCm8F6tLQGrlxVHkFc+5NfUiS6qnjtypdwIzntMfCxjqAlKDyI3NvdOuF4BSCHxJ+B+wDMiBLzMY034J7dlARbVC+96KN5ElDwRHOsDclx0pJGzl0AcKiyBq6inMWzqxGeEcA1Q8lycb8PjWnI0TZ6UlveTz0YZ8dUh1gB8qH+baU3XrSubSbY8fwZc1cJCACvYeZELIUxf9HkTavF2X9Zm3mb8OElhfMrXGOzV6sCbML8RkPo3W9msffOkrbC14Psev7OOeSxtszuUuQz5+SV3g8tLmZu9IAqnb1FZFb7RSzHJ1OSY2MXILo1ztjHJ8AAVf+G7JK5jfKXtDpTRHLfEdNjKMXTmym/nggxm+j7DjnpCnwix+gAvo0G9HmT3bRLiX9/ha8qRw48nOa4qpNYsiFzL/vLRQt3FHXi575WHNs9W618QI7AamYo3S7lvb5ei5Uifko/NV9+31GeJ1scDVRqO+aDaVQtdaCuzyYGgvqT4ySj20MnOoyFYhwPM4M0ZwdhWCoo86hR524N3YmUOhNAJFcrSWqoeLzAjdnSkZkP/6NL7XzGXIpdTeG6IdmAe5zZjm0LaCfqGANZVOs7HotA1cT9JH450ZQL7BtUIXxjVwLjx7oT6ydkaQ5/qoBT60UmOoqPCMa6QwV3TAnlkoik7+LGF8K0e/cB/Mb/BlBGpogt84btQCn6KUr4ltReUL7EuAfkEP4XnU35kF2G6SHZX2EUKbBLY1yJtTPmaIvnUkWDPl6+D+ANsO2PFoHcCkNNE+4hnkDNsQbAxzhfwrO2gH9q/IfD8i+IH4LtA34APeDYbREJ6JGxds4fME9oxYE0bw3k/BzgF8AhxWKZ4lDvNJBZ/OMOqQvwbZBA403AP4WKDvyOdLCjzAf8DdEPBpPoeEGfrZRP9WuGYBcwWGqZHSPiTgGe4GsE/wZR5yPFOGLWLiphfYkk9diWOaA48wZsyxzAszQG75PNCPEQOWTbTNsSkN5hNhjr6CTwHsX0OfXOYTag/8NYscsRUs0hngRHoVSEtMr8gN4ML0SjqtMCOdaqgZ5Al5Q39QpznpFPyE9QEvPFOHwgGxJ44YX+Sro3UY3+FzSNgPcR8G2QHfYB0L1m6xNSTsw3fYX2wUzN8+xGN+4LidMAlywtLW+jCuGx51SZwusgpL0uWI6ZLFB8U8x7F14PyLMI/iHXWLsc74nRBG4OcBcbHKMeoMMIF5GtOFSdoZI96w9nMI7yrDZIx5pGQ4H2M7Pc8HsH7OcgxqMmeapLgyUtLkCDXZYjg2BfIB+mXOjcg0zXQDmDNdkgbGLL5AN+Qvajg2EMMS5jONUYy3OJ5Mm4iVFwgCaonyQ8ugOxppXjN2Pa0B++5LPYwFyD2wnmhokJO0scBiawF8tnJjliww3xos/iSIHQHybWg0c4Z3k9opb9A8zK1NXDeXKe5x/zgG9TvMhYpP4Absr6r8ULL8Q74zHUHcArbICeDQh7WikPZboA0Lcx36nXP7Eos7j+8fYww5IV3CPhuoyYKwRNxg/2a7yucsTsCHnPBjWCKulIfZvtHfKoeQLcxVyBPjnPhrsDgtT7kBbApckwfGbwufS84V+iJZeE/Ffs0lLTENtthZprWQrwOPeZHwHeL8FY89K2N5EW0vU53iNQfsT76iFskHnIc5KkCeLOSpsJtMH+gDwxH7+oxTxJrGdtEu+NIlez3KB3DuNVG/yCnytWKcttmZwmIFz0HMZ9jfx71An3WGY5VnOmgL9Y/7lzjmyEfB+KD8g7wodE40kccJnvciyyER4wTzBe0T/x4zTpD3KWkUNNTIKMey+DqQz2CTny0KOyOP51XO+/GsRq2VNI/2NWAag7MX/cb8DfYEfm6Vx7OpSXlHYOcVcXswGNc87lt0BpoYo2y/B8JpiFqhs12t7gLIMYsptI/nZoUh3W+YBjF/s/eCv0tV/EMccP6JP5aLaTzlFVwfNO9hHioZx5TLGcekLeQXcn5xyjM9OjfxPDieE0fdgW0Y1+cx12c5kcUXu3PEOd1F8H7F+ZEJZ8Ksc+ISdO/Q+bVgsdo2ch43cH5gjKLNBb9TNXg/aJtyUws5KGgc2YP8X9K5grEp8TuTcLxvaJw35JByyYI4hLxeVrozWRxTrqLYZPkJxoy5pvopiw8r7bF7HsUlxfQIzzYzJJ+IJx6XdO4eYy2hewS/r3DO2N2DuMuPsetoLvkAd1e6JxF3Wp/nVQvPETpLelM4K1BTmPNZroO4oLsEagNzh4z3HJNyKOXXA8uR7E7HzpYGnT8sTjHmqrsLxp3AcjD9PU5PuRD96nMuKB4QY3bPA0wucm/Z4O2gJdoj5fSzPH3M6fxuD+cUnX8G4Ui2WV4mfzm3MjtTMD7pbor7k9nZ5mJ+R92rVklcUG5ld6QVG0/aBx+nOZ1lcJ+s+KD7yjEXU07F9zm7A44Xgom5bJZU34/JrdkggU+Y+7fACv1266nZX1Wfa2q65J4+/bx7m1ef0eFucfwsVOb7+Y1PUXBvCyr78Hl/fXw+fZ4CbDpfWUv6fK3FZ2sVxpfW6hSfrhX2P13LDL60lvLpWmXjs7XgPvmVtQzh87WsT9fqfYAhfX+zVPA7yD+n/qFe1j9EWbz6Jrxau1Fsrf2qb8Jf1z8+KpDzqtJ5dZxXuatS9leq3MeK+rG8/rWK+s3C9c8rl0u/tQ7+rjpz/AGzv10HVz8x9IsL31L9SlF/dFUTC278x/rEn1XlVN+Vn5XadZT/k6U2WfhbUf6H/BjMTwn0KsGeB7r8OwNdlmuX8fnv/sCL/KR+q0myKtUVUXx6VJ7eiU6tf3sS1Fo1pKL8H0oDFe736u69unuv7t6ru/fq7r26e6/u3qu79+ruvbp7r+7eq7v36u69unuv7t6ru/fq7r26e6/u3qu79+ruvbr7/726Kwt/WHVXlv47q7u3ij4f/JbubysFSe9KQf9uzVeqPX07VXrEp6c/qhJ0/Q8+pMXaXW6T/56KsCz8aRXh619Pv/kLyX82Uz+BGan2+W8kq/V/kpnrf7LiRtL+z/iN5OMv4f/yX0mG19O/ZMQy2+mfg5Jb/wI=</diagram></mxfile>
\ No newline at end of file
documentation/fonctionnement_synchro_distant.png

15.1 KiB

<mxfile modified="2019-05-15T13:45:14.165Z" host="www.draw.io" agent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0" etag="7NW-oTR3yv0mZXlWeKPD" version="10.6.7" type="device"><diagram id="mWMuuFM-TLnJckGO8D4I" name="Page-1">3Vhbj5s4FP41PM4IcMjlMSVNq2pXu1Kk2faRgkPcOpg1zm1//R5jGwwmk0TNNFUz0sQ+vn/nnO+z46F4e/zAk3LzJ8sw9UI/O3po4YXhCI3gvzSclGHqI2XIOcmUKWgNK/If1kZfW3ckw1Wno2CMClJ2jSkrCpyKji3hnB263daMdlctkxw7hlWaUNf6D8nERp8i8lv7R0zyjVk58HXLNjGdtaHaJBk7WCb03kMxZ0yo0vYYYyqxM7ioccszrc3GOC7ENQNCNWCf0J0+24rteIr17sTJHBk2WspieqKkyDD30LvDhgi8KpNU2g/gaLBtxJZCLYDiV7aDjtkfXxtDkn7PubT+tRMwC9b2NaE0ZpTxeiUUx8tlHIPdPYs+3h5zgY+WSZ/tA2ZbLPgJuhxNYKkROs4CA/uh9dpYmzaWw8ywRMdJ3kzcQgkFjeYwsshB1gvHFBZ4l5E9FHNRH1GZ1gyOaOM9/nfHTMNTVSfAHDoEYXlsG80sC1wJ8mTmgo2p6bpLgHlg4bvvpUgEYcWNe3lYrC2XM/jcJ9bQA4MtGgi2Hqi4yOaS+iSuNKkqknYhxEciPlvlL1D2nyNdW8hD+qZyMpUC9vnZrlijZLUdVtfMuLNoV4p8bGoSCc+x7vXyInjxiUTkQMJpddp/Yqv1U6DFBGcd0nb9ZPkhGvCDsXFMIYb3Xaofco5e4W9G6oTRYTDpRkE067lXHVEPspm5N08jESacxr2JFDDORHWoNKe+KnomTvQsvBh585kA8ayz2c/Ieq1s4LMUtLcfXpAkohtQleDsOzbJVjCZg53806aEkryQUQnBUOe6TDkCYjvXDVuSZXKZQRLgKvMvBdb1aRzMph3cZ5GTxcFoIHzCO6Tx1HHEQB5TCtecc3BY+CdVqe4+a3KUAPW5bx3JP8dR0DKuP5rxLbv63AnmyeQSzOEboRy4l57b2dIwX3Ab8xmW9W9g2bNo9xjvBq60eRY9ki0R6rJcc8W/lS5R0KPLvqzejy5fRfb1+94LtF17H5OU+/z4G1POk4zgDg80N3TTtiBcKwUsBZfRixcthycG4vssdYBgPUcPu2oNe9+V0JgrvdT3YX+/cx9Vv5Jo/qBLxj2XTFyPTNHb8PmwR1wtDRz8fx0t/TH0/fEl7N9KSwexNxPf4znSiuMXq+XSc6TV4WbUxeeIo6YX3ievyoCtxI8V2N4zIuw/I64W2MkFpT4jsODl5GR1K2WH6vyGw6h7/w6R/+q+Rj3h7/WHgtrBXdW+ue+2ET4vSwoEbN5L8onkb0lVf9dKANvyvwHYv7EKzMKuK0YuEQXBz1QBQzyWn9BvqwKToKfBQ/jfSQig2v5IrbKq/aUfvf8f</diagram></mxfile>
\ No newline at end of file
documentation/fonctionnement_synchro_locale.png

15.5 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment