Par exemple, vous utilisez SQL Error Logging Facility (SELF).
Vous voulez analyser les erreurs générées
Les erreurs sont stockées dans la vue SQL_ERROR_LOG de QSYS2
Vous avez une zone LOGGED_SQLCODE et une LOGGED_SQLSTATE qui contiennent le SQLCODE et le SQLSTATE de l’erreur
Vous voulez connaitre l’explication sur l’erreur SQL, vous avez des fonctions table SQLCODE_INFO et SQLCODE_STATE qui interprètent ce code
exemple :
SELECT LOGGED_SQLCODE, STATEMENT_TEXT, STATEMENT_OPERATION_DETAIL, PROGRAM_LIBRARY, PROGRAM_NAME FROM QSYS2.SQL_ERROR;
Ajouter la Fonction table
SELECT LOGGED_SQLCODE, (SELECT Message_text FROM TABLE ( SYSTOOLS.SQLCODE_INFO(LOGGED_SQLCODE) )) AS SQLCODE_TEXT, STATEMENT_TEXT, STATEMENT_OPERATION_DETAIL, PROGRAM_LIBRARY, PROGRAM_NAME FROM QSYS2.SQL_ERROR;
Vous pouvez le faire sur le SQLSTATE ou sur le SQLCODE
Autre exemple, dans une programme RPGLE vous pouvez faire ceci par exemple
W_Text_sql char(512) ; W_sqlcode like(sqlcode) ;
// traitement des erreurs SQL
if sqlcode <> 0; w_sqlcode = sqlcode; exec sql // calcul du nombre de job à supprimer SELECT Message_text into :W_Text_sql FROM TABLE ( SYSTOOLS.SQLCODE_INFO(:w_sqlcode)) ; // Ecriture message dans la log snd-msg (W_Text_sql) ; endif ;
Pour analyser des journaux la difficulté est de traiter le poste *entry data qui est spécifique à chaque fichier
La tr2 de la v7R6 et la tr8 de la v7r5 permet de résoudre cette problématique en créant une fonction spécifique sur un fichier pour analyser les postes de TYPE R
Voici un petit protocole de test
— création d’un schéma
create schema testjrn ;
création d’une table journalisée par défaut sur qdftjrn
CREATE TABLE TESTJRN/TESTABLE ( NUMERO INT NOT NULL WITH DEFAULT, NOM CHAR ( 50) NOT NULL WITH DEFAULT, PRENOM CHAR ( 50) NOT NULL WITH DEFAULT, DATMAJ TIMESTAMP NOT NULL WITH DEFAULT) ;
— génération de modifications dans le journal
INSERT INTO TESTJRN/TESTABLE VALUES(1, ‘Berthoin’, ‘Pierre-Louis’, current timestamp);
INSERT INTO TESTJRN/TESTABLE VALUES(2, ‘Berthoin’, ‘Younes’, current timestamp) ;
INSERT INTO TESTJRN/TESTABLE VALUES(3, ‘Mbappé’, ‘Kilian’, current timestamp) ;
INSERT INTO TESTJRN/TESTABLE VALUES(4, ‘Chombier’, ‘Patrice’, current timestamp) ;
UPDATE TESTJRN/TESTABLE SET PRENOM = ‘Robert’ WHERE NUMERO = 4;
DELETE FROM TESTJRN/TESTABLE WHERE NUMERO =3 :
— génération de la fonction table spécifique à votre fichier
Vous voulez savoir le droit qu’un utilisateur aura quand il tentera d’accéder à un fichier IFS
Voici un script avec les requêtes qui vous aidera à déterminer le droit que vous obtiendrez.
Vous aurez 8 étapes possibles , et il s’arrêtera à la première correspondance.
--
-- Voici une liste de commande SQL pour trouver le droit d'un utilisateur
-- Sur un fichier IFS
-- ici l'utilisateur PLB
-- tente d'accéder au fichier
--/home/plb/Controle_demarrage_SBS.csv
-- 1 si user *ALLOBJ
-- Si oui tous les droits
--
SELECT SPECIAL_AUTHORITIES,
FROM qsys2.user_info
WHERE AUTHORIZATION_NAME = 'PLB';
--
-- 2 si user sur l'objet
--
SELECT *
FROM TABLE (
QSYS2.IFS_OBJECT_PRIVILEGES('/home/plb/Controle_demarrage_SBS.csv')
)
WHERE AUTHORIZATION_NAME = 'PLB';
--
-- 3 si utilisateur dans liste d'autorisations
--
SELECT *
FROM QSYS2.AUTHORIZATION_LIST_USER_INFO
WHERE AUTHORIZATION_NAME = 'PLB'
AND AUTHORIZATION_LISt = (SELECT AUTHORIZATION_LIST
FROM TABLE (
QSYS2.IFS_OBJECT_PRIVILEGES('/home/plb/Controle_demarrage_SBS.csv')
)
WHERE AUTHORIZATION_NAME <> '*PUBLIC');
--
-- 4 si groupe all obj
-- Si oui tous les droits
--
SELECT
SPECIAL_AUTHORITIES
FROM qsys2.user_info
WHERE AUTHORIZATION_NAME = ( SELECT GROUP_PROFILE_NAME
FROM qsys2.user_info
WHERE AUTHORIZATION_NAME = 'PLB');
--
-- 5 si groupe sur l'objet
--
SELECT * FROM TABLE(QSYS2.IFS_OBJECT_PRIVILEGES('/home/plb/Controle_demarrage_SBS.csv'))
where AUTHORIZATION_NAME =
( SELECT GROUP_PROFILE_NAME
FROM qsys2.user_info
WHERE AUTHORIZATION_NAME = 'PLB');
--
--6 Si groupe dans la liste d'autorisation
--
SELECT *
FROM QSYS2.AUTHORIZATION_LIST_USER_INFO
WHERE AUTHORIZATION_NAME = (SELECT GROUP_PROFILE_NAME
FROM qsys2.user_info
WHERE AUTHORIZATION_NAME = 'PLB')
AND AUTHORIZATION_LIST = (SELECT AUTHORIZATION_LIST
FROM TABLE (
QSYS2.IFS_OBJECT_PRIVILEGES('/home/plb/Controle_demarrage_SBS.csv')
)
WHERE AUTHORIZATION_NAME <> '*PUBLIC');
--
--7 Si public
--
SELECT *
FROM TABLE (
QSYS2.IFS_OBJECT_PRIVILEGES('/home/plb/Controle_demarrage_SBS.csv')
)
WHERE AUTHORIZATION_NAME = '*PUBLIC'
AND DATA_AUTHORITY <> '*AUTL';
--
--8 Si public reporté sur la liste d'autorisation
--
SELECT *
FROM QSYS2.AUTHORIZATION_LIST_USER_INFO
WHERE AUTHORIZATION_NAME = '*PUBLIC'
AND AUTHORIZATION_LISt = (SELECT AUTHORIZATION_LIST
FROM TABLE (
QSYS2.IFS_OBJECT_PRIVILEGES('/home/plb/Controle_demarrage_SBS.csv')
)
WHERE AUTHORIZATION_NAME <> '*PUBLIC');
Remarque :
Ce mécanisme peut être complété par des groupe additionnels , les droits proposés devenant complétifs (‘Ajouter au droit existant’)
Il est conseillé de les utiliser modérément , on peut avoir un système de droit assez efficace juste avec le mécanisme historique
Dans l’IFS l’adoption de droit ne s’applique pas, mais dans vos sript sh vous pouvez utiliser la notion de SETUID
https://www.gaia.fr/wp-content/uploads/2017/02/team3.png600600Pierre-Louis BERTHOIN/wp-content/uploads/2017/05/logogaia.pngPierre-Louis BERTHOIN2026-07-03 10:13:032026-07-03 10:13:04Droit d’utilisateur sur un fichier IFS
Pour connaitre le type d’un fichier, vous pouvez vous baser sur le type du Fichier .PDF, .JPG, etc …
Ou vous baser sur le nombre magique , ou signature binaire soit les 4 premiers octets en Hexa
SELECT HEX(SUBSTR(LINE, 1, 4)) AS SIGNATURE FROM TABLE(QSYS2.IFS_READ_BINARY(‘/home/test.pdf’)) FETCH FIRST 1 ROW ONLY;
Voici un exemple sur 4 fichiers que vous pouvez trouver sur votre partition
SELECT CASE
WHEN HEX(SUBSTR(LINE,1,4)) = '25504446'
THEN 'PDF'
WHEN HEX(SUBSTR(LINE,1,4)) = '89504E47'
THEN 'PNG'
WHEN HEX(SUBSTR(LINE,1,4)) = 'FFD8FFE0'
THEN 'JPG'
WHEN HEX(SUBSTR(LINE,1,4)) = '504B0304'
THEN 'ZIP'
ELSE 'Autre' // inconnu
END AS TYPE
FROM TABLE(QSYS2.IFS_READ_BINARY('/home/vert.jpg'))
FETCH FIRST 1 ROW ONLY;
Résultat :
Conclusion :
C’est simple, et efficace, il y a sans doute d’autres manières de faire
https://www.gaia.fr/wp-content/uploads/2017/02/team3.png600600Pierre-Louis BERTHOIN/wp-content/uploads/2017/05/logogaia.pngPierre-Louis BERTHOIN2026-05-11 14:42:272026-05-11 14:42:28Trouver le type d’un fichier
BOM signifie Byte Order Mark 👉 en français : marque d’ordre des octets
C’est une séquence d’octets placée au tout début d’un fichier texte pour indiquer :
l’encodage du fichier
parfois l’ordre des octets (endianness)
📌 À quoi sert un BOM ? 1️⃣ Identifier l’encodage
Quand un logiciel ouvre un fichier, le BOM lui dit :
« Ce fichier est en UTF-8 / UTF-16 / UTF-32 »
Sans BOM, certains outils devinent l’encodage… et se trompent.
2️⃣ Indiquer l’ordre des octets (UTF-16 / UTF-32)
Pour les encodages multi-octets :
Little Endian
Big Endian
Le BOM permet de savoir dans quel ordre lire les octets.
Sur L’IBMi on l’utilisera pour échanger un fichier UTF8 avec un autre système que le réclame, ou à l’inverse il faudra l’enlever sur un fichier recu !
Voici 2 procédures pour vous aider et une requête
Une requête pour afficher le BOM sur un fichier IFS
WITH FICHIER AS (
SELECT DATA
FROM TABLE (
QSYS2.IFS_READ_BINARY('/tmp/test.txt')
)
)
SELECT
HEX(SUBSTR(DATA, 1, 4)) AS BOM_HEXA,
CASE
WHEN SUBSTR(DATA, 1, 3) = X'EFBBBF'
THEN 'UTF-8 avec BOM'
WHEN SUBSTR(DATA, 1, 2) = X'FFFE'
THEN 'UTF-16 Little Endian'
WHEN SUBSTR(DATA, 1, 2) = X'FEFF'
THEN 'UTF-16 Big Endian'
WHEN SUBSTR(DATA, 1, 4) = X'FFFE0000'
THEN 'UTF-32 Little Endian'
WHEN SUBSTR(DATA, 1, 4) = X'0000FEFF'
THEN 'UTF-32 Big Endian'
ELSE
'Aucun BOM détecté (UTF-8 sans BOM probable)'
END AS ENCODAGE_DETECTE
FROM FICHIER;
Une procédure pour ajouter un BOM sur un fichier à envoyer
CREATE OR REPLACE PROCEDURE AJOUTER_BOM (
IN p_path VARCHAR(1024) CCSID 1208,
IN p_bom VARCHAR(10)
)
LANGUAGE SQL
BEGIN
DECLARE v_data BLOB(10M);
DECLARE v_new_data BLOB(10M);
DECLARE v_bom BLOB(4);-- Déterminer le BOM à ajouter
SET v_bom =
CASE UPPER(p_bom)
WHEN 'UTF8' THEN X'EFBBBF'
WHEN 'UTF16LE' THEN X'FFFE'
WHEN 'UTF16BE' THEN X'FEFF'
ELSE NULL
END;
IF v_bom IS NULL THEN
SIGNAL SQLSTATE '38001'
SET MESSAGE_TEXT = 'BOM non reconnu (UTF8, UTF16LE, UTF16BE)';
END IF;
-- Lire le fichier
SELECT DATA
INTO v_data
FROM TABLE (
QSYS2.IFS_READ_BINARY(p_path)
);
-- Vérifier si le BOM est déjà présent
IF SUBSTR(v_data, 1, LENGTH(v_bom)) <> v_bom THEN
SET v_new_data = v_bom || v_data;
-- Réécriture du fichier avec BOM
CALL QSYS2.IFS_WRITE_BINARY(
PATH_NAME => p_path,
DATA => v_new_data,
REPLACE => 1
);
END IF;
END;
Une procédure pour enlever le BOM
CREATE OR REPLACE PROCEDURE SUPPRIMER_BOM (
IN p_path VARCHAR(1024) CCSID 1208,
IN p_bom VARCHAR(10)
)
LANGUAGE SQL
BEGIN
DECLARE v_data BLOB(10M);
DECLARE v_bom BLOB(4);
DECLARE v_len INTEGER;-- Déterminer le BOM à supprimer
SET v_bom =
CASE UPPER(p_bom)
WHEN 'UTF8' THEN X'EFBBBF'
WHEN 'UTF16LE' THEN X'FFFE'
WHEN 'UTF16BE' THEN X'FEFF'
WHEN 'UTF32LE' THEN X'FFFE0000'
WHEN 'UTF32BE' THEN X'0000FEFF'
ELSE NULL
END;
IF v_bom IS NULL THEN
SIGNAL SQLSTATE '38001'
SET MESSAGE_TEXT =
'BOM non reconnu (UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE)';
END IF;
SET v_len = LENGTH(v_bom);
-- Lire le fichier
SELECT DATA
INTO v_data
FROM TABLE (
QSYS2.IFS_READ_BINARY(p_path)
);
-- Vérifier et supprimer le BOM demandé
IF SUBSTR(v_data, 1, v_len) = v_bom THEN
SET v_data = SUBSTR(v_data, v_len + 1);
-- Réécrire le fichier sans BOM
CALL QSYS2.IFS_WRITE_BINARY(
PATH_NAME => p_path,
DATA => v_data,
REPLACE => 1
);
END IF;
END;
Conclusion
Sur IBMi ca ne sert à rien le CCSID donne cette information
Ajouter l’action dans VSCode en remplaçant <BIBOBJ>, <BIBBDD>, <PATH> par votre bibliothèque, la bibliothèque de base de données et le chemin du fichier à générer :
?<BIBOBJ>/TODBML LIB(<BIBBDD>) PATH('<PATH>')
Paramètres de la commande:
LIB : Bibliothèque de la base de données
PATH : Le chemin du fichier dbml qui sera généré
Exemple :
ps : Vous pouvez faire une action de type « fichier » avec rafraichissement de filtre.
Lancer l’action
Visualisation du diagramme
Ouvrir le fichier et cliquer sur l’icone « show diagram » en haut à droite.
A vous de jouer !
Il est intéressant de constater que la richesse du rendu semble proportionnelle à la densité des relations dans la base… une coïncidence, sans doute 🙂
/wp-content/uploads/2017/05/logogaia.png00Florian Gradot/wp-content/uploads/2017/05/logogaia.pngFlorian Gradot2025-09-18 09:35:242025-09-29 10:22:24Schéma de base données dans VSCode
Suite à des demandes multiples, je propose une implémentation de « DUMP » des enregistrements d’une table (plutôt d’un objet *FILE / PF-DTA, que ce soit un PF ou une table).
L’idée est d’obtenir u script SQL contenant les instructions INSERT permettant de reproduire les données dans une autre base.
Premièrement, la réplication de données sans utiliser les commandes de sauvegarde/restauration. Ces dernières nécessitent des droits élevés, alors qu’ici nous ne faisons que manipuler de la donnée.
Deuxièmement, dans le cadre de traitement de journaux (initialement en vue d’une fonction de type CDC), pour permettre d’isoler un enregistrement que l’on souhaite répliquer (avec ou sans transformation) dans une autre table.
Vous trouverez certainement d’autres usages !
Limites
Le code est fourni « as is », pour démonstration.
Quelques limites d’usage actuellement
Types de colonnes non supportées actuellement : CLOB, BLOB, DATALINK, XML, GRAPHIC, VARGRAPHIC, {VAR}CHAR CCSID 65535
Pas plus de 16Mo par enregistrement
On ne gère pas les alias, partitions, IASP
250 colonnes maximum
En cas de multi-membres, seul le premier membre est traité
On peut bien évidemment ajouter de nouvelles fonctionnalités !
N’hésitez pas à donner un feedback, améliorer le code
Vous avez une instruction SQL , MERGE qui est assez PUISSANTE pour fusionner des fichiers Voici un exemple complet :
Il intègre les éléments suivants -La création -La mise à jour -La suppression
On a 2 tables Products et Mouvements
Les règles choisies sont les suivantes :
-Si le produit existe on ajoute la quantité -S’ il est nouveau, on le créé -Si nom du produit est SUPPRESSION, on supprime
J’ai utilisé la convention de nommage *SYS
et dans tous les cas on met à jour la date de modification
-- voici les scripts pour tester
-- Création de la table des produits
-- Option *SYS et *NONE
CREATE TABLE GDATA/PRODUCTS (
PRODUCT_NUMBER DECIMAL(10, 0) NOT NULL ,
PRODUCT_NAME VARCHAR(100) NOT NULL,
QUANTITY DECIMAL(10, 0) NOT NULL DEFAULT 0,
LAST_UPDATE_TS TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (PRODUCT_NUMBER)
);
-- alimentation du fichier
INSERT INTO GDATA/PRODUCTS VALUES(1, 'CLOU', 50, current timestamp) ;
INSERT INTO GDATA/PRODUCTS VALUES(2, 'VIS', 20, current timestamp) ;
INSERT INTO GDATA/PRODUCTS VALUES(3, 'ECROU', 25, current timestamp) ;
INSERT INTO GDATA/PRODUCTS VALUES(4, 'RONDELLE', 120, current timestamp) ;
-- Création de la table des mouvements
CREATE TABLE GDATA/MOUVEMENTS (
PRODUCT_NUMBER DECIMAL(10, 0) NOT NULL ,
PRODUCT_NAME VARCHAR(100) NOT NULL,
QUANTITY DECIMAL(10, 0) NOT NULL DEFAULT 0,
LAST_UPDATE_TS TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (PRODUCT_NUMBER)
);
-- alimentation de la tables des mouvements
INSERT INTO GDATA/MOUVEMENTS VALUES(1, 'CLOU', 30, current timestamp) ; -- changement de quantité
INSERT INTO GDATA/MOUVEMENTS VALUES(3, 'SUPPRESSION', 0, current timestamp) ; -- suppression
INSERT INTO GDATA/MOUVEMENTS VALUES(5, 'RESSORT', 100, current timestamp); -- Nouveau
-- Fusion des 2 tables
MERGE INTO GDATA/PRODUCTS AS T -- T est l'alias de la table CIBLE (PRODUCTS)
USING GDATA/MOUVEMENTS AS S -- S est l'alias de la table SOURCE (MOUVEMENT)
ON (T.PRODUCT_NAME = S.PRODUCT_NAME) -- La jointure se fait sur le nom du produit
-- 1. Gérer la suppression si le produit correspond ET que la source indique 'SUPPRESSION'
WHEN MATCHED AND S.PRODUCT_NAME = 'SUPPRESSION' THEN
DELETE
-- 2. Gérer la mise à jour si le produit correspond ET que la source N'indique PAS 'SUPPRESSION'
WHEN MATCHED AND S.PRODUCT_NAME <> 'SUPPRESSION' THEN
UPDATE SET T.QUANTITY = T.QUANTITY + S.QUANTITY,
T.LAST_UPDATE_TS = CURRENT_TIMESTAMP
-- 3. Gérer l'insertion si le produit NE correspond PAS ET que la source N'indique PAS 'SUPPRESSION'
WHEN NOT MATCHED AND S.PRODUCT_NAME <> 'SUPPRESSION' THEN
INSERT (PRODUCT_NUMBER, PRODUCT_NAME, QUANTITY, LAST_UPDATE_TS)
VALUES (S.PRODUCT_NUMBER, S.PRODUCT_NAME, S.QUANTITY, CURRENT_TIMESTAMP);
Remarque :
Vous pouvez également utiliser la commande CPYF avec le paramètre MBROPT(*UPDADD) mais plus compliqué de gérer les suppressions.
Vous devez échanger un fichier avec un partenaire qui vous le demande au format JSON
Il n’existe pas de CVTJSONxx comme la commande CPYTOIMPF pour le CSV
Pas de panique vous avez des services SQL qui font ceci voici un exemple :
Création d’une table de test
SET SCHEMA = ‘votre base’; CREATE OR REPLACE TABLE CLIENTS ( ID_CLIENT INTEGER NOT NULL WITH DEFAULT, NOM_CLIENT VARCHAR ( 50) NOT NULL WITH DEFAULT, VILLE VARCHAR ( 50) NOT NULL WITH DEFAULT ) ; INSERT INTO CLIENTS VALUES(1, ‘Dupont’, ‘Paris’) ; INSERT INTO CLIENTS VALUES(2, ‘Durand’, ‘Lyon’) ;
Voici la syntaxe qu’il vous faudra utiliser pour le convertir sous forme de flux json
SELECT JSON_OBJECT(
'id_client' VALUE ID_CLIENT,
'nom_client' VALUE NOM_CLIENT,
'ville' VALUE VILLE
) AS CLIENT_JSON
FROM CLIENTS;
Maintenant il va falloir générer le fichier .JSON dans l’IFS par exemple en utilisant la procédure IFS_WRITE_UTF8
Malheureusement ce service n’existe pas sous forme de fonction on devra utiliser une des possibilités de SQL « Dynamic compound statement » qui permet de compiler un module dynamiquement
Ce qui donnera donc
BEGIN
CALL QSYS2.IFS_WRITE_UTF8(
PATH_NAME => 'PLB_CLIENTS.json',
LINE => '',
OVERWRITE => 'REPLACE',
END_OF_LINE => 'NONE'
);
FOR SELECT TRIM(CAST(JSON_OBJECT(
'id_client' VALUE ID_CLIENT,
'nom_client' VALUE NOM_CLIENT,
'ville' VALUE VILLE
) AS VARCHAR(32000))) AS Line_to_write
FROM PLB.CLIENTS
DO
CALL QSYS2.IFS_WRITE_UTF8(
PATH_NAME => 'PLB_CLIENTS.json',
LINE => Line_to_write
);
END FOR;
END;
Si vous avez beaucoup de fichiers à convertir ca peut être fastidieux pas de panique nous avons fait une commande qui va vous aider
https://github.com/Plberthoin/PLB/tree/master/GTOOLS, vous avez l’habitude un source SQLRPGLE et un source CMD à compiler , voila SME
Vous pouvez enregistrer le scripte SQL (CVTFICJSON.SQL) avec l’option Sauvegarder le scripte
Vous pourrez le customiser :
en le formatant par ACS ,
en enlevant des zones
en sélectionnant des enregistrements
etc …
Remarque :
Vous avez une option pour agréger
Vous pouvez faire beaucoup mieux , c’est juste pour vous aider à démarrer dans le domaine
https://www.gaia.fr/wp-content/uploads/2017/02/team3.png600600Pierre-Louis BERTHOIN/wp-content/uploads/2017/05/logogaia.pngPierre-Louis BERTHOIN2025-05-02 12:16:162025-05-02 12:16:17Convertissez un fichier en JSON