0 Abonnés · 123 Publications

  

InterSystems Caché est un SGBD multi-modèles et un serveur d'applications. Consultez plus de détails ici.

Documentation.

Article Sylvain Guilbaud · Oct 29, 2025 14m read

Aperçu

Cette interface Web est conçue pour faciliter la gestion des tables de recherche de données (Data Lookup Tables) via une page Web conviviale. Elle est particulièrement utile lorsque les valeurs de votre table de recherche sont volumineuses, dynamiques et changeantes. Les utilisateurs finaux peuvent gérer efficacement les données de la table de recherche en fonction de leurs besoins grâce à un accès contrôlé à cette interface Web (autorisations de lecture, d'écriture et de suppression limitées à cette page).

Les données gérées via cette interface peuvent être utilisées de manière transparente pour les règles HealthConnect ou les transformations de données, ce qui élimine le besoin d'une surveillance et d'une gestion manuelles constantes des tables de recherche et permet ainsi un gain de temps considérable.

Remarque:
Si la table de recherche de données classique ne répond pas à vos besoins en matière de mappage, vous pouvez créer une table personnalisée et adapter cette interface Web ainsi que sa classe de support avec un minimum de modifications. Un exemple de code de classe est disponible sur demande.

Conditions préalables

Avant d'utiliser l'interface Web, veillez à avoir créé une table de recherche de données vide dans HealthConnect:

  1. Accédez à Interoperability → Configure → Data Lookup Tables.
  2. Cliquez sur New pour ouvrir la fenêtre contextuelle "Create New Lookup Table" (Créer une nouvelle table de recherche).
  3. Saisissez le nom souhaité pour votre table de recherche et cliquez sur OK.
  4. Cliquez sur Save pour finaliser la création de la table.
  5. Vérifiez la création de la table de recherche en cliquant sur Open dans la même fenêtre. Votre nouvelle table devrait apparaître dans la liste.

Dans cette documentation, la table de recherche de données utilisée à titre d'exemple est intitulée:
"Exemple de table de recherche de données"

Mode d'emploi

Le processus de configuration est simple:

  1. Obtention du code:Téléchargez le code fourni à partir du référentiel GitHub ou copiez-le à partir du lien communautaire.
  2. Création d'une page CSP:Créez une nouvelle page CSP dans votre environnement HealthConnect et insérez tout le code du fichier DataLookupWebApp.
  3. Création d'une classe:Copiez et insérez le code de classe fourni dans la documentation ou dans le référentiel.

Configuration

Avant la compilation et l'exécution:

  1. Indiquez le nom de votre table de recherche de données sur la page Web en remplaçant l'espace réservé indiqués par le cadre rouge dans la capture d'écran ci-dessous par le nom réel de votre table de recherche.

  1. Vérifiez si le nom de la classe dans le code correspond au nom de votre classe personnalisée.

  1. Attribuez un nom à votre table de recherche dans la classe, comme indiqué par myDtName = 'votre table de recherche'. Consultez la capture d'écran ci-dessus.
  1. Compilez la classe et la page CSP pour finaliser la configuration.

Mise à l'essai de l'application

Ajout d'enregistrements

  • Saisissez la clé et la paire clé-valeur correspondante dans les champs du formulaire.
  • Cliquez sur Add Record pour insérer les données dans la table de recherche.

Capture d'écran 1: Ajout d'enregistrements (122 – Radiographie de la colonne vertébrale)

Mise à jour de l'enregistrement

  • Sélectionnez un enregistrement existant et modifiez la paire clé-valeur si nécessaire.

  • Cliquez sur Update pour enregistrer les modifications. Exemple : Modification de  122 "Radiographie de la colonne vertébrale" en "Radiographie abdominale".

Suppression de l'enregistrement

  • Sélectionnez un enregistrement à supprimer (144 – IRM)

  • Cliquez sur le bouton Delete pour supprimer l'enregistrement de la table de recherche. Exemple : Suppression de "144 - IRM".

Vérification de la table de recherche de données dans HealthConnect

Accédez à Interoperability → Configure → Data Lookup Tables à Open

Recherche de l'enregistrement

Code / Ressources

  • La version complète du code source est également disponible pour téléchargement et consultation sur GitHub.

https://github.com/pandeySR/Reusable-Web-Interface-for-Data-Lookup-Table

<!DOCTYPE html>
<!--
This web application is designed and developed to insert, update, and delete records (key–value pairs) in the Data Lookup Table through a web interface.
Author: Sanjib Pandey
Date: 16/10/2025
-->
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="author" content="Sanjib Pandey" />
  <title>YOUR TITLE : Data Lookup Table - Data Record </title>
  <!-- Bootstrap CSS CDN, jQuery, Datatable -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> 
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> 
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
  <link rel="stylesheet" href="https://cdn.datatables.net/1.13.5/css/dataTables.bootstrap5.min.css" />
  <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
  <script src="https://cdn.datatables.net/1.13.5/js/jquery.dataTables.min.js"></script>
  <script src="https://cdn.datatables.net/1.13.5/js/dataTables.bootstrap5.min.js"></script>
  <style>
    h2 {
      text-align: center;
      color: #fff;
      background-color: #00b300;
      padding: 15px 10px;
      border-radius: 5px;
      margin-bottom: 5px;
      margin-top: 0px;
    }
    html, body {
      height: 100%;
      margin: 0;
    }
    body {
      display: flex;
      min-height: 100vh;
      background-color: #f8f9fa;
      padding-top: 10px;
      flex-direction: column;
      align-items: center;
    }
    .container {
      max-width: 1100px;
      background: white;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0015px rgba(0,0,0,0.2);
      margin-top: 5px;
      width: 100%;
    }
 
    .table-scroll-vertical
    {
 	 max-height: 450px;
 	 overflow-y: auto;
  	 border: 1px solid #dee2e6;
  	 border-radius: 5px;
  	 margin-top: 0; 
	}
	
	.small-italic 
	{
  	 font-size: 0.85em; 
  	 font-style: italic; 
  	 color: #C0C0C0; 
	}
	
	.dataTables_filter label 
	{
  	 font-weight: bold;
  	 color: #333;
  	 color: red;
  	 float: left;
	}

	.dataTables_filter input
	{
  	 border: 2px solid #993399;
  	 border-radius: 5px;
  	 padding: 6px 10px;
  	 outline: none;
   	 width: 250px;
	}
	
	.page-footer
	{
  	 text-align: center;
  	 padding: 10px 5px;
  	 font-size: 0.95rem;
 	 color: #809fff;
 	 background-color: #f8f9fa;
  	 border-top: 1px solid #993399;
  	 margin-top: 5px;
	}
  </style>
  <style>
  @keyframes blink
  {
    50%
    {
      opacity: 0;
    }
  }
</style>
</head>
<body>
<div class="container">
<div style="overflow: hidden;">
  <div style="float: left; font-size: smaller; font-weight: bold;color:#b3ffff;animation: blink 1s infinite;">
    Environment : DEV
  </div>
  <h2 style="text-align: center; margin: 0;">
    Your Organization Name or Company Name<br>
    <span style="font-size: smaller; color: yellow;text-align: center;">Data Lookup Table</span>
  </h2>
</div> 
 <form id="recordForm"class="form-horizontal" action="" method="POST">
 	<div class="form-group">
        <!--for any other information if you want> -->
       </div>
      <div class="row mb-3">
        <label for="key"class="col-sm-3 col-form-label fw-bold">Key :</label>
        <div class="col-sm-9">
          <input type="text"class="form-control" id="key" name="key" placeholder="Enter the Key data !" required />
        </div>
      </div>
      <div class="row mb-3">
        <label for="kvalue"class="col-sm-3 col-form-label fw-bold">Key Pair Value</label>
        <div class="col-sm-9">
          <input type="text"class="form-control" id="kvalue" name="kvalue" placeholder="Enter the key paris value !" required />
        </div>
      </div>
	<div class="d-flex justify-content-between align-items-center mt-4 flex-wrap">
		<p class="mb-2 mb-md-0 text-primary">
    		<i>Click the edit icon to modify a record, then press "Update" to save. To delete, press the "Delete" button.</i> 
  		</p> 
  		
	<div class="d-flex flex-wrap justify-content-end">
		<button class="btn btn-success me-2" id="Add" type="submit" name="Add">
			<i class="fa fa-plus me-1" style="font-size:18px; color:white;"></i> Add Record
		</button>
	
		<button class="btn btn-primary me-2" type="submit" name="Update">
			<i class="fa fa-edit me-1" style="font-size:18px;"></i> Update
    	</button>

		<button class="btn btn-danger me-2" id="Delete" type="submit" name="Delete">
			<i class="fa fa-remove me-1" style="font-size:18px;"></i> Delete
		</button>
		
		<button class="btn btn-info" id="Cancel" type="submit" name="Cancel">
  			<i class="glyphicon glyphicon-refresh" style="font-size:18px;"></i> Cancel
		</button>
  	   </div> 
    </div>
    <div></div>
	</form>
<script language=SQL name="query">
  SELECT KeyName, DataValue FROM Ens_Util.LookupTable WHERE TableName='Data Lookup Table Example'
</script>

<script language=cache runat=server>
 S myKeyName=$Get(%request.Data("key",1))
 S myKeyValue=$Get(%request.Data("kvalue",1))
 S myDtName="Data Lookup Table Example"
 I ($Data(%request.Data("Add",1)))
 {
	I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	{	
		I ##Class(SANJIB.DataLookup.Methods).ChkLookupKeyValue(myDtName,myKeyName)="F"
		{
			D##Class(SANJIB.DataLookup.Methods).InsertLookUpValues(myDtName,myKeyName,myKeyValue)
		}
		else
		{	W"The key has already been inserted.."&html<<divstyle=color:red>#(myKeyName)#</div>> }
		
	}
 }
 I ($Data(%request.Data("Update",1)))
 {
	I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	{
		D##Class(SANJIB.DataLookup.Methods).UpdateLookUpValues(myDtName,myKeyName,myKeyValue)
	}
 }
 I ($Data(%request.Data("Delete",1)))
 {
	 I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	 {
		D##Class(SANJIB.DataLookup.Methods).DeleteLookUpValues(myDtName,myKeyName)		
	 }
 }	
</script>
 <div class="table-responsive table-scroll-vertical"> 
    <table class="table table-bordered border-primary table table-hover mt-2" id="dataTable" style="min-width: 1000px;">
      <thead class="table-warning">
        <tr>
          <th>Key Data</th>
          <th>Key Pair Value</th>
          <th style="text-align:center;">Actions</th> 
        </tr>
      </thead>
      <tbody id="tableRecordset">
 		</tr>
 		<csp:while counter=myQueryRow condition=query.Next()>
 			<tr class='#($S(myQueryRow#2:"LightRow",1:"LightRow"))#'>
  				<csp:while counter=myQueryColumn condition="(myQueryColumn<query.GetColumnCount())">
      				<td style="background-color:#e6ffff">#(query.GetData(myQueryColumn))#</td>
  						</csp:while>
 							<td style="text-align:center;">
          						<button class="btn btn-sm btn-danger me-2 edit-delete-btn" data-id="#(query.GetData(0))#">
    								<i class="fa fa-edit"></i>
    								<i class="fa fa-trash"></i>
  								</button>
        					</td> 
 						</tr>
 				</csp:while>
      		</tbody>
       </table>
    </div>
  </div>
  
<script language=javascript>
  document.addEventListener('DOMContentLoaded', () => 
  {
    const editButtons = document.querySelectorAll('.edit-delete-btn');
    editButtons.forEach(button =>
    {
      button.addEventListener('click', () => 
      {
        const row = button.closest('tr');
        document.getElementById("Add").disabled=true
        const kID=row.cells[0].textContent.trim();
        const pairVal = row.cells[1].textContent.trim();
        document.getElementById('key').value = kID;
        document.getElementById('kvalue').value = pairVal;
        document.getElementById('key').focus();
      });
    });
  });
</script>
 <script language=javascript>
    document.getElementById('Cancel').addEventListener('click', () => 
    {
    	event.preventDefault();
    	document.getElementById('recordForm').reset();
    });
</script>

<script language=javascript>
  $(document).ready(function()
  {
    $('#dataTable').DataTable(
    {
      "order": [[0, "asc"]],
      "paging": true,
      "lengthChange": false,
      "searching": true,
      "info": true,
      "autoWidth": false
    });
  		$('#dataTable_filter input').css('font-size', '12px').attr('placeholder', 'Type to search..?');
  });
</script>

</body> 

<script language="javascript">
  document.addEventListener('DOMContentLoaded', () =>
  {
  	const updateBtn = document.querySelector('button[name="Update"]');
  	const deleteBtn = document.querySelector('button[name="Delete"]');
  	const addBtn = document.getElementById('Add');
  	const cancelBtn = document.getElementById('Cancel');
  	updateBtn.style.display = 'none';
  	deleteBtn.style.display = 'none';
  	addBtn.style.visibility = 'visible';  
  	addBtn.disabled = false;
  document.querySelectorAll('.edit-delete-btn').forEach(editBtn =>
  {
    editBtn.addEventListener('click', () =>
    {
      updateBtn.style.display = 'inline-block';
      deleteBtn.style.display = 'inline-block';
      addBtn.style.display = 'none';
      addBtn.style.visibility = 'visible'; 
      addBtn.disabled = true;
    });
  });
  updateBtn.addEventListener('click', (e) =>
  {
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';
    addBtn.disabled = false;
  });
  deleteBtn.addEventListener('click', (e) =>
  {
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';
    addBtn.disabled = false;
  });
  cancelBtn.addEventListener('click', (e) =>
  {
    e.preventDefault();  
    document.getElementById('recordForm').reset();
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';  
    addBtn.disabled = false;              
  });
});
</script>

<footer class="page-footer">
  <a href="https://sanjibpandey.wixsite.com/pandey/" target="_blank" rel="noopener noreferrer" style="color: #6c757d; text-decoration: none;"> https://sanjibpandey.wixsite.com/pandey/</a>
</footer>
</html>

 

/// Cette classe est conçue et créée pour insérer, mettre à jour et supprimer des enregistrements (clé & valeur)/// dans la table de recherche de données à partir d'une application Web. /// Ensuite, cela peut être utilisé dans des règles ou des transformations de données à l'aide de la fonction de recherche./// Auteur : Sanjib Pandey/// Date : 16/10/2025Class SANJIB.DataLookup.Methods Extends%Persistent
{

/// Avec cette méthode, on ajoute une valeur à la table de recherche. Les paramètres obligatoires sont:/// dtTableName : Le nom de votre table de recherche de données/// keyData     : La clé/// keyValue    : La valeur ( la valeur de paire de clés)	ClassMethod InsertLookUpValues(dtTable As%String = "", keyData As%String = "", keyValue As%String = "") As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") && (keyValue '="")
		{
			S mySQLStatement = "INSERT into Ens_Util.LookupTable (KeyName,DataValue,TableName) values ('"_keyData_"','"_keyValue_"','"_dtTable_"')"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"InserRecorded":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode vérifie si la valeur existe déjà dans la table de recherche de données.ClassMethod ChkLookupKeyValue(dtTable As%String, keyData As%String) As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="")
		{
			S mySQLStatement="SELECT KeyName FROM Ens_Util.LookupTable WHERE TableName='"_dtTable_"' and KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			D myRecordSet.%Next()
			I (myRecordSet.%SQLCODE=0)
			{ 
				S tSC= "T"
			}
			else
			{
				S tSC="F"
			}
		}
		else
		{
			S tSC= "Invalid Parameters - missing table name or key !"
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode met à jour uniquement la valeur clé dans la table de recherche de données.ClassMethod UpdateLookUpValues(dtTable As%String = "", keyData As%String = "", keyValue As%String = "") As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") && (keyValue '="")
		{
			S mySQLStatement = "UPDATE Ens_Util.LookupTable SET DataValue='"_keyValue_"' WHERE TableName='"_dtTable_"' AND KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"Updated Record":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode est utilisée pour supprimer un enregistrement de la table de recherche de données.ClassMethod DeleteLookUpValues(dtTable As%String, keyData As%String) As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") 
		{
			S mySQLStatement = "DELETE FROM Ens_Util.LookupTable WHERE  TableName='"_dtTable_"' And KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"Deleted Record":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

Storage Default
{
<Data name="MethodsDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
</Data>
<DataLocation>^SANJIB.DataLookup.MethodsD</DataLocation>
<DefaultData>MethodsDefaultData</DefaultData>
<IdLocation>^SANJIB.DataLookup.MethodsD</IdLocation>
<IndexLocation>^SANJIB.DataLookup.MethodsI</IndexLocation>
<StreamLocation>^SANJIB.DataLookup.MethodsS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

Conclusion

Cette interface Web réutilisable et cette classe offrent un moyen simple mais efficace pour la gestion des tables de recherche de données dans HealthConnect, en améliorant l'efficacité et la flexibilité. Grâce à l'accès contrôlé dont bénéficient les utilisateurs finaux, cette interface réduit les frais administratifs traditionnellement associés à la maintenance des tables de recherche.

0
0 17
Article Lorenzo Scalese · Oct 28, 2025 10m read

Le déploiement de nouvelles instances IRIS peut être une tâche fastidieuse, en particulier lors de la mise en place de plusieurs environnements avec des configurations en miroir.

J'ai fait face à ce problème très souvent et je souhaite partager mon expérience et mes recommandations concernant l'utilisation d'Ansible pour rationaliser le processus d'installation d'IRIS. Mon approche inclut également la gestion des tâches supplémentaires généralement effectuées avant et après l'installation d'IRIS.

Ce manuel suppose que vous disposez d'une compréhension de base du fonctionnement d'Ansible, je ne détaillerai donc pas ses principes fondamentaux. Toutefois, si vous avez des questions sur les points abordés ici, n'hésitez pas à les poser dans les commentaires ci-dessous.

Les exemples fournis dans ce manuel ont été testés à l'aide d' Ansible 3.6 sur un serveur Red Hat 8, avec IRIS 2023.1.1 et Red Hat 8 comme environnement client. D'autres versions d'Ansible, de Red Hat (ou d'autres variantes d'UNIX) et d'IRIS peuvent également fonctionner, mais les résultats peuvent varier.

Installation d'Ansible

Le serveur Ansible nécessite une distribution Linux. Nous utilisons Red Hat 8 dans cet article, mais d'autres distributions et versions Linux devraient également fonctionner.

Pour installer les paquets Ansible, il faut d'abord installer EPEL:

[ansible@auto01 ansible]$ yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm

Ensuite, il faut installer Ansible:

[ansible@auto01 ansible]$ yum install ansible

En plus des paquets, Ansible nécessite un accès SSH aux serveurs distants. Je recommande de créer une paire de clés SSH, ce qui est plus sûr que l'utilisation de mots de passe traditionnels. De plus, l'utilisateur servant à se connecter aux serveurs distants doit disposer de privilèges administratifs (c'est-à-dire faire partie du groupe wheel).

Fichiers et dossiers

Pour préserver une structure organisée, je recommande les fichiers et dossiers suivants dans le répertoire ansible:

[ansible@auto01 ansible]$ ls -l
total 4
-rw-r--r--. 1 ansible ansible 247 Dec  500:57 ansible.cfg
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 files
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 inventory
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 library
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 playbooks
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 templates
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 vars
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 vault
Fichier/DossierDescription
ansible.cfgFichier de configuration d'Ansible. Contient des directives sur le comportement d'Ansible.
filesContient les fichiers supplémentaires nécessaires aux playbooks, tels que le fichier tar.gz d'installation d'IRIS.
inventoryContient les fichiers d'inventaire de l'hôte. Vous pouvez avoir un seul fichier d'inventaire volumineux ou plusieurs fichiers de moindre taille. Le fractionnement de l'inventaire nécessite davantage d'efforts lorsque vous exécutez des playbooks sur plusieurs hôtes.
libraryContient des fichiers de bibliothèque supplémentaires d'Ansible. Non requis pour ces exemples, mais utile pour de futures extensions.
playbooksContient tous les playbooks développés, y compris le playbook d'installation IRIS décrit ci-dessous.
templatesContient les fichiers modèles utilisés par les playbooks. Ceux-ci sont transférés vers les serveurs et instanciés avec les paramètres corrects.
varsContient les variables disponibles pour tous les playbooks.
vaultContient des variables sensibles accessibles uniquement via la commande ansible-vault. Utile pour gérer les mots de passe.

 

Après avoir configuré cette structure de dossiers, copiez le programme d'installation IRIS et la clé de licence IRIS dans le dossier files. Le résultat devrait apparaître comme suit:

[ansible@auto01 ansible]$ ls -l files/
total 759976
-rw-rw-r--. 1 ansible ansible 778207913 Dec  514:32 IRISHealth-2023.1.1.380.0.22870-lnxrh8x64.tar.gz
-rw-rw-r--. 1 ansible ansible      1160 Sep  519:13 iris.key

 

Inventaire

Pour exécuter des playbooks dans Ansible, il est nécessaire de définir l'inventaire des serveurs. Il existe plusieurs méthodes pour ce faire, et chacune présente ses propres avantages. Dans cet article, nous utiliserons un seul fichier pour définir tous les serveurs.

Le ficher servers.yml contiendra l'inventaire complet, répertoriant chaque serveur ainsi que les variables requises pour l'installation d'IRIS. Voici un exemple:

[ansible@auto01ansible]$catinventory/servers.yml 
---all:  hosts:test01.mydomain:      iris_user:irisusr      iris_group:irisgrp      mgr_user:irisown      mgr_group:irismgr      platform:lnxrh8x64      iris_cmd:iris      iris_instances:        - name:TEST01          superserver_port:51773          webserver_port:52773          binary_file:IRISHealth-2023.1.1.380.0.22870-lnxrh8x64          key_file:iris.key          install_dir:/test/iris          jrnpri_dir:/test/jrnpri          jrnsec_dir:/test/jrnsec          config_globals:16384          config_errlog:10000          config_routines:"0,128,0,128,0,1024"          config_gmheap:1048576          config_locksiz:128057344

 

Fichier coffre-fort

Pour sécuriser les mots de passe, créez un fichier coffre-fort contenant les mots de passe des comptes IRIS SuperUser et CSPSystem.

Pour modifier le fichier coffre-fort par défaut, utilisez la commande suivante:

[ansible@auto01ansible]$ansible-vaulteditvault/defaults.yml---# Default passwordsiris_user_passwd:"Ch4ngeTh!s"

 

Playbook

Pour effectuer une installation IRIS, il est nécessaire d'exécuter plusieurs tâches sur le serveur cible. Ces tâches sont regroupées et classées dans un fichier appelé playbook.
Un playbook consiste essentiellement en une liste de tâches qui sont exécutées de manière séquentielle sur les hôtes distants.

Vous trouverez ci-dessous le playbook que j'ai développé pour installer IRIS:

[ansible@auto01ansible]$catplaybooks/install_iris.yml## Playbook to install Iris#- hosts:all  become:yes  gather_facts:no  tasks:  - name:"Load default passwords"    include_vars:"../vault/defaults.yml"### PRE-INSTALL TASKS:  - name:"Install required packets"    yum:      name:"{{ item }}"      state:latest    loop:      -"httpd"      -"java-1.8.0-openjdk"      -"mod_auth_mellon"      -"mod_ssl"  - name:"Create iris group"    group:      name:"{{ iris_group }}"      gid:5005  - name:"Create iris mgr group"    group:      name:"{{ mgr_group }}"      gid:5006  - name:"Create iris owner user"    user:      name:"{{ mgr_user }}"      uid:5006      group:"{{ iris_group }}"      groups:"{{ mgr_group }}"  - name:"Create iris user"    user:      name:"{{ iris_user }}"      uid:5005      group:"{{ iris_group }}"  - name:"Create mgr folder"    file:      path:"{{ item.install_dir }}/mgr"      state:directory      owner:"{{ iris_user }}"      group:"{{ iris_group }}"      mode:0775    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Copy license key"    copy:      src:"../files/{{ item.key_file }}"      dest:"{{ item.install_dir }}/mgr/iris.key"      owner:"{{ iris_user }}"      group:"{{ iris_group }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Create /install folder"    file:      path:"/install"      state:directory      mode:0777  - name:"Create Instances install folders"    file:      path:"/install/{{ item.name }}"      state:directory      mode:0777    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Copy IRIS installer"    copy:      src:"../files/{{ item.binary_file }}.tar.gz"      dest:"/install/{{ item.name }}/"    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Untar IRIS installer"    command:      cmd:"tar -xzf /install/{{ item.name }}/{{ item.binary_file }}.tar.gz"      chdir:"/install/{{ item.name }}/"    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"### IRIS INSTALL:  - name:"Install Iris"    command:      cmd:"./irisinstall_silent"      chdir:"/install/{{ item.name }}/{{ item.binary_file }}"    environment:      ISC_PACKAGE_INSTANCENAME:"{{ item.name }}"      ISC_PACKAGE_INSTALLDIR:"{{ item.install_dir }}"      ISC_PACKAGE_PLATFORM:"{{ platform }}"      ISC_PACKAGE_UNICODE:"Y"      ISC_PACKAGE_INITIAL_SECURITY:"Normal"      ISC_PACKAGE_MGRUSER:"{{ mgr_user }}"      ISC_PACKAGE_MGRGROUP:"{{ mgr_group }}"      ISC_PACKAGE_USER_PASSWORD:"{{ iris_user_passwd }}"      ISC_PACKAGE_CSPSYSTEM_PASSWORD:"{{ iris_user_passwd }}"      ISC_PACKAGE_IRISUSER:"{{ iris_user }}"      ISC_PACKAGE_IRISGROUP:"{{ iris_group }}"      ISC_PACKAGE_SUPERSERVER_PORT:"{{ item.superserver_port }}"      ISC_PACKAGE_WEBSERVER_PORT:"{{ item.webserver_port }}"      ISC_PACKAGE_CLIENT_COMPONENTS:"standard_install"      ISC_PACKAGE_STARTIRIS:"N"    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Remove installers"    file:      path:"/install/{{ item.name }}"      state:absent    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"### IRIS CUSTOMIZATIONS:  - name:"Change iris.cpf"    lineinfile:      path:"{{ item[0].install_dir }}/iris.cpf"      regexp:"{{ item[1].from }}"      line:"{{ item[1].to }}"      backup:yes    with_nested:      -"{{ iris_instances }}"      -[{from:"^TerminalPrompt=.*",to:"TerminalPrompt=8,3,2"},{from:"^FreezeOnError=0",to:"FreezeOnError=1"},{from:"^AutoParallel=.*",to:"AutoParallel=0"},{from:"^FastDistinct=.*",to:"FastDistinct=0"},{from:"^LockThreshold=.*",to:"LockThreshold=10000"},{from:"^EnsembleAutoStart=.*",to:"EnsembleAutoStart=1"},{from:"^MaxIRISTempSizeAtStart=.*",to:"MaxIRISTempSizeAtStart=300"}]    loop_control:      label:"{{ item[0].name }}: {{ item[1].to }}"  - name:"Change Journal Current Dir"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^CurrentDirectory=.*"      line:"CurrentDirectory={{ item.jrnpri_dir }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change Journal Alternate Dir"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^AlternateDirectory=.*"      line:"AlternateDirectory={{ item.jrnsec_dir }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change Journal Prefix name"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^JournalFilePrefix=.*"      line:"JournalFilePrefix={{ item.name }}_"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change Globals memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^globals=.*"      line:"globals=0,0,{{ item.config_globals }},0,0,0"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change errlog memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^errlog=.*"      line:"errlog={{ item.config_errlog }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change routines memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^routines=.*"      line:"routines={{ item.config_routines }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change gmheap memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^gmheap=.*"      line:"gmheap={{ item.config_gmheap }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change locksiz memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^locksiz=.*"      line:"locksiz={{ item.config_locksiz }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"### START IRIS:  - name:"Start Iris"    command:"iris start {{ item.name }}"    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"...

Comme vous pouvez le constater, ce playbook comporte plusieurs tâches, dont la plupart sont explicites d'après leur nom. Les commentaires indiquent les tâches à effectuer avant l'installation, l'installation proprement dite et les personnalisations à effectuer après l'installation. Après avoir exécuté ce playbook, vous disposerez d'une nouvelle instance IRIS installée sur le système cible, dont la mémoire et d'autres paramètres auront été personnalisés.

Lancez l'installation!

Après avoir configuré l'inventaire, le ficheir coffre-fort et les playbooks, vous êtes prêt à exécuter l'installation IRIS à l'aide d'Ansible.
Pour ce faire, exécutez la commande suivante:

[ansible@auto01 ansible]$ ansible-playbook -K --ask-vault-pass -i inventory/servers.yml playbooks/install_iris.yml
BECOME password: 
Vault password: 

PLAY [all] ************************************************************************************************************************************************** . . .

Lorsque l'exécution du playbook est terminée, vous recevez un résumé de statuts des tâches qui vous permet de vérifier que tout a été exécuté avec succès.

Et voilà, vous venez d'installer IRIS à l'aide d'Ansible! 😁

0
0 14
Article Lorenzo Scalese · Sept 23, 2025 8m read

Mes clients me contactent régulièrement à propos du dimensionnement de la mémoire lorsqu'ils reçoivent des alertes indiquant que la mémoire libre est inférieure à un seuil ou lorsqu'ils constatent que la mémoire libre a soudainement diminué. Existe-t-il un problème? Leur application va-t-elle cesser de fonctionner parce qu'elle manque de mémoire pour exécuter les processus système et applicatifs? La réponse est presque toujours non, il est inutile de s'inquiéter. Mais cette réponse simple n'est généralement pas suffisante. Que se passe-t-il?

Considérez le graphique ci-dessous. Il montre le résultat de la métrique free dans vmstat. Il existe d'autres moyens d'afficher la mémoire libre d'un système, par exemple la commande free -m. Parfois, la mémoire libre disparaît progressivement au fil du temps. Le graphique ci-dessous est un exemple exagéré, mais il illustre bien ce qui se passe.

image

Comme vous pouvez le constater, vers 2 heures du matin, une partie de la mémoire est récupérée, puis chute soudainement à près de zéro. Ce système exécute l'application IntelliCare EHR sur la base de données InterSystems IRIS. Les informations vmstat proviennent d'un fichier HTML ^SystemPerformance qui collecte les métriques vmstat, iostat et plusieurs autres métriques système. Que se passe-t-il d'autre sur ce système ? Comme nous sommes en pleine nuit, je ne m'attends pas à ce qu'il se passe grand-chose à l'hôpital. Examinons iostat pour les volumes de la base de données.

image

On constate une augmentation soudaine des lectures au moment où la mémoire libre diminue. La baisse de la mémoire libre signalée correspond à un pic des lectures en gros blocs (taille de requête de 2048 Ko) indiqué dans iostat pour le disque de la base de données. Il s'agit très probablement d'un processus de sauvegarde ou d'une copie de fichiers. Bien sûr, corrélation n'est pas synonyme de causalité, mais cela vaut la peine d'être examiné et, en fin de compte, cela explique ce qui se passe.

Examinons d'autres résultats de ^SystemPerformance. La commande free -m est exécutée à la même fréquence que vmstat (par exemple, toutes les 5 secondes) et est accompagnée de la date et de l'heure, ce qui nous permet également de représenter graphiquement les compteurs dans free -m.

Les compteurs:

  • Memtotal – Total de RAM physique.
  • used – RAM activement utilisée (applications + système d'exploitation + cache).
  • free – RAM complètement inutilisée.
  • shared – Mémoire partagée entre les processus.
  • buf/cache – RAM utilisée pour les tampons et le cache, récupérable si nécessaire.
  • available – RAM disponible sans swap.
  • swaptotal – Espace de swap total sur le disque.
  • swapused – Espace de swap actuellement utilisé.
  • swapfree – Espace de swap inutilisé.

Pourquoi la mémoire libre diminue-t-elle à 2 heures du matin?

  • Les lectures séquentielles de grande taille remplissent le cache de page du système de fichiers, consommant temporairement de la mémoire qui apparaît comme "utilisée" dans free -m.
  • Linux utilise de manière agressive la mémoire inexploitée pour la mise en cache des E/S afin d'améliorer les performances.
  • Une fois la sauvegarde terminée (≈ 03h00), la mémoire est progressivement récupérée au fur et à mesure que les processus en ont besoin.
  • Vers 6 heures du matin, l'hôpital commence à s'activer et la mémoire est utilisée pour IRIS et d'autres processus.

Une mémoire libre insuffisante ne constitue pas une pénurie, mais plutôt une utilisation de la mémoire "libre" par le système à des fins de mise en cache. Il s'agit d'un comportement normal sous Linux! Le processus de sauvegarde lit de grandes quantités de données, que Linux met agressivement en cache dans la mémoire tampon/cache. Le noyau Linux convertit la mémoire "libre" en mémoire "cache" afin d'accélérer les opérations d'E/S.

Résumé

Le cache du système de fichiers est conçu pour être dynamique. Si la mémoire est requise par un processus, elle sera immédiatement récupérée. Il s'agit d'un élément normal de la gestion de la mémoire sous Linux.


Les Huge Pages ont-elles un impact?

Pour optimiser les performances et réserver de la mémoire pour la mémoire partagée IRIS, la meilleure pratique pour les déploiements IRIS en production sur des serveurs dotés d'une mémoire importante consiste à utiliser les Huge Pages de Linux. Pour IntelliCare, j'utilise généralement 8 Go de mémoire par noyau et environ 75 % de la mémoire pour la mémoire partagée d'IRIS (tampons Routine et Global, GMHEAP et autres structures de mémoire partagée). La répartition de la mémoire partagée dépend des exigences de l'application. Vos exigences peuvent être complètement différentes. Par exemple, en utilisant ce rapport CPU/mémoire, 25 % suffisent-ils pour les processus IRIS et les processus du système d'exploitation de votre application?

InterSystems IRIS utilise l'E/S directe pour les fichiers de base de données et les fichiers journaux, ce qui contourne le cache du système de fichiers. Ses segments de mémoire partagée (globales, routines, gmheap, etc.) sont alloués à partir de Huge Pages.

  • Ces pages immenses (huge pages) sont dédiées à la mémoire partagée IRIS et n'apparaissent pas comme "libres" ou "cache" dans free -m.
  • Once allocated, huge pages are not available for filesystem cache or user processes.

Cela explique pourquoi les métriques free -m semblent "insuffisantes" même si la base de données IRIS elle-même ne manque pas de mémoire.


Comment la mémoire libre pour un processus est-elle calculée?

À partir de ce qui précède, dans free -m, les lignes pertinentes sont les suivantes:

  • free – RAM totalement inutilisée.
  • available – RAM encore utilisable sans échange.

La disponibilité est un bon indicateur: elle inclut la mémoire cache et les tampons récupérables, indiquant ce qui est réellement disponible pour les nouveaux processus sans échange. Quels processus? Pour plus d'informations, consultez InterSystems Data Platforms and Performance Part 4 - Looking at Memory . Voici une liste simple: système d'exploitation, autres processus d'application non-IRIS et processus IRIS.

Examinons un graphique de la sortie free -m.

image

Bien que la valeur de la mémoire libre (free) chute à près de zéro pendant la sauvegarde, la valeur de la mémoire disponible (available) reste beaucoup plus élevée (plusieurs dizaines de Go). Cela signifie que le système pourrait fournir cette mémoire aux processus si nécessaire.

A quel endroit apparaissent les pages immenses dans la mémoire libre?

Par défaut, free -m n'affiche pas directement les pages immenses. Pour les voir, vous avez besoin des entrées /proc/meminfo telles que HugePages_Total, HugePages_Free et Hugepagesize.

Puisque le système d'exploitation réserve des pages immenses au démarrage, elles sont effectivement invisibles pour free -m. Elles sont verrouillées et isolées du pool de mémoire général.

Résumé

  • La "mémoire disponible" insuffisante constatée vers 02h00 est due au remplissage du cache de pages Linux par des lectures de sauvegarde. Il s'agit d'un comportement normal qui n'indique pas une pénurie de mémoire.
  • Les pages immenses réservées à IRIS ne sont pas affectées et continuent à servir efficacement la base de données.
  • La mémoire réellement disponible pour les applications est mieux mesurée par la colonne disponible, qui montre que le système dispose encore d'une marge suffisante.

Mais attendez, que se passe-t-il si je n'utilise pas les Huge Pages?

Généralement, on n'utilise pas les Huge Pages sur les systèmes non productifs ou à mémoire limitée. Les gains de performances des Huge Pages ne sont généralement pas significatifs en dessous de 64 Go, bien qu'il soit toujours recommandé d'utiliser les Huge Pages pour protéger la mémoire partagée IRIS.

A propos. J'ai vu des sites rencontrer des problèmes en allouant des pages immenses moins grandes que la mémoire partagée, ce qui oblige IRIS à essayer de démarrer avec des tampons globaux très petits ou à échouer au démarrage si memlock est utilisé (envisagez memlock=192 pour les systèmes de production).

Sans Huge Pages, les segments de mémoire partagée IRIS ( globales, routines, gmheap, etc.) sont alloués à partir de pages de mémoire normales du système d'exploitation. Cela apparaîtrait sous la mémoire "utilisée" dans free -m. Cela contribuerait également à réduire la mémoire "disponible", car cette mémoire ne peut pas être facilement récupérée.

  • utilisée – Beaucoup plus élevée, reflétant la mémoire partagée IRIS + le noyau + d'autres processus.
  • libre – Probablement moins suffisante, car plus de RAM est allouée en permanence à IRIS dans le pool régulier.
  • buf/cache – Augmenterait toujours pendant les sauvegardes, mais la marge apparente pour les processus semblerait plus restreinte, car la mémoire IRIS se trouve dans le même pool.
  • disponible – Plus proche de la véritable “mémoire libre + cache récupérable” moins la mémoire IRIS. Cela semblerait plus petit que dans votre configuration Huge Pages.

Alors, faut-il utiliser Huge Pages dans des systèmes de production?

OUI!

Pour la protection de la mémoire. La mémoire partagée IRIS est protégée contre:

  • Remplacement en cas de sollicitation de la mémoire.
  • Concurrence avec les opérations du système de fichiers telles que les sauvegardes et les copies de fichiers, comme nous l'avons vu dans cet exemple.

Autres remarques - trop profondément dans les détails...

Comment les données sont-elles collectées?

La commande utilisée dans ^SystemPerformance pour une collecte de 24 heures (17 280 secondes) avec des coches toutes les 5 secondes est la suivante:

free -m -s 5 -c 17280 | awk '{now=strftime(""%m/%d/%y %T""); print now "" "" $0; fflush()}' > ","/filepath/logs/20250315_000100_24hours_5sec_12.log

0
0 22
Article Sylvain Guilbaud · Août 29, 2025 1m read

Rubrique FAQ InterSystems

Par défaut, l'ordre des colonnes d'une table est déterminé automatiquement par le système. Pour modifier cet ordre, définissez explicitement l'ordre de chaque propriété à l'aide du mot-clé SqlColumnNumber lors de la définition de la classe.

Exemple :

Property Name As %String [SqlColumnNumber = 2];

Veuillez consulter la documentation ci-dessous.

SqlColumnNumber

Si vous souhaitez modifier le nom de la table SQL, spécifiez SqlTableName. Si vous souhaitez modifier le nom de la colonne (nom du champ), spécifiez SqlFieldName.

0
0 16
Article Sylvain Guilbaud · Juil 8, 2025 3m read

Si vous migrez d'Oracle vers InterSystems IRIS, comme beaucoup de mes clients, vous risquez de rencontrer des modèles SQL spécifiques à Oracle nécessitant une conversion.

Prenons l'exemple suivant:

SELECT (TO_DATE('2023-05-12','YYYY-MM-DD') - LEVEL + 1) AS gap_date
FROM dual
CONNECT BY LEVEL <= (TO_DATE('2023-05-12','YYYY-MM-DD') - TO_DATE('2023-05-02','YYYY-MM-DD') + 1);

Dans Oracle:

  • LEVEL est une pseudo-colonne utilisée dans les requêtes hiérarchiques (CONNECT BY). Elle commence à 1 et s'incrémente de 1.
  • CONNECT BY LEVEL <= (...) détermine le nombre de lignes à générer.
  • La différence entre les deux dates plus un donne 11, donc la requête génère 11 lignes, en comptant à rebours à partir du 12 mai 2023 jusqu'au 2 mai 2023.

Répartition du résultat:

LEVEL = 1  → 2023-05-12
LEVEL = 2  → 2023-05-11
...
LEVEL = 11 → 2023-05-02

La question est maintenant de savoir comment obtenir ce résultat dans InterSystems IRIS, qui ne prend pas en charge CONNECT BY?

Une solution consiste à implémenter une requête de type SQL à l'aide d'ObjectScript qui imite ce comportement. Vous trouverez ci-dessous un exemple de définition CREATE QUERY qui accepte une date de début STARTDATE et un nombre de jours DAYS, et renvoie la liste des dates par ordre descendant.


✅ InterSystems IRIS: mise en œuvre d'une requête de l'intervalle de date

CREATE QUERY GET_GAP_DATE(IN STARTDATE DATE, IN DAYS INT)
  RESULTS (GAP_DATE DATE)
  PROCEDURE
  LANGUAGE OBJECTSCRIPT

Execute(INOUT QHandle BINARY(255), IN STARTDATE DATE, IN DAYS INT) { SET QHandle("start") = STARTDATE SET QHandle("days") = DAYS SET QHandle("level") = 1 RETURN $$$OK }

Fetch(INOUT QHandle BINARY(255), INOUT Row %List, INOUT AtEnd INT) { IF (QHandle("level") > QHandle("days")) { SET Row = "" SET AtEnd = 1 } ELSE { SET Row = $ListBuild(QHandle("start") - QHandle("level") + 1) SET QHandle("level") = QHandle("level") + 1 } RETURN $$$OK }

Close(INOUT QHandle BINARY(255)) { KILL QHandle QUIT $$$OK }

Vous pouvez exécuter la commande CREATE QUERY Vous pouvez exécuter la commande CREATE QUERY ci-dessus dans le portail IRIS System Management, ou via un outil tel que DBeaver ou un éditeur Python/Jupyter Notebook utilisant JDBC/ODBC.


🧪 Exemple d'utilisation:

Pour générer le même résultat que la requête Oracle ci-dessus, utilisez:

SELECT * FROM GET_GAP_DATE(
  TO_DATE('2023-05-12', 'YYYY-MM-DD'),
  TO_DATE('2023-05-12', 'YYYY-MM-DD') - TO_DATE('2023-05-02', 'YYYY-MM-DD') + 1
);

Cela donnera le résultat suivant:

GAP_DATE
----------
2023-05-12
2023-05-11
...
2023-05-02
(11 rows)

🔁 Utilisation avancée: Jointure avec d'autres tables

Vous pouvez également utiliser cette requête comme sous-requête ou dans des jointures:

SELECT * 
FROM GET_GAP_DATE(TO_DATE('2023-05-12', 'YYYY-MM-DD'), 11) 
CROSS JOIN dual;

Cela vous permet d'intégrer des plages de dates dans des flux SQL plus importants.


J'espère que cela sera utile à tous ceux qui sont confrontés à des scénarios de migration d'Oracle vers IRIS ! Si vous avez mis au point des solutions alternatives ou si vous proposez des améliorations, n'hésitez pas à me faire part de vos commentaires.

0
0 31
Question Franck Hanotin · Juil 3, 2025

Bonjour,

j'essaie de faire une connexion avec un serveur SFTP avec 

set ssh = ##class(%Net.SSH.Session).%New()

set Status=ssh.Connect(Server,FtpPort)

set Status=ssh.AuthenticateWithKeyPair(User,"E:\Datas\export\ClePublic.txt","E:\Clé ssh\id-rsa.ppk","")

et là j'ai l'erreur:

TOOLS>zw Status                                                                

1
0 45
Article Sylvain Guilbaud · Avr 22, 2025 3m read

Rubrique FAQ InterSystems

Les variables globales temporaires stockées dans les bases de données IRISTEMP/CACHETEMP sont utilisées lorsqu'un processus n'a pas besoin de stocker des données indéfiniment, mais requiert les performances élevées des variables globales. Les bases de données IRISTEMP/CACHETEMP ne sont pas journalisées ; leur utilisation ne crée donc pas de fichiers journaux.

Le système utilise les bases de données IRISTEMP/CACHETEMP pour le stockage temporaire et les utilisateurs peuvent y accéder à cette fin.

0
0 30
InterSystems officiel Adeline Icard · Mars 6, 2025

À partir de la version 2025.1 de la plateforme de données InterSystems IRIS®, InterSystems abandonne officiellement MultiValue et l'inclut dans la liste des fonctionnalités obsolètes et abandonnées. Bien qu'InterSystems continue de prendre en charge les clients existants qui utilisent MultiValue, cette fonctionnalité n'est pas recommandée pour les nouvelles applications.

Ce que cela signifie pour vous :

0
0 31
Article Sylvain Guilbaud · Mars 11, 2024 6m read

Définition de la variable d'environnement TZ sur Linux

La liste de contrôle de la mise à jour (Update Checklist) pour v2015.1 recommande de définir la variable d'environnement TZ sur les plates-formes Linux et renvoie à la page de manuel de tzset. Cette recommandation vise à améliorer les performances des fonctions de Cache liées à l'heure. Vous pouvez en savoir plus à ce sujet ici:

https://community.intersystems.com/post/linux-tz-environment-variable-not-being-set-and-impact-caché

La page de manuel de mon système de test CentOS 7 ( la même chose pour RHEL 6) indique ce qui suit:

“La fonction tzset() initialise la variable tzname à partir de la variable  d'environnement  TZ.   Cette fonction est automatiquement appelée par les autres fonctions de conversion de l'heure qui dépendent du fuseau horaire.”

Alors, comment définissez-vous TZ? Comment affecte-t-elle les horaires sur un serveur Linux? Voici ce que nous pouvons apprendre:

Le fuseau horaire du système --

Pour mon test, j'utilise Ensemble 2016.1 sur un système virtuel CentOS. Tout d'abord, vérifions le fuseau horaire du système. Pour cela, il faut utiliser l'utilitaire system-config-date.

Qu'en est-il de "L'horloge du système utilise l'UTC" ? Il s'agit de l'horloge matérielle du serveur. Sur un serveur dédié, l'UTC est très répandu. Lorsque Linux est utilisé dans une configuration à double démarrage avec Windows, ce n'est pas le cas (Windows utilise l'heure locale pour son horloge système).

Comme les configurations à double démarrage ne sont pas courantes pour les installations de Cache' et d'Ensemble, la question n'est pas abordée plus en détail. Les idées clés ici consistent à régler le fuseau horaire du système sur celui du serveur et à régler correctement l'heure de l'horloge matérielle.

 

L'heure et la date vues par les utilisateurs –

Jetons un coup d'œil. Voici un extrait de ma session de Terminal:

Tout semble en ordre. Mon processus shell (qui exécute la commande date) et mon processus Cache' affichent la même heure à l'exception des quelques secondes nécessaires à la saisie de la commande WRITE.

Définition de la variable TZ --

Maintenant, définissons TZ dans l'environnement. La commande à utiliser est tzselect. Voici un script de la sortie de la commande pour définir TZ

[ehemdal@localhost ~]$ tzselect

Veuillez indiquer un lieu afin que les règles relatives au fuseau horaire puissent être définies correctement.

Veuillez sélectionner un continent ou un océan.

  1. Afrique

  2. Amériques

  3. Antarctique

  4. Océan Arctique

  5. Asie

  6. Océan Atlantique

  7. Australie

  8. Europe

  9. Océan Indien

  10. Océan Pacifique

  11. aucun - Je veux spécifier le fuseau horaire en utilisant le format Posix TZ.

#? 2

Veuillez sélectionner un pays.

  1. Anguilla 19) République dominicaine 37) Pérou

  2. Antigua -et-Barbuda & 20) Équateur 38) Porto Rico

  3. Argentine 21) Salvador 39) Saint-Barthélemy

  4. Aruba 22) Guyane française 40) Saint-Christophe-et-Niévès;

  5. Bahamas 23) Groenland 41) Sainte-Lucie

  6. Barbade 24) Grenade 42) Saint-Martin (Néerlandais)

  7. Bélize 25) Guadeloupe 43) Saint-Martin (Français)

  8. Bolivie 26) Guatemala 44) Saint-Pierre-et-Miquelon;

  9. Brésil 27) Guyane 45) Saint Vincent

  10. Canada 28) Haïti 46) Suriname

  11. Caraïbes NL 29) Honduras 47) Trinité-et-Tobago;

  12. Îles Caïmans 30) Jamaïque 48) Îles Turques et Caïques Est

  13. Chili 31) Martinique 49) États-Unis

  14. Colombie 32) Mexique 50) Uruguay

  15. Costa Rica 33) Montserrat 51) Vénézuéla

  16. Cuba 34) Nicaragua 52) Îles Vierges britanniques

  17. Curaçao 35) Panama 53) Îles Vierges (États-Unis)

  18. Dominique 36) Paraguay

