Ecriture d'une procédure de migration spécifique 

A partir de la version 6.4. de Sage X3, la migration d'une version antérieure vers la version 6 s'appuie sur un moniteur d'enchaînement lançant des procédures de migration unitaires de façon automatisée.

Il est possible d'ajouter des procédures de migration spécifiques, en suivant les préconisations données dans cette annexe technique.

En l'occurrence, rajouter des procédures de migrations spécifiques se fera en :

  • écrivant un traitement de migration dont la description détaillée et les règles de nommage sont précisées ci-dessous.
  • référençant ce traitement comme une procédure de migration dans le dossier superviseur par la fonction correspondante, ce avant toute création d'un plan de migration et tout lancement de revalidation d'un dossier.

Cette documentation décrit en détail la structure d'un tel traitement de migration, mais on pourra aussi, si nécessaire, se référer au traitement UUMGTRTPUR01, qui réalise la migration des demandes d'achat, et dont le source est fourni à titre d'exemple (sa modification - au risque de rendre la procédure de migration inopérante - est bien entendu prohibée).

Principes de base

Lors de la validation de dossier:

  • chaque table de flux est renommée (par défaut en faisant précéder son nom par la lettre U). On leur affecte le code activité MIG (ce qui permettra de les identifier lors de la suppression finale des tables temporaire, une fois que la migration aura été validée).
  • son contenu est bien entendu gardé identique à la table de flux d'origine
  • ses index sont supprimés (à l'exception du premier d'entre eux et éventuellement d'un index supplémentaire qui serait utile pour accélérer le traitement de migration).
  • une ou des nouvelle(s) table(s) de flux sont créées vides conformément au dictionnaire de la nouvelle version (en y intégrant bien entendu les champs spécifiques ou les modifications spécifiques de champs standard faites sur la table d'origine). Souvent, la table vide porte le même nom que la table d'origine, mais ce n'est pas forcément le cas (cette table peut ne plus exister dans la nouvelle version, par exemple parce que les données qu'elles contenait ont été transférées dans une nouvelle table).

Il est possible de gérer des migrations de tables spécifiques avec le même mécanisme. Ceci est décrit dans le paragraphe ci-dessous.

Le sous-programme suivant permet de connaître le nom de la table telle qu'elle a été renommée :
     # FOLDER = code du dossier
     # PTABLE = nom de la table de mouvement avant migration
     # MTABLE = nom de la table de mouvement renommée
     # Si ERR>0  : une erreur s'est produite (le traitement devra s'arrêter)
     Call MIGTABNAME(FOLDER,PTABLE, MTABLE, ERR) From TRTMIG

La règle de base de renommage (mais elle a des exceptions, notamment si le nom de la table excède 12 caractères) consiste à renommer la table XXX en la préfixant par un "U". Ainsi, une table nommée ZMYTABLE sera renommée en UZMYTABLE. Le point d'entrée TABNAME dans TRTMIG permet d'affecter MTABLE en connaissant PTABLE avec une règle éventuellement différente.

Chaque procédure unitaire est définie par un programme migrant une ou plusieurs tables de la base. Ce traitement doit s'appeler UUMGSPExxxnn, xxx étant une racine identifiant la table a migrer et nn étant deux chiffres (pour permettre de disposer si nécessaire de plusieurs traitements). Son principe de fonctionnement est le suivant :

  • Il va lire la ou les tables d'origine (en trouvant leur nom grâce au sous-programme ci-dessus), mais pas les mettre à jour.
  • Il doit écrire dans la ou les table(s) finale(s), les données modifiées, complétées ou transcodées. Ces tables finales ont été décrites dans le dictionnaire de la nouvelle version. Elles existent, mais elles sont réputées être vides au début de la migration.
  • Il est le plus "unitaire" possible, afin de permettre, lorsque c'est possible, une décomposition de la migration en procédures parallélisables.

Afin d'obtenir les meilleures performances possibles :

  • la table finale est livrée vide au départ, et elle est indexée a minima (un seul index). Les autres index seront créés à la fin de la procédure unitaire.
  • on permettra de réaliser le traitement partiel des informations de départ (par exemple en ne migrant que les 2 derniers exercices), et en permettant le traitement ultérieur des données non migrées.
  • l'écriture dans cette table se fera de préférence en mode groupé (en utilisant les syntaxes Writeb), en faisant des Commit sur la table à chaque paquet de N écritures (N étant fixé à une valeur raisonnable).
  • la trace de migration sera mise à jour de façon optimisée.

Afin de permettre une reprise et une interruption aisée des procédures unitaires :

  • on écrira dans une table dédiée, à chaque Commit, un pointeur indiquant quelles sont les données déjà traitées, ainsi que des informations complémentaires.
  • on testera, à chaque Commit, si une demande d'interruption a été faite.
  • on écrira la procédure de telle façon qu'elle puisse être réexécutée en repartant de tables destination vides.

Description du traitement de migration

Dans le traitement de migration, on trouvera les éléments suivants :

  • Au début, des lignes permettant le déclenchement direct de la procédure.
  • Un sous-programme RAZ_UUMGSPExxxnn qui servira à remettre la situation initiale afin de pouvoir. relancer la procédure.
  • Un sous-programme MAJ_UUMGSPExxxnn contenant la procédure de migration.
  • Un sous-programme PATCH.
  • Un sous-programme UTI_MOUL.

Ces sous-programmes sont décrits ci-dessous.

On supposera dans les exemples donnés que le traitement UUMGTRTZMY01 correspond à la phase de migration unitaire d'une table unique, qui s'appelle ZMYTABLE. La phase préliminaire de revalidation de dossier a fait les choses suivantes :

  • elle a renommé ZMYTABLE conformément à la règle définie ci-dessus (la table remplie avec les valeurs avant migration s'appelle par défaut UZMYTABLE).
  • Elle a créé la nouvelle table ZMYTABLE avec la nouvelle structure, mais cette table est vide.

La procédure de migration va s'attacher à remplir la table ZMYTABLE à partir des informations de la table UZMYTABLE.

Les lignes préliminaires

Les lignes préliminaires permettent de lancer le traitement directement depuis l'éditeur, par la fonction Exécution. On y trouve normalement les lignes suivantes :

# Définition du dossier courant, et lecture de la table des dossiers
Local Char FOLDER(30) : FOLDER = nomap
# Nom de la procédure de migration
Local Char PROG(20) : PROG="UUMGTRTZMY01"
If !GSERVEUR
  Call SAIDOS(FOLDER,"") From SAIDOS
Endif
If FOLDER<> ""
  If clalev([F:ADS])=0  : Local File ADOSSIER [ADS] : Endif
  Read [ADS] DOSSIER=[L]FOLDER : If fstat : Raz [F:ADS] : Endif
  # Ouverture de la trace et affichage d'une fenêtre de temporisation
  If !GSERVEUR
    Call TEMPON("O") From GESECRAN
    Call OUVRE_TRACE(PROG) From LECFIC
  Endif
  # Lancement de la procédure
  Call MAJ_UUMGSPEZMA00(FOLDER)
  # Fermeture trace
  If !GSERVEUR
    Call TEMPOFF     From GESECRAN
    Call FERME_TRACE From LECFIC
    Call LEC_TRACE From LECFIC
  Endif
Endif
End

Le sous-programme de remise à zéro

Le sous-programme déclaré par
      Subprog RAZ_UUMGTRTZMY01(FOLDER)
sert à se remettre dans la situation initiale si nécessaire, afin de pouvoir relancer la procédure.

Il doit suivre au minimum les étapes de base suivantes :

  • Vérifier que les tables d'origine et de destination existent bien, par exemple avec les lignes suivantes :
          # Trouver la table d'origine pour la table à remplir (ici UZMYTABLE)
          Call MIGTABNAME(FOLDER,"ZMYTABLE",OLDTABLE,ERR) From TRTMIG
          If ERR : End : Endif
          # La table d'origine existe-t-elle bien ?
          If filinfo(filpath("FIL",OLDTABLE,"fde",[F:ADS]DOSSIER),0)<0
               End
          Endif
  • Ne faire des traces que pour les erreurs (aucun message d’avancement ou avertissement)
  • Vider la ou les tables dans lesquelles la procédure MAJ insère les données. Pour ce faire, on dispose du sous-programme suivant:
          # Vidage brutal et irréversible de la table ZMYTABLE
          Call MIGTABRAZ(FOLDER,"ZMYTABLE",ERR)
          If ERR : End : Endif
  • Si des tables communes à plusieurs traitement doivent être partiellement purgées, on utilisera des transactions classiques du type :
           Trbegin [...] : Delete [...] Where ... : Commit
  • Remettre à zéro les informations sur le flux déjà migré pour cette table, par le sous-programme suivant :
          # Pas de lignes migrées à cet instant
          Call MIGRAZKEY(FOLDER,"UUMGTRTZMY01",ERR) From TRTMIG

Le sous-programme de mise à jour

Le sous-programme MAJ_UUMGTRTZMY01, servant à transcoder le contenu de la table UZMYTABLE pour remplir la table ZMYTABLE, doit réaliser les tâches suivantes :

  • Le nom de la procédure de migration doit être défini au départ
    Local Char PROG(20) : PROG="UUMGTRTZMY01"
  • On mettra à jour le statut de la procédure avec le sous-programme suivant (y compris si une condition préliminaire rend le lancement inutile, par exemple s'il n'y a rien à traiter : il faut alors que le statut soit Terminé pour que la procédure soit considérée comme faite et que les suivantes puissent s'enchaîner). Sinon, on mettra le statut à En cours :
          # CURSTAT variable Integergérant le statut (menu local 21)
          # Les valeurs possibles étant Attente, En cours, Terminée...
          # Si la procédure est lancée, on aura CURSTAT=2
          # Si elle est terminée, on utilisera CURSTAT=3
          # En cas d'erreur, il faudra la positionner à 7
          # Dans la suite, la variable PROG contient le nom du de la procédure
          #  (UUMGTRTZMY01 par exemple)
          Call MIGSTKENDFLG (DOSSIER,PROG,CURSTAT,ERR) From TRTMIG
  • On testera la présence des tables et des tables antérieures en utilisant le sous-programme MIGTABNAME à l'instar de ce qui est fait dans le sous-programme d'initialisation. En cas d'erreur, il ne faut pas oublier de mettre le statut de la tâche à la bonne valeur (7) avec le sous-programme MIGSTKENDFLG.
  • On ouvrira ensuite les tables nécessaires à la migration. L'ouverture des tables internes de la procédure de migration se fait par :
          Call MIG_OUVRE From TRTMIG
  • Les traces seront utilisées avec parcimonie (inutile d'en mettre une sur chaque ligne, on a vu des migrations s'arrêter faute de place disque pour écrire les traces). Il est par contre utile de mettre une trace commençant par un message de type "Démarrage de la phase mettant à jour xxx", ainsi qu'une ligne d'horodatage avec un message tel que "Début du processus à :". Ces traces utiliseront les sous-programmes suivants :
          # Ligne de trace si ERR=0, ligne d'erreur sinon
          Call ECR_TRACE("message",ERR) From GESECRAN
          # Ligne de trace horodatée
          Call ECR_TIME("message") From DOSSUB
  • Afin d’améliorer le temps d’insertion dans les tables, on supprime les index en ne laissant que le premier actif en début de procédure et on les recrée en fin de procédure, par les sous-programmes :
          Call MIGTABINIT(FOLDER,"ZMYTABLE",ERR) From TRTMIG
    Mais cela impose que la procédure soit la seule à travailler sur la table au moment où elle s’exécute. Aucune autre procédure ne doit lire, modifier ou insérer dans la table durant l’exécution de la procédure. Si la table doit être accédée par plusieurs procédures, il ne faut pas utiliser ce sous-programme.

  • On retrouvera alors la première clé à traiter dans la boucle de traitement. En effet, si on reprend la migration après une interruption, cette clé n'est pas vide. On la retrouvera par le sous-programme décrit ci-dessous :

           # TBMKEY(1..NBMKEY) est un tableau des valeurs de clés
           #   où reprendre la lecture. On retrouve au minimum les composants
           #   de la dernière valeur de clé traitée, mais on peut y rajouter
           #   un segment qui pourra indiquer le niveau de rupture atteint
           #   sur une clé multiple. TBMKEY doit être vide avant l'appel
           #   S'il est vide au retour, aucune valeur de clé courante n'existait
           #   (on démarre alors la procédure au début).
           # NBMKEY permet de définir le nombre de valeurs que l'on attend
           # NBL et NBUP comptent les lignes lues et mises à jour jusque là
           Call MIGGETKEY (FOLDER,PROG,NBMKEY,TBMKEY,NBL,NBUP,ERR)
           &    From TRTMIG
  • On pourra alors entrer dans la boucle de lecture de la table ou des tables d'origine et d'écriture des tables de destination. Pour des raisons de performance, il est préférable de bannir les ordres de lecture isolés sur des tables et privilégier les instructions Link, de préférence avec des jointures strictes (syntaxe Where CLE~=... ). Si une lecture doit être faite sur une "petite" table pour des raisons d'initialisation ou de contrôle (par "petite", on entend moins de 1000 lignes), il est souvent préférable de stocker le contenu utile de cette table dans des variables en mémoire avant de commencer la boucle.
  • Afin d'éviter des erreurs liées à l'écriture d'un trop grand nombre de lignes, on comptera le nombre de lignes écrites et on interrompra la boucle toutes les N lignes (N pouvant être fixé à 50.000 par exemple, par l'intermédiaire de la variable globale GMAXUPDTRS).
  • Pour des raisons de performance, on utilisera aussi de préférence l'instruction Writeb (qui a été introduite à partir du moteur utilisé en version 6.4). Cette instruction groupe les écritures et améliore les performances, notamment mais pas uniquement sur une architecture multi-tiers. Cette instruction n'est utilisable que si on ne relit pas la table dans la boucle (ex : relecture pour savoir si enreg existe déjà). Elle suppose de fixer le facteur de groupage par une variable système nommée adxwrb (10 semble une bonne valeur). Il faut alors empiler les valeurs de clés écrites pour pouvoir restituer l'ensemble des clés en cours en cas d'erreur (cette erreur se produit alors sur l'instruction Flush et non pas sur Writeb). Le sous-programme suivant peut être utilisé :
            # Empilement des clés.
            # KEY_ARRAY est un tableau alphanumérique déclaré par
            # Local Char KEY_ARRAY(N)(adxwrb)  en début de traitement
            # (la taille N dépend de la taille des valeurs de clés)
            # NUMBER une variable Shortintindiquant le nombre de clés empilées
            # KEY_VALUE valeur de clé encodée si la clé est en plusieurs parties
            Call STK (KEY_ARRAY, NUMBER,KEY_VALUE) From TRTMIG
  • En cas de sortie de boucle (après N boucles, ou parce que le traitement est terminé), on fera un Flushsur la table (si Writebest utilisé) en gérant une éventuelle erreur. Si une erreur s'est produite, et si adxwrbest supérieur à 1, on pourra retrouver les clés sur lesquelles l'erreur a pu se produire dans le tableau KEY_ARRAY, sur les adxwrbpremiers indices : on fera un Rollback avant de mettre la procédure en l'état Terminé avec erreurs et de terminer le traitement.
  • Si tout s'est bien passé, on mettra à jour le nombre de lignes lues et mises à jour par l'appel suivant:
         Call MIGSTKKEY (FOLDER,PROG,NBMKEY,TBMKEY,NBL,NBUP,ERR)
         &    From TRTMIG
    On fera ensuite un Commit.
  • On pourra ensuite tester si une demande d'arrêt a été faite. Si ce n'est pas le cas, et si toutes les lignes n'ont pas été traitées, on recommencera une boucle sur N lignes. Le test d'arrêt se fait par le sous-programme suivant :
          # La variable STOP revient égale à 0 si une demande d'arrêt a été faite
          Call MIGSTKPROGRESS (FOLDER,PROG,STOP,ERR) From TRTMIG
  • Si une demande d'arrêt a été faite (ou si le traitement est terminé), on fera les opérations suivantes :
          # Indexation de la ou des tables traitées
          # (uniquement si MIGTABINIT a été utilisé)
             Call MIGTABEND(FOLDER,"ZMYTABLE",ERR) From TRTMIG
          # Signaler au moniteur la fin du traitement
             Call MIGTRTEND(FOLDER,PROG) From TRTMIG
             Call ECR_TIME(PROG+" : Tâche terminée") From DOSSUB

On pourra ensuite fermer les tables et libérer des ressources si on en a, puis arrêter le sous-programme par l'instruction End.

Le sous-programme PATCH

Ce sous-programme permet un lancement par patch. Il doit simplement s'assurer que la table ADOSSIER est ouverte, lire l'enregistrement correspondant au dossier à traiter, puis lancer la procédure de migration. Il pourrait, par exemple, être écrit comme suit :

Subprog PATCH(FOLDER)
Value Char FOLDER
# Pas de lancement sur le dossier superviseur
If FOLDER=GDOSX3 : End : Endif

#Chargement de la classe [F:ADS]
If clalev([F:ADS])=0  Local File ADOSSIER [ADS] : Endif
Read [ADS] DOSSIER=FOLDER : If fstat : Raz [F:ADS] : Endif
# Lancement de la procédure
Call MAJ_UUMGTRTZMY01(FOLDER)
End


Le sous-programme UTI_MOUL

Ce sous-programme permet un lancement lors de la validation de dossier. Il est assez similaire au sous-programme PATCH, mais n'a pas besoin de s'assurer que la table ADOSSIER est ouverte, ni de lire l'enregistrement correspondant au dossier à traiter, puisque ceci est assuré par le traitement appelant. Sa seule tâche est donc de lancer la procédure correspondante. Il pourrait, par exemple, être écrit comme suit :

Subprog UTIL_MOUL(FOLDER)
Value Char FOLDER
# Pas de lancement sur le dossier superviseur
If FOLDER=GDOSX3 : End : Endif

# Lancement de la procédure
Call MAJ_UUMGTRTZMY01(FOLDER)
End


Le point d'entrée MIGTAB

Si on désire que des tables de flux spécifiques soient gérées par le traitement de migration d'une façon analogue (c'est-à-dire renommées de la même façon dans le début de la validation de dossier, puis créées avec la nouvelle structure), les étapes suivantes sont à faire :

  • il faut utiliser le point d'entrée MIGTAB pour indiquer qu'une table de flux spécifique devra être traitée de la même façon que les tables standard dans la première phase (renommage et suppression des index superfétatoires).
  • Il  faut créer dans le dossier superviseur (ou dans le dossier de référence si on est dans une architecture à 3 niveaux) la description des tables spécifiques dont la structure change lors du changement de version, sinon les nouvelles tables de flux ne seront pas créées vides avec la nouvelle structure.
  • Ceci est une exception un peu particulière à la règle qui indique que, normalement on ne crée pas de table spécifique dans le dossier superviseur. La raison en est simple : on a bien besoin de disposer d'un dictionnaire "de référence" dans ce cas particulier. Or le seul dont on dispose, si on est dans une architecture à deux niveaux (cas le plus fréquent) est celui du dossier "superviseur" (X3 dans le cas de Sage X3). Il est d'ailleurs à noter que ces tables spécifiques créées dans le dossier superviseur peuvent être écrasées sans préavis si on installe une nouvelle version du dossier superviseur. Il faut donc considérer que la présence de tables spécifiques dans X3 est temporaire et qu'une sauvegarde de la description de ces tables doit exister ailleurs.
  • Une table spécifique pouvant être utilisée pour alimenter des champs spécifiques d'une table standard, il n'est pas forcément nécessaire de créer une nouvelle table spécifique lors du changement de version. Dans ce cas, on renommera la table spécifique en utilisant le point d'entrée MIGTAB, et on utilisera des points d'entrée dans les procédures standard ou des procédures complémentaires pour faire les mises à jour qui manqueraient sans créer de table nouvelles dans le dictionnaire d'arrivée.

Si ce point d'entrée n'est pas utilisé, les tables spécifiques, protégées par un code activité spécifique, ne seront absolument pas touchées. On les retrouvera dans l'état d'origine, ce qui correspondra souvent à ce qui doit être fait par défaut si le changement de version n'a pas d'incidence sur la structure des tables spécifiques.

De même tout champ spécifique présent dans une table de flux standard et dont le contenu devrait être transféré à l'identique ne nécessite aucune précaution particulière : les traitements standard de migration utilisent des affectations de classes pour réaliser les copies de données.

Le nom du traitement où se trouve le point d'entrée MIGTAB dépend du progiciel basé sur SAFE X3. Dans le cas de Sage X3, ce point d'entrée MIGTAB se trouve dans le traitement TRTMIGTABX3.

On y ajoutera dans ce traitement des lignes de ce type :
    Call MIGTABTABADD(DOSSIER,"ZMYTABLE","ZMY","index",
&         NBTAB,WTABLE,WTABLEJ,WABRJ,WTABIDX,ERR) From TRTMIG

En sachant que les noms de variables donnés ci-dessus sont pour la plupart fixes :

  • DOSSIER est le code du dossier.
  • le nom de la table à migrer donné comme exemple ici est "ZMYTABLE".
  • "ZMY" correspond à l'abréviation de la table d'origine après qu'elle ait été renommée. Si cette valeur est vide, une abréviation temporaire est déterminé, sous la forme U## ou W##, où ## est un nombre.
    Dans tous les cas cette abréviation n’est pas utilisée dans les traitements: Dans les procédures, on utilisera une autre abréviation temporaire comme [XXXM] où XXX est l’abréviation de la table migrée.
  • NBTAB est la variable contenant le nombre de table de flux définies comme devant être migrées (chaque appel à TRTMIG incrémente cette variable).
  • WTABLE, WTABLEJ, WABRJ, WTABIDX sont des tableaux contenant l'ensemble des éléments liés aux tables de flux déjà déclarées.
  • ERR renvoie une valeur non nulle en cas d'erreur.

Les points d'entrée des traitements de migration standard

Chaque procédure de migration standard dispose par ailleurs de points d'entrée permettant d'interagir avec la logique de basculement des tables standard. Ces points d'entrée sont les suivants :

  • CRITSEL permet d'alimenter la variable CRITERESPE pour filtrer les données à traiter. Le détail du contexte utilisable est défini dans l'annexe décrivant les procédures de migration.
  • VERIFSEL permet de recalculer des zones de la table d'origine en cours de lecture durant le traitement, et éventuellement d'exclure un enregistrement qui ne sera donc pas réécrit en mettant ISVALID à 0. Le détail du contexte utilisable est défini dans l'annexe décrivant les procédures de migration.