Loading README.md +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 : Loading @@ -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). Loading Loading @@ -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 : Loading @@ -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; ``` Loading Loading @@ -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 Loading Loading @@ -225,7 +244,8 @@ return [ 'password' => 'y', 'dbname' => 'z', 'charset' => 'AL32UTF8', ] ], 'eventmanager' => 'orm_oracle', ], 'orm_default' => [ 'driverClass' => 'Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver', Loading @@ -240,6 +260,13 @@ return [ ], ], ], 'eventmanager' => [ 'orm_sifac' => [ 'subscribers' => [ \Doctrine\DBAL\Event\Listeners\OracleSessionInit::class, ], ], ], ], ]; ``` Loading Loading @@ -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 ; ``` Loading @@ -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 Loading @@ -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` : Loading @@ -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. Loading
README.md +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 : Loading @@ -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). Loading Loading @@ -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 : Loading @@ -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; ``` Loading Loading @@ -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 Loading Loading @@ -225,7 +244,8 @@ return [ 'password' => 'y', 'dbname' => 'z', 'charset' => 'AL32UTF8', ] ], 'eventmanager' => 'orm_oracle', ], 'orm_default' => [ 'driverClass' => 'Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver', Loading @@ -240,6 +260,13 @@ return [ ], ], ], 'eventmanager' => [ 'orm_sifac' => [ 'subscribers' => [ \Doctrine\DBAL\Event\Listeners\OracleSessionInit::class, ], ], ], ], ]; ``` Loading Loading @@ -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 ; ``` Loading @@ -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 Loading @@ -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` : Loading @@ -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.