#? 49

Veuillez sélectionner l'une des régions de fuseau horaire suivantes.

  1. Est (la plupart des régions) 16) Centre-ND (Morton rural)

  2. Est-MI (la plupart des régions) 17) Centre-ND (Mercer)

  3. Eastern - KY (région de Louisville) 18) Montana (la plupart des régions)

  4. Est - KY (Wayne) 19) Montana - ID (sud); OR (est)

  5. Est - IN (la plupart des régions) 20) MST - Arizona (sauf Navajo)

  6. Est - IN (Da, Du, K, Mn) 21) Pacifique

  7. Est - IN (Pulaski) 22) Alaska (la plupart des régions)

  8. Est - IN (Crawford) 23) Alaska - région de Juneau

  9. Est - IN (Pike) 24) Alaska - région de Sitka

  10. Est - IN (Switzerland) 25) Alaska - Île d'Annette

  11. Centre (la plupart des régions) 26) Alaska - Yakutat

  12. Centre - IN (Perry) 27) Alaska (ouest)

  13. Centre - IN (Starke) 28) Îles Aléoutiennes

  14. Centre - MI (frontière du Wisconsin) 29) Hawaï

  15. Centre - ND (Oliver)

#? 1

Les informations suivantes ont été données:

  États-Unis

  Est (la plupart des régions)

