diff --git a/README.md b/README.md index 4487c0a1864731348b298fd7b4524eb202872ae0..68706c8a1906bdb7c9e039df0a0c64d7f9ff7ae2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ 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 : - - - 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) ; +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", 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 `'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`). -- 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 +### 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; ``` @@ -177,6 +189,13 @@ EOT * 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 : @@ -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.