Commit acbc6c84 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Amélioration du README

parent 36f5f248
Loading
Loading
Loading
Loading
+78 −116
Original line number Diff line number Diff line
UnicaenDbImport
===============

Ce module a pour but de simplifier l'import de données d'une table (ou d'un select) d'une base de données source vers une 
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 :
@@ -17,13 +17,12 @@ Concrètement :
    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.
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.**

Les données sources sont recopiées dans une table intermédiaire et c'est à partir de cette table intermédiaire 
qu'est réalisé le différentiel avec la table destination.

**La différence avec le module UnicaenImport ?**
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).
@@ -65,7 +64,7 @@ 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"
    php public/index.php run import --name "IMPORT_PFI_SIFAC"
    ```
    
  - lancer tous les imports :
@@ -81,24 +80,37 @@ il faut programmer le lancement périodique de cette commande à l'aide de CRON
Contraintes
-----------

- L'identifiant unique commun des enregistrements source et destination doit être de type chaîne de caractères.
### Identifiant commun

- Les colonnes synchronisables de la table destination ne peuvent être que de type chaîne de caractères 
  (`varchar` par exemple). 
  Donc 2 solutions :
L'identifiant unique commun des enregistrements source et destination doit être de type **chaîne de caractères**.

  - Dans le cas d'une source de données spécifiée par un "select", convertir en amont chaque colonne en chaîne de 
    caractères (en faisant un `TO_CHAR(debut_validite,'YYYY-MM-DD')` par exemple en PostgreSQL) ;
### Source de type `'select'`

  - Dans le cas d'une source de type "table", 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`).
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`.

- La table destination doit posséder les colonnes d'historique suivantes (syntaxe PostgreSQL) :
```
created_on TIMESTAMP(0) WITH TIME ZONE DEFAULT LOCALTIMESTAMP(0) NOT NULL
updated_on TIMESTAMP(0) WITH TIME ZONE
deleted_on TIMESTAMP(0) WITH TIME ZONE
- 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;
```


@@ -178,6 +190,13 @@ EOT
                 */
                '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
@@ -225,7 +244,8 @@ return [
                 'password' => 'y',
                 'dbname'   => 'z',
                 'charset'  => 'AL32UTF8',
             ]
             ],
             'eventmanager' => 'orm_oracle',
         ],
         'orm_default' => [
             'driverClass' => 'Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver',
@@ -240,6 +260,13 @@ return [
             ],
         ],
     ],
     'eventmanager'  => [
        'orm_sifac' => [
            'subscribers' => [
                \Doctrine\DBAL\Event\Listeners\OracleSessionInit::class,
            ],
        ],
     ],
 ],
];
```
@@ -294,24 +321,24 @@ Dans le moteur

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 est supprimée.
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, l'import échouera.*
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 `ztemptable` ressemble 
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_ztemptable(
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_ztemptable src
FULL OUTER JOIN ztemptable dest ON src.code = dest.code
FROM src_progfin src
FULL OUTER JOIN progfin dest ON src.code = dest.code
;
```
 
@@ -324,83 +351,11 @@ chargée de :
  - inscrire dans un "registre d'import" (table `IMPORT_REG`) les instructions SQL nécessaires à la mise à jour de 
    la table destination. 

Voici à quoi ressemble cette fonction pour une table destination `ztemptable` (syntaxe PostgreSQL) :

```sql
CREATE OR REPLACE FUNCTION create_import_metarequest_for_ztemptable(
    src_code TEXT, src_libelle TEXT, src_debut_validite TEXT, src_fin_validite TEXT,
    dest_code TEXT, dest_libelle TEXT, dest_debut_validite TEXT, dest_fin_validite TEXT, dest_deleted_on TIMESTAMP(0) WITH TIME ZONE,
    import_hash VARCHAR(255)
) RETURNS VARCHAR(255) AS
$Q$
DECLARE
    operation VARCHAR(64);
    hash VARCHAR(255);
    SQL TEXT;
BEGIN
    -- l'enregistrement existe dans la source mais pas dans la destination : il devra être ajouté
    IF (src_code IS NOT NULL AND dest_code IS NULL) THEN
        operation = 'insert';
        hash = concat(concat(src_code, '-'), import_hash);
        SQL = concat('INSERT INTO ztemptable(code, libelle, debut_validite, fin_validite, created_on) VALUES (', quote_literal(src_code), ', ', quote_literal(src_libelle), ', ', quote_literal(src_debut_validite), ', ', quote_literal(src_fin_validite), ', LOCALTIMESTAMP(0)) ;');
        SQL = concat(SQL, ' UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = ''', hash, ''' ;');
        INSERT INTO import_reg(operation, TABLE_NAME, source_code, field_name, to_value, from_value, SQL, created_on, import_hash) VALUES ('insert', 'ztemptable', src_code, NULL, NULL, NULL, SQL, LOCALTIMESTAMP(0), hash);
    END IF;
 
    -- l'enregistrement existe dans la destination et n'est pas historisé
    IF (src_code IS NOT NULL AND dest_code IS NOT NULL AND dest_deleted_on IS NULL) THEN
        -- 'libelle' doit être mis à jour
        IF (src_libelle <> dest_libelle) THEN
            operation = 'update';
            hash = concat(concat(concat(concat(dest_code, '-'), dest_libelle), '-'), import_hash);
            SQL = concat('UPDATE ztemptable SET libelle = ', quote_literal(src_libelle), ', updated_on = LOCALTIMESTAMP(0) WHERE code = ', quote_literal(dest_code), ' ;');
            SQL = concat(SQL, ' UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = ''', hash, ''' ;');
            INSERT INTO import_reg(operation, TABLE_NAME, source_code, field_name, to_value, from_value, SQL, created_on, import_hash) VALUES ('update', 'ztemptable', src_code, 'libelle', src_libelle, dest_libelle, SQL, LOCALTIMESTAMP(0), hash);
        END IF;
        -- 'debut_validite' doit être mis à jour
        IF (src_debut_validite <> dest_debut_validite) THEN
            operation = 'update';
            hash = concat(concat(concat(concat(dest_code, '-'), dest_debut_validite), '-'), import_hash);
            SQL = concat('UPDATE ztemptable SET debut_validite = ', quote_literal(src_debut_validite), ', updated_on = LOCALTIMESTAMP(0) WHERE code = ', quote_literal(dest_code), ' ;');
            SQL = concat(SQL, ' UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = ''', hash, ''' ;');
            INSERT INTO import_reg(operation, TABLE_NAME, source_code, field_name, to_value, from_value, SQL, created_on, import_hash) VALUES ('update', 'ztemptable', src_code, 'debut_validite', src_debut_validite, dest_debut_validite, SQL, LOCALTIMESTAMP(0), hash);
        END IF;
        -- 'fin_validite' doit être mis à jour
        IF (src_fin_validite <> dest_fin_validite) THEN
            operation = 'update';
            hash = concat(concat(concat(concat(dest_code, '-'), dest_fin_validite), '-'), import_hash);
            SQL = concat('UPDATE ztemptable SET fin_validite = ', quote_literal(src_fin_validite), ', updated_on = LOCALTIMESTAMP(0) WHERE code = ', quote_literal(dest_code), ' ;');
            SQL = concat(SQL, ' UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = ''', hash, ''' ;');
            INSERT INTO import_reg(operation, TABLE_NAME, source_code, field_name, to_value, from_value, SQL, created_on, import_hash) VALUES ('update', 'ztemptable', src_code, 'fin_validite', src_fin_validite, dest_fin_validite, SQL, LOCALTIMESTAMP(0), hash);
        END IF;
 
    END IF;
 
    -- l'enregistrement existe dans la destination mais historisé : il sera dé-historisé
    IF (src_code IS NOT NULL AND dest_code IS NOT NULL AND dest_deleted_on IS NOT NULL) THEN
        operation = 'undelete';
        hash = concat(concat(dest_code, '-'), import_hash);
        SQL = concat('UPDATE ztemptable SET libelle = ', quote_literal(src_libelle), ', debut_validite = ', quote_literal(src_debut_validite), ', fin_validite = ', quote_literal(src_fin_validite), ', updated_on = LOCALTIMESTAMP(0), deleted_on = null WHERE code = ', quote_literal(dest_code), ' ;');
        SQL = concat(SQL, ' UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = ''', hash, ''' ;');
        INSERT INTO import_reg(operation, TABLE_NAME, source_code, field_name, to_value, from_value, SQL, created_on, import_hash) VALUES ('undelete', 'ztemptable', src_code, NULL, NULL, NULL, SQL, LOCALTIMESTAMP(0), hash);
    END IF;
 
    -- l'enregistrement existe dans la destination mais plus dans la source : il sera historisé
    IF (src_code IS NULL AND dest_code IS NOT NULL AND dest_deleted_on IS NULL) THEN
        operation = 'delete';
        hash = concat(concat(dest_code, '-'), import_hash);
        SQL = concat('UPDATE ztemptable SET deleted_on = LOCALTIMESTAMP(0) WHERE code = ', quote_literal(dest_code), ' ;');
        SQL = concat(SQL, ' UPDATE import_reg SET executed_on = LOCALTIMESTAMP(0) WHERE import_hash = ''', hash, ''' ;');
        INSERT INTO import_reg(operation, TABLE_NAME, source_code, field_name, to_value, from_value, SQL, created_on, import_hash) VALUES ('delete', 'ztemptable', src_code, NULL, NULL, NULL, SQL, LOCALTIMESTAMP(0), hash);
    END IF;
 
    RETURN operation;
END; $Q$
LANGUAGE plpgsql;
``` 
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 se fait colonne par colonne.*
différence et donc sa mise à jour se fait colonne par colonne.*

### Table `IMPORT_REG`, registre d'import

@@ -414,10 +369,11 @@ Ce registre comporte les colonnes suivantes :
  - 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 réalisant l'opération de mise à jour de la table destination
  - 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
  - la date d'exécution éventuelle de la ligne de registre (i.e. de l'instruction SQL)
  - hash permettant d'identifier cette ligne de registre pour mettre à jour sa date d'exécution
  - 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` :

@@ -436,15 +392,21 @@ Exemple de contenu de la table `import_reg` :
| 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 = $$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    | 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 = $$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    | 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 = $$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 :
  - Un "hash" (`eb1ab85c-916a-11e7-aba7-0242f725575b` par exemple) identifie un ensemble d'instructions SQL de 
    mise à jour (cf. colonne `import_hash`) qui seront exécutées au sein d'une même transaction dans la base de données
    destination.
  - Chaque instruction SQL de mise à jour de la table destination (`INSERT INTO progfin(...` par exemple) 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.*

  - 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.