Par conséquent, TZ='America/New_York' sera utilisé.

L'heure locale est maintenant: Mar 31 mai 11:21:04 EDT 2016.

Le temps universel est maintenant: Mar 31 mai 15:21: 04 UTC 2016.

Les informations ci-dessus sont-elles correctes?

  1. Oui

  2. Non

#? 1

Vous pouvez rendre cette modification permanente par vous-même en ajoutant la ligne

  TZ='America/New_York'; export TZ

dans le fichier '.profile' dans votre répertoire personnel, puis déconnectez-vous et reconnectez-vous.

Voici à nouveau la valeur de TZ, cette fois sur la sortie standard, afin que vous

puissiez utiliser la commande /usr/bin/tzselect dans des scripts shell:

America/New_York

[ehemdal@localhost ~]$

Le fichier ~/.profile (s'il existe) est exécuté lorsque vous vous connectez et définit la variable TZ pour vous. Vous pouvez utiliser un fichier d'initialisation différent si vous utilisez un shell autre que /bin/sh ou /bin/bash. J'ai défini cette variable et je me suis reconnecté. Si vous mettez à jour un fichier comme /etc/profile, vous pouvez appliquer cela à tous les utilisateurs.

Ici, vous pouvez voir que la TZ est définie pour mon utilisateur (ehemdal), mais PAS pour l'utilisateur root.

TZ et l'heure et la date vues par les utilisateurs –

Que se passe-t-il si un utilisateur se connecte à votre serveur depuis un autre fuseau horaire ? La variable TZ permet de conserver l'heure locale de l'utilisateur tout en laissant la gestion des fuseaux horaires et de l'heure d'été au système d'exploitation. Cela affecte également l'heure utilisée par Cache'. Par exemple, j'ai décidé de changer mon fuseau horaire pour celui d'Honolulu.

Voici deux captures d'écran qui montrent le résultat.

Le processus de mon utilisateur a défini le fuseau horaire sur Pacifique/Honolulu. Le processus root n'a pas défini de TZ (il utilise donc le fuseau horaire du système America/New_York). Au niveau du système d'exploitation (avec la commande date), l'affichage reflète l'heure locale pour les deux utilisateurs. La commande date reflète l'heure locale de l'utilisateur (HST pour l'utilisateur ehemdal et EDT pour root). Comme $HOROLOG obtient sa valeur à partir de l'heure du système d'exploitation disponible pour le processus utilisateur, les valeurs de $H sont DIFFÉRENTES pour les deux utilisateurs.

J'ai choisi l'heure d'Honolulu comme exemple intéressant car Hawaï n'observe pas l'heure d'été.  En paramétrant correctement TZ pour tous les utilisateurs, l'heure locale peut "avancer et reculer" pour les utilisateurs qui observent l'heure d'été, et rester stable pour ceux qui n'observent pas l'heure d'hiver.

1
0 490
Article Guillaume Rongier · Jan 24, 2025 6m read

Dans le cadre du concours Open Exchange, l'hôpital Salford Royal (Dean White et Mark O'Reilly) a développé une API REST pour SharePoint, un modèle qui fonctionne mais qui peut aussi servir de point de départ à vos propres applications REST 

Conditions préalables

Lorsque vous utilisez la v1 du service REST de l'API de Sharepoint, vous avez besoin d'un identifiant locataire, d'un identifiant client, d'un code secret client et d'un nom de locataire 

Configuration 

Configuration d'un serveur OAuth

 

Le code au milieu est l'identifiant locataire 

Créez un nom de config client comme vous le souhaitez 

Configurer votre client oauth en remplaçant l'adresse IP de votre serveur par l'adresse IP du serveur sur lequel vous vous trouvez (pas l'adresse VIP - si vous ne faites pas partie d'un VIP, l'adresse locale peut fonctionner) 

Ajouter les informations d'identification client 

modifiez les paramètres sur SharepointRESTConnector comme HTTPSERVER,SHAREPOINT-SITENAME- SHAREPOINT FILEPATH- SSL (vierge jusqu'à la v. 1.3) les paramètres remplacent le nom locataire et l'identifiant locataire. 

Code 

SharePointOnlineRESTOperation

OAuth Scope n'est pas utilisateur dans cet exemple  mais est laissé ici comme modèle si vous en avez besoin pour d'autres implémentations rest 

Il utilise et s'appuie sur rest par défaut   Set tSC=..AddAccessToken(.tHttpRequest) qui gère le jeton et transmet toutes les propriétés supplémentaires requises pour l'API. Pour l'API SharePoint, une ressource est nécessaire et cette dernière est ajoutée dans les paramètres dans les notes de commentaires 

/// Pour SPO, les paramètres doivent être {"resource":"00000003-0000-0ff1-ce00-000000000000/{TennantName}.sharepoint.com@{TennantID}"} <p>/// 00000003-0000-0ff1-ce00-000000000000 est l'identifiant ResourceID attribué à SPO par Microsoft et ne doit pas être modifié <p>/// {TennantName} doit être modifié pour être identique à celui du serveur HTTP, par exemple intersystems.sharepoint.com <p>/// {TennantID} est l'identifiant de votre nom de serveur 

Obtention d'une liste de fichiers 

Appelle la liste des fichiers dans le répertoire dont vous disposez. Cette fonction peut indiquer le temps écoulé depuis le dernier téléchargement ou la durée totale des fichiers Elle interroge l'en-tête

Elle appelle GetFolderByServerRealativeURL

Set..Adapter.URL="/sites/"_$$$URLENCODE(..SharepointSitename)_"/_api/web/GetFolderByServerRelativeUrl('"_$$$URLENCODE(..SharepointFilePath)_"')/Files"_filter Set..Adapter.URL="/sites/"_$$$URLENCODE(..SharepointSitename)_"/_api/web/GetFolderByServerRelativeUrl('"_$$$URLENCODE(..SharepointFilePath)_"')/Files"_filter

 La réponse est lue par le processeur. 

Il envoie des messages http comme le ferait POSTMAN 

Une méthode de réponse Constuct a été tirée de l'opération générique écrite par intersystems pour renvoyer des réponses http 

Suppression du fichier

Appelle une demande d'envoi de suppression à getfolderbyserverrelativeurl/files getfolderbyserverrelativeurl/files 

lignes clés ci-dessous 

Set..Adapter.URL="/sites/"_$$$URLENCODE(..SharepointSitename)_"/_api/web/GetFolderByServerRelativeUrl('"_$$$URLENCODE(..SharepointFilePath)_"')/Files('"_$$$URLENCODE(pRequest.FileName)_"')"Set tSC=..AddAccessToken(.tHttpRequest)
  	s tSC = ..SendRequest(.tHttpResponse,send,tHttpRequest, .pResponse)
    Quit..constructResponse(.tHttpResponse,.pResponse)

Téléchargement du fichier

Si c'est un Ens.StringContainer (vous pourriez en faire un message sur mesure qui l'étendrait, comme Messages.DownloadSharpointFile), il lit le nom et l'envoie dans l'url de l'api. Il lit le paquet de réponses et ajoute le flux binaire à un steamcontainer. Comme toujours, il faut créer le flux et l'empaqueter dans le streamcontainer. 

Code clé ci-dessous (quelques s ont été modifiés pour être affichés ici) 

set binaryStream =##Class(%Stream.FileBinary).%New()
  Set tSC=..AddAccessToken(.tHttpRequest)
  Set..Adapter.URL="/sites/"_$$$URLENCODE(..SharepointSitename)_"/_api/web/GetFolderByServerRelativeUrl('"_$$$URLENCODE(..SharepointFilePath)_"')/Files('"_$$$URLENCODE(pRequest.StringValue)_"')/OpenBinaryStream()"Set tHttpResponse = ##class(%Net.HttpResponse).%New()
  set send="GET"set tSC = ..SendRequest(.tHttpResponse,send,tHttpRequest, .pResponse)
  set pDownloadResponse =##Class(Ens.StreamContainer).%New(binaryStream)
  set pDownloadResponse.OriginalFilename=pRequest.StringValue
	

Ajout du fichier

GetFolderByServerRelativeUrl/filepath/Files/add(url=filename,overwrite?)

Lignes clées 

Set..Adapter.URL="/sites/"_$$$URLENCODE(..SharepointSitename)_"/_api/web/GetFolderByServerRelativeUrl('"_$$$URLENCODE(..SharepointFilePath)_"')/Files/add(url='"_fn_"',overwrite="_$$$URLENCODE(..OverwriteExistingFile)_")"Set tSC=..AddAccessToken(.tHttpRequest)
  s tHttpRequest.EntityBody=##Class(%Stream.FileBinary).%New()
	s sc=tHttpRequest.EntityBody.CopyFromAndSave(pFileToUpload.Stream)
	Set tHttpResponse = ##class(%Net.HttpResponse).%New()
	S send="POST"s tSC = ..SendRequest(.tHttpResponse,send,tHttpRequest, .pResponse)

Envoi de la demande 

Cette fonction permet d'envoyer toute requête qui attend une réponse http. 

Assure le transfert des réponses et l'envoi de ENSLIB.HTTP.GenericMessage. De nombreux en-têtes sont renvoyés et une case à cocher permet de simplifier la réponse pour qu'elle ne contienne qu'un code d'erreur et des données. 

Construction de la réponse

Utilisé ailleurs dans l'EIT et non dans le code original de cette méthode

AddAccessToken

C'était le véritable apprentissage. il s'agit d'un code par défaut pour utiliser les paramètres OAuth Intersystems et non pas un code en dur à chaque fois que nous avons besoin de l'utiliser. 

Tout est construit autour de trois appels 

est autorisé et 

Set isAuthorised = ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..OAuthClientApplicationName,sessionId,..OAuthScope,.accessToken,,.responseProperties,.error)

 Obtenez un jeton d'accès

Set tSC = ##class(%SYS.OAuth2.Authorization).GetAccessTokenClient(..OAuthClientApplicationName,..OAuthScope,.properties,.error,.sessionId)

et un jeton d'ajout qui l'ajoute à l'en-tête - malheureusement, il ne semble pas qu'il puisse l'ajouter au corps du message si l'identifiant est requis par d'autres API

;La valeur par défaut de sslConfiguration provient de l'instance OAuth2.Client.        Set tSC  = ##class(%SYS.OAuth2.AccessToken).AddAccessToken(pHttpRequest,sendType,,..OAuthClientApplicationName,sessionId)

En outre, l'API Sharepoint nécessite une ressource. Nous avons généralisé l'utilisation de JSON, et si vous avez besoin d'autres paramètres, nous avons pensé à les ajouter en JSON afin de pouvoir réutiliser le modèle à l'avenir.

il l'ajoute à l'objet chaîne de caractères utilisé par les propriétés. il s'agit d'une chaîne de caractères sérialisée sous forme de tableau ou similaire 

s paramsarr = [].%FromJSON(..Params)
            s iterator = paramsarr.%GetIterator()
            s properties=""While iterator.%GetNext(.key,.value)
            {
                s properties(key)=value
            }

Exemples de traces

 

Obtention de la liste des fichiers

Téléchargement de fichiers

suppression de fichiers

si vous cochez cette case 

Ajout de fichiers

Remerciements à @Dean White 
 

https://youtu.be/485dTXYp2BU

Mise à jour - ajout d'un lien YouTube et correction du lien d'échange ouvert;

0
0 57
Annonce Iryna Mykhailova · Jan 17, 2025

Depuis quelques mois, nous avons automatisé l'analyse du code source des projets sur Github, afin que toute personne possédant un projet open source puisse faire analyser son code sans frais.

Il vous suffit de créer le fichier ".github/workflows/objectscript-quality.yml" dans votre projet avec le contenu suivant :

0
0 46
Article Robert Cemper · Sept 15, 2024 3m read

Vous connaissez probablement cette situation :
il y a quelque temps, vous avez trouvé une fonction $ZU très spéciale pour
un type de problème spécifique. Une sorte de formule mystique.
Elle est devenue populaire et a été utilisée par de nombreux programmeurs
dans tout votre code et toutes vos installations .

Plusieurs versions et updates plus tard, vous êtes informé par ISC que
votre $ZU mystique est déprécié et n'est plus supporté. Et on vous conseille
de le remplacer par un nouveau $something(). 

0
0 56
Article Guillaume Rongier · Août 29, 2024 4m read

   

📜 Sainte Thècle, verset  8: "Étendez votre souris sur l'écran, et l'océan de données ouvrira un chemin devant vous!!"

Bonjour la communauté, tout d'abord, toutes mes excuses si quelqu'un a été offensé par le blasphème 😔

Avez-vous déjà pensé d'avoir séparé le code source et les données de la base de données, ce qui serait intéressant? Peut-être aimeriez-vous pouvoir sauvegarder votre code sans avoir à copier des giga-octets d'informations de vos données clients.

Je vais vous expliquer les étapes à suivre pour séparer votre océan de code source et de données d'espace de noms dans deux bases de données différentes.

Pour cet exemple, je vais partir d'un nouvel espace de noms que je vais créer pour l'exemple.

Tout d'abord, nous allons créer deux nouvelles bases de données:

1. Accédez au portail et à la section Bases de données locales:

Cliquerons sur le bouton "Créer une nouvelle base de données":

Nous le nommerons et indiquerons le dossier dans lequel il sera stocké (je l'ai préfixé "Tutoriel" car j'ai l'intention d'appeler l'espace de noms "Tutoriel"):

Il nous permet maintenant de choisir la taille que nous voulons attribuer et de déterminer si nous voulons activer la journalisation pour cela:

Sélectionnons "Créer une nouvelle ressource":

Nous attribuons le nom de la nouvelle ressource, nous mettons la description et, si nous le jugeons approprié, nous marquons les autorisations d'accès public:

Suivons donc ensuite les mêmes étapes pour l'autre base de données:

Sélectionnons également "Créer une nouvelle ressource":

Maintenant, nous avons créé nos deux bases de données.

Maintenant, créons le nouvel espace de noms ("Namespace") et attribuons-lui les deux bases de données que nous venons de créer.

Nous accédons à la section "Namespaces"::

Nous cliquons sur le bouton "Créer un nouveau Namespace".

Attribuons-lui un nom et choisissons la base de données pour les données et pour le code source, puis cliquons sur le bouton "Enregistrer" (Save).

Et voilà, nous avons créé notre tout nouveau Namespace avec deux bases de données distinctes, l'une pour les données et l'autre pour le code source.

Cela pourrait également être très utile dans des situations telles que... Imaginez que vous ayez une base de données contenant une grande quantité de données partagées par tous les membres de l'équipe de développement.

Ces membres peuvent avoir des ordinateurs portables qu'ils transportent et vous ne voudriez pas que ces ordinateurs portables aient une copie des données de la base localement, soit pour des raisons de sécurité, soit parce que vous pourriez avoir à créer des données de test sur chacun d'entre eux.

Dans ce cas, il peut être judicieux de créer le code source du Namespace localement et les routines dans une base de données distante. "Quoi? Comment connecter une base de données distante?" C'est simple comme bonjour!

Connexion à une base de données distante. **Vous devez préalablement configurer le serveur distant comme ECP (je l'explique à la fin du tutoriel).

Nous accédons à la section des serveurs distants pour le configurer (si nous ne l'avons pas déjà configuré).

Nous cliquons sur le bouton "Serveurs de données":

Et nous ajoutons le nouveau serveur.

Nous remplissons les informations:

Par défaut, il est désactivé. Cliquons sur le bouton "Modifier l'état" pour l'activer:

Nous passons maintenant à la section "Bases de données distantes".

Et nous cliquons sur le bouton "Créer une base de données distante":

Sélectionnons le serveur qui possède la base de données distante et choisissons-le dans le menu déroulant:

Ensuite, nous allons créer un nouveau Namespace "hHybride" avec les informations du code source sur la machine locale et les données sur un serveur distant.

Nous accédons à la section Namespaces et cliquons sur le bouton "Créer un Namespace". Pourtant, cette fois dans la section Database pour les globales nous allons sélectionner l'option "Remote Database" et choisir la base de données distante que nous avons créée:

Et maintenant, nous avons configuré notre incroyable Namespace hybride!

**Configuration du serveur comme ECP (pour pouvoir servir des bases de données distantes, cela nécessite une licence payante):

Définissons le nombre maximum de serveurs de données, SSL, etc. et cliquons sur les boutons "Sauvegarder" et "Activer":

Maintenant nous pouvons avoir nos données séparées sur le même serveur ou même sur des serveurs différents.

Voici une vidéo qui explique comment créer les bases de données et comment créer et configurer le Namespace:

<iframe allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/uHT0d_qEPUc" width="640"></iframe>

J'espère que ce tutoriel vous rendra plus agréable la longue traversée du désert 🌴🐪 pour obtenir le pardon du CTO / CIO jusqu'à ce que vous atteigniez la retraite promise.

Rendez-vous dans la prochaine publication! Faites-moi savoir si vous avez trouvé cet article intéressant; tous vos commentaires ou questions sont toujours les bienvenus. 🙌

0
0 59
InterSystems officiel Adeline Icard · Juil 15, 2024

Du 15 octobre 2024, la prise en charge de Caché & Ensemble sur MacOS sera obsolète.

Caché & Ensemble 2018.1.9 continuera d'être pris en charge, mais il n'y aura pas d'autres versions de maintenance pour MacOS. Cela signifie que Caché & Ensemble 2018.1.9 sera la version finale de ces produits sur MacOS.

Pour rappel, les versions de maintenance pour Caché et Ensemble sur les autres plateformes prises en charge prendront fin le 31 mars 2027.  Plus de détails à ce sujet peuvent être trouvés dans l'annonce de l'année dernière.

0
0 44
Article Pierre LaFay · Juin 26, 2024 2m read

InterSystems FAQ rubric

Le type TIMESTAMP correspond au type de données %Library.TimeStamp (=%TimeStamp) dans les produits InterSystems, et le format est YYYY-MM-DD HH:MM:SS.nnnnnnnnn.

Si vous souhaitez modifier la précision après la virgule, définissez-la à l'aide de la méthode suivante.

1) Définir la précision à l'échelle du système

Portail de gestion : [Administration du système] > [Configuration] > [Paramètres SQL et objets] > [Paramètres SQL généraux] 
Précision de l'heure par défaut pour GETDATE(), CURRENT_TIME, CURRENT_TIMESTAMP. Vous pouvez spécifier le nombre de chiffres entre 0 et 9.

0
0 45
Article Pierre LaFay · Juin 26, 2024 1m read

InterSystems FAQ rubric

Pour désactiver le délai d'attente, réglez le délai d'attente de la requête sur désactivé dans les paramètres DSN :

Panneau de configuration de Windows > Outils d'administration > Sources de données (ODBC) > Configuration DSN du système

Si vous cochez la case Désactiver le délai d'attente des requêtes, le délai d'attente sera désactivé.

Si vous souhaitez le modifier du côté de l'application, vous pouvez le définir au niveau de l'API ODBC.

0
0 53
Article Iryna Mykhailova · Juin 17, 2024 2m read

FAQ InterSystems

Vous pouvez utiliser la classe %IndexBuilder pour effectuer une reconstruction d'index à l'aide de plusieurs processus. L'exemple suivant a pour but de définir l'index standard HomeStateIdx pour la colonne Home_State (informations d'état de l'adresse de contact) de Sample.Person.

Les étapes sont les suivantes:

1. Masquez le nom de l'index à ajouter/reconstruire à partir de l'optimiseur de requêtes.

>write$system.SQL.SetMapSelectability("Sample.Person","HomeStateIdx",0)
1
0
0 60
Article Pierre LaFay · Juin 12, 2024 1m read

InterSystems FAQ rubric

Si vous souhaitez exécuter un fichier exécutable du système d'exploitation, une commande ou un programme créé dans un produit InterSystems au démarrage de ce dernier, écrivez le traitement dans la routine SYSTEM^%ZSTART. (La routine %ZSTART est créée dans l'espace de noms %SYS).

Avant d'écrire du code dans la routine SYSTEM^%ZSTART, assurez-vous qu'il fonctionne correctement dans toutes les conditions.

0
0 39
InterSystems officiel Sylvain Guilbaud · Juin 4, 2024

À partir de la sortie de la plateforme de données InterSystems IRIS® 2022.3, InterSystems a corrigé le mécanisme d'application des licences pour inclure les requêtes REST et SOAP. En raison de ce changement, les environnements dotés de licences non basées sur le cœur qui utilisent REST ou SOAP peuvent connaître une plus grande utilisation des licences après la mise à niveau. Pour déterminer si cet avis s'applique à votre licence InterSystems, suivez les instructions de la FAQ liée ci-dessous.

Ce tableau résume l'application :

0
0 71
Article Iryna Mykhailova · Mai 13, 2024 1m read

Dans Studio, vous pouviez ouvrir une classe directement par son nom, sans avoir à parcourir l'arborescence du paquetage par de multiples clics jusqu'à ce que vous arriviez à la classe souhaitée.

Vous pouvez faire Ctrl + O ou (File -> Open) et taper simplement le nom de la classe, par exemple :

Appuyez sur la touche "Enter" et la classe est ouverte.

Comment y parvenir en VSCode ?

C'est assez facile en fait, il suffit d'appuyer sur Ctrl + T (pour Windows par exemple), ou Go -> Go to Symbol in Workspace..., tapez le nom de la classe et vous y êtes.

Par exemple :

Cette solution a été mentionnée par @Dmitry Maslennikov comme une suggestion pour le rapport de problème GitHub (désigné comme une demande d'améliorationpour ces fonctionnalités - "Ouverture d'une classe par le nom à partir du serveur".

Voici un petit GIF pour la démonstration :

 

0
0 171
Article Iryna Mykhailova · Mai 10, 2024 2m read

Aimeriez-vous avoir accès au terminal Web directement à partir de votre VSCode ?

Voici une nouvelle entrée dans la rubrique "Trucs et astuces de VSCode" - et elle ressemble beaucoup à la précédente à propos de l'assistant SOAP Wizard.

Même principe et même résultat, bien que le cas d'utilisation soit différent.

Supposons donc que vous désiriez ouvrir le Terminal Web (et pour ceux d'entre vous qui ne sont pas encore familiers avec cet excellent outil créé par l'incroyable @Nikita Savchenko consultez sapage d'accueil) à partir de VSCode - vous pouvez adopter une approche similaire à celle que j'ai décrite dans l'article précédent.

Je reprendrai les étapes -

  • Ouvrez les paramètres JSON de l'extension ObjectScript
  • Ajouter un objet "links" dans l'objet "conn".
  • Ajouter la ligne suivante à l'intérieur de "links"
            "$(terminal) WebTerminal": "${serverUrl}/terminal/?ns=${ns}${serverAuth}"

Il se présentera comme suit -

 

Une fois que vous avez fait cela, vous pouvez trouver Web Terminal dans le menu en cliquant sur Connexion dans la barre d'état inférieure de VSCode.

Ceci est mentionné dans la discussion this VSCode ObjectScript's GitHub Issues (à propos de variables ${...} utilisées dans l'URL ci-dessus), par @John Murray 


Voici un court GIF pour illustrer ce processus (en commençant par le menu standard et en terminant par l'option permettant de lancer le terminal Web). -

 

0
0 65
Article Iryna Mykhailova · Mai 8, 2024 2m read

Another VSCode "Tips & Tricks" entry -

Do you want to see this option in VSCode?

This time we'll focus on how to get the SOAP Wizard as was available in Studio (to define a WSDL-based SOAP Web Service client (and Business Operation), or service).

If you work with Web Services you most probably used the SOAP Wizard in Studio. You would open it via Tools -> Add-Ins

And this opened a "Server Template" -

In VSCode you might be wondering how you can access this.

Well the basic fact you need to understand is that truly this Add-In or Wizard is simply a web page, displayed within Studio per above, and as such can also be accessed in a simple browser. VSCode facilitates the opening of such a browser with the desired content (ultimately constructing the correct URL with the right server name, port, web application, etc.).

The result would be the same as you see an option to open the Management Portal or the Class Reference (with relevant URLs) when you click on the Server Connection on the bottom Status Bar of VSCode, for example:

You will also see an entry for the SOAP Wizard.

You can achieve this by adding a 'links' entry in to your 'conn' object in your Settings JSON of your ObjectScript Extension, and specifying the desired URL (using the relevant variables).

This is mentioned in the VSCode ObjectScript's GitHub Issues discussion under a "SOAP Wizard" issue, with comments by @John Murray and @Ondřej Hoferek, and also referred to in this comment on a Community post by @Timothy Leavitt 
 

The JSON value would be:

"SOAP Wizard": "${serverUrl}/isc/studio/templates/%25ZEN.Template.AddInWizard.SOAPWizard.cls?Namespace=${namespace}${serverAuth}"

And this part would look like this:

Once you have this you will see an extra option when you click on the Connection -

And choosing that option will take you to the desired Wizard (opened in a Browser):

Here's a short GIF demonstrating this process (starting from the regular menu when clicking the Connection, and ending with the menu including the SOAP Wizard).

 

Note there are other Wizards (or Templates) you can add this way (like the XSD Wizard for example).

0
0 86
Article Iryna Mykhailova · Mai 6, 2024 3m read

Salutations à toutes et à tous,

Faut-il connecter votre VSCode à un serveur d'InterSystems ?

Connaissez-vous déjè de cette option ?

Puisque beaucoup de gens commencent à utiliser VSCode comme IDE pour nos produits, j'ai pensé qu'il pourrait être bénéfique de fournir quelques petits trucs et astuces pour les aider à démarrer et à devenir plus productifs. C'est pourquoi je lance cette série de petits articles.

Quelques éclaircissements importants :

1. Il ne s'agit pas d'un tutoriel ou d'une ressource d'apprentissage formelle - si vous recherchez ce type de ressources, veuillez consulter cet excellent exercice en ligne, par exemple, ou examiner cette excellente session de notre dernier Sommet virtuel. 

2. Ces "trucs et astuces" ne sont pas mes inventions - ils sont généralement déjà documentés ou mentionnés quelque part, je voulais simplement les rassembler et peut-être fournir quelques conseils ou exemples supplémentaires. J'essaierai de faire référence à la source le cas échéant.

3. Bien que j'aie déjà noté quelques idées et sujets à aborder dans cette "série", je ne sais pas à quelle vitesse je pourrai les mettre en place, et je ne peux donc pas m'engager sur la fréquence à laquelle ils apparaîtront...

Voici le premier -

Parmi les choses que vous devez faire en premier dans VSCode, il y a la définition de vos connexions au serveur. Vous pouvez le faire manuellement dans le fichier JSON Settings de l'extension InterSystems Server Manger, ou en appuyant sur le signe (+) lorsque vous essayez de vous connecter à un serveur (et en répondant aux invites), mais si vous êtes sous Windows, vous pouvez simplement importer vos définitions de serveur depuis le Registre Windows.

En fait, cela fait partie de la documentation de l'extension (dans la page VSCode Marketplace de l'extension, dans le GitHub Repository Readme, et dans la page Détails de l'extension dans VSCode lui-même - c'est en fait le même texte...) -

Sous Windows, vous pouvez lancer Import Servers from Registry à partir de Command Palette pour créer des entrées de connexion pour toutes les connexions que vous avez précédemment définies avec le gestionnaire de serveur InterSystems (InterSystems Server Manager).

Lorsque vous installez une instance sur une machine Windows ou définissez des serveurs pour un client via l'outil Server Manger, le registre Windows est mis à jour.

Par exemple, j'ai eu un client avec 9 serveurs différents définis sur lesquels il pourrait vouloir se connecter (environnements Prod / Dev / Test, LAN vs. DMZ, etc.), tandis qu'un autre client en a encore plus (ou beaucoup plus). Faire cela un par un dans VSCode pourrait être fastidieux (et sujet à l'erreur humaine). 

Supposons qu'il y ait par exemple la situation suivante sur ma machine, telle qu'elle est définie dans l'InterSystems Server Manager -

Je peux utiliser l'option "Importation de serveurs à partir du registre" de l'extension InterSystems Server Manager.

Ouvrez simplement la palette de commandes (View -&gt Command Palette... or Ctrl+Shift+P) et tapez après >le caractère "InterSystems", ainsi vous verrez l'option suivante -

[Ou une autre option dans le commentaire ci-dessous]

Ensuite, on vous demandera un nom d'utilisateur pour vous connecter à l'un des serveurs (vous n'êtes pas obligé de le saisir si vous ne voulez pas qu'il soit enregistré, mais il vous sera demandé à chaque fois que vous vous connecterez), puis un mot de passe, puis si vous voulez enregistrer le mot de passe pour tous les autres serveurs (vous n'êtes pas obligé d'enregistrer le mot de passe du tout si vous ne voulez pas) et enfin toutes les définitions de serveurs seront ajoutées.

Par exemple, voici ce à quoi ressembleront les paramètres JSON après l'importation, d'après l'image du Gestionnaire de serveur ci-dessus (et en supposant que j'ai fourni un nom d'utilisateur de SuperUtilisateur) -

Voici un petit GIF démontrant ce processus rapide (il commence par la définition des 3 connexions de serveur par défaut, et se termine avec les 5 serveurs supplémentaires selon l'exemple ci-dessus) :

 

0
0 80
Article Lorenzo Scalese · Avr 25, 2024 7m read

En tant que modèle linguistique d'IA, ChatGPT est capable d'effectuer une variété de tâches telles que traduire, écrire des chansons, répondre à des questions de recherche et même générer du code informatique. Avec ses capacités impressionnantes, ChatGPT est rapidement devenu un outil populaire pour diverses applications, des chatbots à la création de contenu.
Mais malgré ses capacités avancées, ChatGPT n'est pas en mesure d'accéder à vos données personnelles. Mais malgré ses capacités avancées, ChatGPT n'est pas en mesure d'accéder à vos données personnelles. Ainsi, dans cet article, je vais démontrer les étapes suivantes pour construire une IA ChatGPT personnalisée en utilisant le LangChain Framework:

  • Étape 1: Chargement du document 

  • Étape 2: Division du document en blocs

  • Étape 3: Utilisation de l'incorporation pour des blocs de données et leur conversion en vecteurs

  • Étape 4: Enregistrement des données dans la base de données de vecteurs

  • Étape 5: Obtention des données (question) de l'utilisateur et leur the intégration

  • Étape 6: Connexion à VectorDB et recherche sémantique

  • Étape 7: Récupération des réponses pertinentes basées sur les requêtes de l'utilisateur et leur envoi au LLM(ChatGPT)

  • Étape 8: Obtention d'une réponse de la part de LLM et renvoi de celle-ci à l'utilisateur

  REMARQUE : Veuillez lire mon article précédent LangChain – Unleashing the full potential of LLMs (LangChain - Libération du plein potentiel des LLM) pour obtenir plus de détails sur LangChain et sur la façon d'obtenir la clé API OpenAI

Alors, commençons
     

Étape 1: Chargement du document 

Tout d'abord, il faut charger le document. Nous allons donc importer PyPDFLoader pour le document PDF 

ClassMethod SavePDF(filePath) [ Language = python ]
{
#pour un fichier PDF, il faut importer PyPDFLoader à partir du framework langchainfrom langchain.document_loaders import PyPDFLoader
# pour un fichier CSV, il faut importer csv_loader# pour un fichier Doc, il faut importer UnstructuredWordDocumentLoader# Pour le document texte, il faut importer TextLoader#importation de l'os pour définir la variable d'environnementimport os
#attribution de la clé API OpenAI à une variable d'environnement 
os.environ['OPENAI_API_KEY'] = "apiKey"#Init du lanceur
loader = PyPDFLoader(filePath)   
#Chargement du document 
documents = loader.load()
return documents
}

Étape 2: Division du document en blocs

Les modèles linguistiques sont souvent limités par la quantité de texte qui peut leur être transmise. Il est donc nécessaire de les diviser en blocs moins volumineux. LangChain fournit plusieurs utilitaires pour ce faire.

L'utilisation d'un séparateur de texte (Text Splitter) peut également contribuer à améliorer les résultats des recherches dans les répertoires de vecteurs, car, par exemple, des blocs moins volumineux ont parfois plus de chances de correspondre à une requête. Tester différentes tailles de blocs (et leur chevauchement) est un exercice intéressant pour adapter les résultats à votre cas d'utilisation.

ClassMethod splitText(documents) [ Language = python ]
{
#Afin de diviser le document, il faut importer RecursiveCharacterTextSplitter du framework Langchain  from langchain.text_splitter import RecursiveCharacterTextSplitter
#Init du séparateur de texte, définition de la taille des blocs (1000) et du chevauchement. = 0
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
#Division du document en plusieurs blocs
texts = text_splitter.split_documents(documents)
return texts
}

Étape 3: Utilisation d'incorporation pour des blocs de données et leur conversion en vecteurs

Les incorporations (embeddings) de texte sont le cœur et l'âme des outils de Large Language Operations.. Techniquement, nous pouvons travailler avec des modèles linguistiques en langage naturel, mais le stockage et l'extraction du langage naturel sont très inefficaces. 

Pour améliorer l'efficacité, il faut transformer les données textuelles en formes vectorielles. Il existe des modèles ML dédiés à la création d'intégrations à partir de textes. Les textes sont convertis en vecteurs multidimensionnels. Une fois ces données incorporées, nous pouvons les regrouper, les trier, les rechercher, etc. Nous pouvons calculer la distance entre deux phrases pour connaître leur degré de parenté. Et le plus intéressant, c'est que ces opérations ne se limitent pas à des mots-clés comme dans les recherches traditionnelles dans les bases de données, mais capturent plutôt la proximité sémantique de deux phrases. Cela rend le système beaucoup plus puissant, grâce à l'apprentissage automatique.
 

Les modèles d'incorporation de texte reçoivent la saisie de texte et renvoient une liste de flottants (embeddings), qui sont la représentation numérique du texte saisi. Les embeddings permettent d'extraire des informations d'un texte. Ces informations peuvent ensuite être utilisées, par exemple, pour calculer les similitudes entre des textes (par exemple, des résumés de films).

Les modèles d'incorporation de texte reçoivent la saisie de texte et renvoient une liste de flottants

    ClassMethod getEmbeddings(query) [ Language = python ]
    {
    #Obtention d'un modèle d'embeddings à partir du framework Langchainfrom langchain.embeddings import OpenAIEmbeddings
    #Definition d'embedding
    embedding = OpenAIEmbeddings()
    return embedding
    }
    

Étape 4: Enregistrement des données dans la base de données de vecteurs

    ClassMethod saveDB(texts,embedding) [ Language = python ]
    {
    #Obtention de la base de données Chroma à partir de langchainfrom langchain.vectorstores import Chroma      
    # Incorporation et stockage des textes# La fourniture d'un répertoire persistant (persist_directory) permet de stocker les embeddings sur le disque# par exemple dans le dossier myData du chemin d'accès à l'application en cours
    persist_directory = "myData"
    vectordb = Chroma.from_documents(documents=texts, embedding=embedding, persist_directory=persist_directory)
    #sauvegarde du document au niveau local
    vectordb.persist()
    vectordb = None
    }
    

Étape 5: Obtention des données (question) de l'utilisateur et leur the intégration

    ClassMethod getVectorData(query) [ Language = python ]
    {
    #REMARQUE: Il faudrait avoir le même embedding utilisée lorsque nous avons sauvegardé des donnéesfrom langchain.embeddings import OpenAIEmbeddings
    #obtention d'embeddings
    embedding = OpenAIEmbeddings()
    #saisie des données de l'utilisateur (paramètre)
    query = query
    #La suite du code...

Étape 6: Connexion à VectorDB et recherche sémantique

#code continue....     from langchain.vectorstores import Chroma
 persist_directory = "myData"## À présent, il est possible de charger la base de données persistante à partir du disque et de l'utiliser comme d'habitude. 
 vectordb = Chroma(persist_directory=persist_directory, embedding_function=embedding)
 return vectordb
 }

Étape 7: Récupération des réponses pertinentes basées sur les requêtes de l'utilisateur et leur envoi au LLM(ChatGPT)

La mémoire de conversation est la façon dont un chatbot peut répondre à de multiples requêtes à la manière d'un chat. Elle assure une conversation cohérente et, sans elle, chaque requête serait traitée comme une entrée totalement indépendante, sans tenir compte des interactions passées.

LLM avec et sans une mémoire de conversation. Les cases bleues représentent les assistances de l'utilisateur et les cases grises représentent les réponses des LLM. Sans mémoire de conversation (à droite), les LLM ne peuvent pas répondre en utilisant la connaissance des interactions précédentes.

La mémoire permet le  Large Language Model (LLM) de se souvenir des interactions précédentes avec l'utilisateur. Par défaut, les LLMs sont  sans état — ce qui signifie que chaque requête entrante est traitée indépendamment des autres interactions. La seule chose qui existe pour un agent sans état est la saisie actuelle, rien d'autre.


Le modèle ConversationalRetrievalChain est un modèle d'IA conversationnelle conçu pour extraire des réponses pertinentes sur la base des requêtes de l'utilisateur. Il fait partie de la technologie de l'équipe de Langchain. Le modèle utilise une approche basée sur la récupération, où il recherche des réponses préexistantes dans une base de données pour trouver la réponse la plus appropriée à une requête donnée. Le modèle est entraîné sur un grand ensemble de conversations pour apprendre les schémas et le contexte afin de fournir des réponses précises et utiles.

ClassMethod retriveResponse(vectordb) [ Language = python ]
{
from langchain.llms import OpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
#La mémoire de conversation est la façon dont un chatbot peut répondre à de multiples requêtes.
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
#Le ConversationalRetrievalChain est un modèle d'IA conversationnelle conçu pour extraire des réponses pertinentes sur la base des requêtes de l'utilisateur.
qa = ConversationalRetrievalChain.from_llm(OpenAI(temperature=0), vectordb.as_retriever(), memory=memory)
return qa
}


Étape 8: Obtention d'une réponse de la part de LLM et renvoi de celle-ci à l'utilisateur 

ClassMethod getAnswer(qa) [ Language = python ]
{
#Obtention d'une réponse de la part de LLM et renvoi de celle-ci à l'utilisateur
getAnswer = qa.run(query)
return getAnswer
}

Pour plus de détails et de fonctionnalités, veuillez consulter mon application irisChatGPT 

Vidéo connexe

<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/h9JoqbUKFBk" title="YouTube video player" width="560"></iframe>

Je vous remercie

1
0 313
Article Pierre LaFay · Avr 19, 2024 1m read

InterSystems FAQ rubric

Lorsque vous exécutez une routine dans le terminal et qu'une erreur se produit dans le programme, si vous n'avez pas défini correctement le piège à erreurs, le programme entrera en mode de débogage comme indiqué ci-dessous.

USER>do^error1
 write A
^
a+2^error1 *A
USER 2d0>

A partir de cet état, entrez la commandeQuit pour revenir à l'état précédant le démarrage de la routine.

USER 2d0>Quit

Si une transaction est en cours de traitement dans la routine où l'erreur s'est produite, une invite similaire à celle ci-dessous s'affiche.

0
0 47
Article Iryna Mykhailova · Avr 6, 2024 2m read

Rubrique FAQ InterSystems

La page de gestion de Web/CSP Gateway est généralement configurée de manière à ne pas être accessible à partir des machines clientes.

Pour y accéder depuis n'importe quel client, procédez comme suit :

0
0 94