Transpilez et déboguez pas-à-pas vos fonctions BW/HANA (table & scalaires) sur PostgreSQL
🏢Espace par défaut
Un environnement = un client × un système
(production, pré-production, développement…). Chacun a ses propres
tables physiques, sa bibliothèque de fonctions, ses scénarios et son
historique — rien ne circule de l'un à l'autre.
Normalement celui de l'environnement actif. Choisir
un autre système permet d'exécuter par exemple le code de
développement contre les données de
production, avant une livraison. Les tables, scénarios et
historique restent ceux de l'environnement actif.
Créer, dupliquer, supprimer un système, ou copier des
données et des fonctions de l'un à l'autre :
⇅ Espace de travail
Sauvegardez ou restaurez d'un coup toute
la bibliothèque de fonctions, les tables physiques
(structure + données) et les scénarios de
non-régression — pour transférer votre travail vers un autre
environnement ou en garder une copie de sûreté.
…
⋯
Tables d'entrée & données d'exemple
Origine des données, table par table
Toutes les tables ne jouent pas le même rôle :
mouvements, données de base,
paramétrage. Chacune peut être lue dans un système
différent le temps d'une exécution — les mouvements du bac à sable,
mais le paramétrage réel de la production. Les tables sont lues
sur place, par des vues : rien n'est copié, et les
systèmes d'origine restent en lecture seule.
objet {nom: valeur}
Variables scalaires
Valeur de chaque variable scalaire en fin d'exécution.
Une variable non résolue n'est pas substituée dans le
SQL généré : son nom y reste tel quel, ce qui provoque plus loin un
column "…" does not exist.
Analysez, transpilez ou exécutez votre fonction pour voir les résultats ici.
Chaque variable-table intermédiaire (lt_…) est
matérialisée puis affichée : c'est le pas-à-pas de débogage impossible dans HANA.
Collez ici le résultat obtenu dans HANA (copie depuis HANA
Studio : colonnes séparées par tabulation, ? = NULL) puis
comparez-le au RETURN de la dernière exécution.
Exécute la même fonction prise dans
deux systèmes différents, contre un seul et
même jeu de données — celui de l'environnement actif. Les
données étant identiques des deux côtés, tout écart constaté vient du
code, et de lui seul. C'est ce qu'on veut savoir
avant une livraison : la nouvelle version ne renvoie-t-elle pas autre
chose que celle en production ?
« Comparer l'aperçu » ne porte que sur les
500 premières lignes : sur un gros résultat il peut
conclure « identiques » alors que tout diffère au-delà. « Comparer
tout » fait calculer l'écart par PostgreSQL
(EXCEPT ALL) sur l'intégralité, sans transférer les
lignes — seul l'échantillon affiché reste borné.
Chaque « Exécuter ▶ » est enregistré
automatiquement. Choisissez deux exécutions (A = avant,
B = après) et comparez : vous voyez exactement ce que
vos changements ont modifié dans le résultat. Épinglez
(📌) une exécution pour la garder indéfiniment comme référence — les
autres sont purgées au-delà des 50 plus récentes.
puis « → scénario » sur la ligne
à figer. Attention : cela fige la sortie de debugBW comme résultat
attendu — utile pour détecter les régressions de vos
futures modifications, mais cela ne prouve pas la
conformité à HANA. À réserver à une exécution déjà validée.
Les tables physiques reconstituent le catalogue HANA
(ZCTT005, /BIC/…) : persistantes, alimentées ici, la transpilation pointe dessus.
Les tables internes (temp_…) sont produites à chaque exécution
pour tracer les étapes.
Sensible à la casse (comme les références quotées HANA). Ex : ZCTT005, /BIC/AAAD_AC4237
Modifiez pour renommer la table.
Renommez une colonne, changez son type, supprimez-la (×)
ou ajoutez-en. Tout est appliqué en une seule transaction (tout ou rien).
Recréez les tables à l'identique depuis HANA sans
ressaisir les types. 1. côté HANA, exécutez la requête
ci-dessous (ou le bouton « SQL de définition HANA » après une analyse) ;
2. copiez tout le résultat (avec l'en-tête) ;
3. collez-le ici et créez les tables.
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE_NAME, LENGTH, SCALE, POSITION
FROM SYS.TABLE_COLUMNS WHERE TABLE_NAME IN ('ZCTT005', '/BIC/AAAD_AC4237')
UNION ALL
SELECT VIEW_NAME, COLUMN_NAME, DATA_TYPE_NAME, LENGTH, SCALE, POSITION
FROM SYS.VIEW_COLUMNS WHERE VIEW_NAME IN ('ZCTT005', '/BIC/AAAD_AC4237')
ORDER BY 1, 6;
Séparateur tabulation/;/, auto-détecté. Types HANA
(NVARCHAR, DECIMAL, …) traduits en PostgreSQL, longueur/précision reprises.
Générez les requêtes d'export de données
à lancer manuellement côté HANA. 1. ajustez le schéma et
la liste ; 2. exécutez les SELECT côté HANA
(Studio) et exportez chaque résultat en CSV ; 3. importez
les CSV via l'onglet « Import CSV ».
Une table BW de production fait couramment
plusieurs millions de lignes : l'extraire en entier ne se fait pas.
Échantillonner chaque table séparément ne marcherait pas non
plus — 50 000 mouvements au hasard et 50 000 lignes de référence au
hasard ne se rejoignent sur rien, et la fonction rend zéro ligne.
Ce plan borne donc une table pilote, puis ne tire des
autres que les lignes qui s'y rattachent, d'après les jointures
écrites dans le code. Les tables qu'aucune jointure ne relie sont
signalées : elles seront extraites en entier.
Pas d'accès direct à HANA depuis l'application, mais un
script Python qui, lui, y a accès ? Générez ici un script autonome
(hdbcli) à exécuter de votre côté. 1. ajustez
le schéma et la liste de tables ; 2. lancez le script sur
un poste avec accès HANA (pip install hdbcli) ;
3. déposez les archives .zip produites
dans l'onglet « Importer une archive ». Chacune porte le nom de table,
le nombre de lignes attendu et les définitions de structure : il n'y a
rien à ressaisir, et les tables absentes sont créées au passage.
Sans la case ZIP, le script écrit des CSV nus : il
faut alors coller les définitions dans « Depuis définitions HANA », puis
importer chaque fichier via « Import CSV » (en-tête coché, séparateur
identique, virgule décimale décochée — le script écrit déjà des nombres
à point). L'import HTTP direct, lui, reste possible si ce poste atteint
aussi debugBW en HTTP, mais il paie un aller-retour réseau par lot de
5000 lignes : sur une grosse table, l'archive est nettement plus
rapide.
Le script accepte --resume (reprendre une
extraction interrompue, table par table) et --jobs N
(extraire N tables en parallèle, une connexion HANA
par worker — séquentiel par défaut). Sur un corpus de plusieurs
centaines de tables, --jobs 4 divise nettement la durée
totale ; montez plus haut avec mesure, c'est la charge sur le nœud
HANA que vous multipliez.
Même principe que le script de données, mais pour
le code : ce script lit les définitions des table
functions directement dans les vues système de HANA et les rapatrie
complètes. C'est la réponse au problème des
définitions tronquées : un export fait depuis un
client SQL plafonne les colonnes CLOB (souvent à 16 Kio) et coupe le
code au milieu, sans rien signaler. Ici la lecture passe par le
pilote, sans limite d'affichage.
Pour les start, end et expert
routines des transformations, voyez le sous-onglet
« Script Python (transformations) » : elles ne vivent
pas dans le même schéma et ne se lisent pas dans les mêmes vues.
Rapatrie les start, end et expert
routines des transformations BW — toutes celles du
schéma d'un coup si vous laissez le filtre vide.
Le code ABAP source vit sur le serveur
d'applications ; ce qui tourne réellement, et ce que HANA expose, est
la procédure générée à la compilation de la méthode
AMDP. C'est elle qu'on lit, plutôt que de demander une extraction ABAP
séparée. Son nom porte le séparateur => entre la classe
et la méthode (/BIC/CL_TRFN0001=>START_ROUTINE) : c'est
ce marqueur qui la distingue d'une procédure ordinaire, sans dépendre
d'une convention de nommage.
Pas d'import direct pour l'instant :
une transformation s'identifie par son triplet nom
technique × source × cible, et l'extraction ne lit pas encore les
métadonnées BW (RSTRAN) qui portent la source et la
cible. Importer sans elles rangerait les routines sous une identité
incomplète — ou, pire, parmi les table functions, où elles ne
s'exécuteraient jamais. Le fichier produit se reprend donc à la main :
collez une classe dans l'onglet Routines AMDP,
renseignez son triplet, et enregistrez-la dans la bibliothèque des
transformations.
Seules les trois routines
START_ROUTINE, END_ROUTINE et
EXPERT_ROUTINE sont reprises : les field routines
et les routines inverses rempliraient la bibliothèque
d'objets que debugBW ne sait pas encore reconnaître comme tels.
Le chemin rapide pour faire entrer de gros
volumes. Le script Python d'export écrit une archive
.zip par table. Déposer et intégrer sont deux
gestes distincts : une archive de plusieurs gigaoctets
arrive rarement par le navigateur, elle est souvent copiée sur le
serveur par un partage réseau ou un scp. Tout ce qui se
trouve dans le répertoire ci-dessous est listé, quelle que soit la
façon dont c'est arrivé.
L'archive porte le vrai nom de
table (les « / » des noms BW sont interdits dans un nom de
fichier, il fallait les retaper), le nombre de lignes
attendu — un CSV coupé reste un CSV valide, c'est le seul
moyen de s'en apercevoir — et, si elles ont été extraites, les
définitions de structure. Il n'y a donc ni nom, ni
séparateur, ni option à ressaisir.
Astuce : quotez les noms de tables ("ZCTT005") pour respecter la casse.
Le filtre est appliqué par PostgreSQL :
il porte sur toute la table, pas seulement sur les 200 lignes
affichées — sur une table de plusieurs millions de lignes, chercher
dans l'aperçu ne trouverait presque jamais rien. Le décompte affiché
est celui des lignes correspondant au filtre, et
« Exporter CSV » suit le filtre : ce qu'on
télécharge est ce qu'on voit.
Chaque scénario mémorise le code, les
tables d'entrée, les paramètres et le
résultat HANA attendu. « Tout rejouer » exécute tout et
compare — le filet de sécurité quand vous modifiez une fonction.
Enregistrez un scénario depuis Déboguer → Fonctions
(« Comparer avec le résultat HANA » → « Enregistrer comme scénario »).
Diagnostic statique (sans exécution) de toutes les
fonctions de la bibliothèque : lesquelles sont
exécutables, lesquelles référencent des tables manquantes
ou des fonctions HANA non traduites, lesquelles sont
couvertes par un scénario de non-régression. Cliquez un
nom pour charger la fonction dans « Déboguer ».
Plan d'extraction — par où commencer
La liste des tables manquantes ne dit pas par où
commencer, et l'ordre compte autant que la liste : les tables sont
partagées entre fonctions, et quelques-unes bien
choisies en rendent beaucoup exécutables. Choisissez une cible —
la liste correspondante est prête à extraire. Elle contient aussi
les vues de calcul et vues de
supervision interrogées par les fonctions : ce ne sont pas
des tables BW, mais elles s'extraient de la même façon. Elles
portent un préfixe de schéma (_SYS_BIC:,
SYS:) que le générateur de script sait interpréter.
Toutes les tables à extraire
Union dédoublonnée des tables
manquantes de l'ensemble du corpus : une même table servant à
plusieurs fonctions n'apparaît qu'une fois. Collez cette liste dans
Préparer → Extraire de HANA → Script Python
pour lancer une seule extraction couvrant tout le
corpus. Les objets système HANA/BW et les vues de calcul en sont
exclus : ce ne sont pas des tables à extraire.
Smoke test — est-ce que ça tourne ?non lancé
Le diagnostic ci-dessus est statique :
il dit qu'une fonction s'analyse et que ses tables sont là, pas
qu'elle s'exécute. Ce lot les exécute une à une et
rapporte où ça casse. Il ne vérifie pas la justesse
des résultats — sans référence HANA, un résultat faux passe pour bon ;
c'est le rôle des scénarios de non-régression. Les fonctions dont il
manque des tables ou des objets HANA sont ignorées,
avec leur motif : les compter en échec noierait les vrais échecs de
transpilation.
Même structure et même nombre de lignes ne veulent
pas dire même contenu. Tant qu'on ne l'a pas écarté,
on ne peut pas conclure qu'un écart de résultat vient du code — et
c'est pourtant la conclusion qu'on tire d'instinct.
L'écart est calculé par PostgreSQL
(EXCEPT ALL) : les lignes ne transitent pas, ce qui rend
la comparaison possible sur des tables de plusieurs millions de
lignes. Le verdict porte sur l'intégralité ; seul l'échantillon
affiché est borné.
Sur une table de production, la question porte presque
toujours sur un document, un client ou une période — rarement sur
les quatre millions de lignes. Les deux modes sont exclusifs.
Sans clé, une ligne modifiée apparaît
deux fois — une fois « absente de B », une fois « en trop en B » —
et c'est à l'œil de faire le rapprochement. Avec une clé, elle est
dite modifiée et l'on montre la colonne qui change.
La clé doit être unique des deux côtés, sinon l'appariement
multiplierait les lignes : ce cas est refusé, pas arrondi.
Le banc de non-régression ne protège de rien
tant qu'il est vide : sans résultat attendu, une transpilation peut
dériver sans que rien ne le dise. La seule référence qui fasse foi
est ce que la fonction rend côté HANA. Ce script,
à exécuter depuis un poste ayant accès à HANA, exécute chaque
fonction là-bas et enregistre son résultat comme attendu.
Il vérifie aussi, table par table, que HANA et
debugBW voient le même nombre de lignes. Comparer un
résultat calculé sur deux millions de lignes à un résultat calculé
sur un échantillon de 50 000 n'a aucun sens : le banc afficherait un
échec qu'on imputerait au transpileur. L'écart est signalé, et le
scénario reste enregistré pour plus tard.
→
⋯
Collez la classe générée par la transformation
(CLASS /BIC/CL_TRFN… IMPLEMENTATION), ou seulement le bloc
METHOD … BY DATABASE PROCEDURE … ENDMETHOD. Avec la section
DEFINITION, la signature et les colonnes attendues du
paquet sont lues au passage.
Origine des données, table par table
Chaque table lue par la routine peut venir d'un autre
système, le temps de l'exécution. La lecture se fait
sur place, par des vues : rien n'est copié, et les
systèmes d'origine restent en lecture seule.
Le paquet n'existe pas hors de BW : c'est le
moteur de transformation qui le fournit. On le reconstitue depuis une
table déjà extraite — la table active de l'ADSO source, en général.
Variables scalaires
Paramètres IMPORTING de la routine
(request, datapackid…) et scalaires
déclarés dans son corps. Une variable non résolue
n'est pas substituée dans le SQL : son nom y reste tel quel, et
l'étape suivante échoue sur un déroutant
column … does not exist.
Exécute la même transformation prise
dans deux systèmes différents, sur un seul et
même paquet de données. Les données étant identiques des deux
côtés, tout écart constaté vient du code, et de lui
seul. C'est ce qu'on veut savoir avant de transporter une
transformation : va-t-elle alimenter l'ADSO cible autrement que celle
déjà en place ? Un écart de comportement s'y matérialise en
données fausses, pas en erreur — il ne se verrait
jamais tout seul.
La transformation est cherchée dans la
bibliothèque de chaque système, par le triplet
nom × source → cible saisi à gauche — deux systèmes
peuvent porter le même nom technique sans que ce soit le même flux.
Le paquet et les paramètres sont ceux de l'écran, appliqués aux deux
côtés.
Chaque « Exécuter la routine ▶ » est
enregistré automatiquement. Choisissez deux exécutions
(A = avant, B = après) et comparez :
vous voyez exactement ce que vos changements ont modifié dans le
contenu d'outTab. Épinglez (📌) une
exécution pour la garder indéfiniment comme référence — les autres
sont purgées au-delà des 50 plus récentes.
Ne sont listées ici que les exécutions de
transformations : celles des table functions ont leur
propre historique, dans l'écran « Fonctions ». Comparer une transformation
à une fonction ne voudrait rien dire, et le serveur le refuse.
Un système = un client × un
environnement (production, pré-production, développement…).
Chacun a ses propres tables physiques, sa
bibliothèque de fonctions, ses scénarios et son
historique, dans un schéma PostgreSQL distinct : rien ne circule de
l'un à l'autre sans qu'on le demande, ci-dessous.
Un système vide, prêt à recevoir des tables et des
fonctions. Pour repartir du contenu d'un système existant, utilisez
plutôt Dupliquer ci-dessous.
Deux questions qui reviennent sans cesse avant une
livraison : le code est-il le même partout, et
les tables le sont-elles ? Rien n'est exécuté ici : on
lit et on compare. Les systèmes sont confrontés ensemble
et non deux par deux — chez un client qui a PROD, PPROD et DEV, la vraie
question est « où est l'intrus ».
En HANA, SESSION_CONTEXT('CLIENT')
rend le mandant de la session — d'où le
WHERE mandt = SESSION_CONTEXT('CLIENT') omniprésent dans le
code ABAP/BW. Ce n'est pas une donnée du code mais une propriété du
système, et elle diffère de l'un à l'autre : elle se
renseigne donc ici. Tant qu'une clé utilisée manque, l'exécution
échoue en le disant plutôt que de rendre
NULL — ce qui viderait le résultat sans prévenir.
Crée un nouveau système et y recopie le contenu
d'un système existant — pour ouvrir un bac à sable sur une copie de la
production sans jamais toucher à l'original.
La copie est faite par PostgreSQL,
d'un schéma à l'autre : les lignes ne transitent pas par l'application,
une table de dix millions de lignes coûte donc le même trajet qu'une
table de dix. Le système source n'est jamais modifié.