Article
· Mars 31, 2023 8m de lecture

Requête en tant que %Query ou requête basée sur ObjectScript

Dans ce didacticiel, j'aimerais parler des requêtes de classe (Class Query). Pour être plus précis, à propos des requêtes basées sur du code écrit par l'utilisateur :

Beaucoup de gens ignorent ce type de requête simplement parce qu'ils ne sont pas très à l'aise avec l'écriture de beaucoup de code ObjectScript pour les méthodes ou qu'ils ne voient pas comment ils peuvent l'utiliser dans leurs applications relationnelles. Mais pour être honnête, pour moi, c'est l'une des inventions les plus cool pour le modèle relationnel d'IRIS ! Il vous permet d'exposer toutes les informations que vous souhaitez (non limitées aux tables de votre base de données) en tant que resultset relationnel pour un client. Ainsi, en gros, vous pouvez encapsuler toutes les données stockées dans votre base de données et au-delà dans une "table virtuelle ordonnée" que votre utilisateur peut interroger. Et du point de vue d'un utilisateur, ce sera le même resultset que s'il avait interrogé une table ou une view.

Pour ne pas être sans fondement, voici quelques exemples de ce que vous pouvez utiliser comme données pour votre requête :

  • Indicateurs système d'IRIS et de votre système d'exploitation local (si IRIS y a accès)
  • Résultats de requêtes externes, comme REST ou SOAP (par exemple, si vous ne souhaitez pas envoyer de requêtes directement depuis un client pour des raisons de sécurité ou pour toute autre raison)
  • Résultats de l'Embedded Cursor ou de Simple Statement
  • Toutes les données ad hoc que vous pouvez composer vous-même
  • À peu près tout ce qui peut venir à l'esprit et qu'il est possible d'obtenir en utilisant ObjectScript

Voyons maintenant comment c'est fait.

Si vous utilisez Studio, c'est très agréable et simple.

Dans votre classe *.cls vous pouvez aller dans le menu Class – Add – Query :

Ou dans la barre d'outils "Membres", cliquez sur l'icône Query :

Et l'assistant de nouvelle requête s'ouvrira. Il vous guidera à travers les étapes de création des méthodes nécessaires pour que votre requête fonctionne.

Examinons ce processus en utilisant l'exemple le plus simple d'encapsulation du résultat de l'Embedded Cursor. Ici, nous allons en fait écrire une requête dans une table, mais c'est juste pour faire simple, nous examinerons plus loin des exemples d'utilisation de %Query pour renvoyer des données à partir d'autres sources.

Disons que nous avons une classe Sample.Human qui extends %Persistent et %Populate. Je vais utiliser ce dernier pour remplir les données parce que je suis si paresseux et parce que je peux :

Class Sample.Human Extends (%Persistent, %Populate)
{

Property Name As %Name;
Property DoB As %Date(MAXVAL = 61727, MINVAL = 32507);
Property Age As %Integer [ Calculated, SqlComputeCode = {set {Age} = $h - {DoB} \ 365.25}, SqlComputed ];
}

Lorsque nous commençons à créer la requête, la première étape de l'assistant consiste à définir un nom et un type :

L'étape suivante consiste à définir les paramètres d'entrée s'il y en a. Pour ajouter un nouveau paramètre, cliquez sur le bouton le plus haut dans la colonne de boutons de droite :

La prochaine étape est une partie très importante - vous devez définir des noms et des types ou des colonnes dans un resultset résultant. Lorsque vous créez une requête basée sur SQL, cette étape est effectuée automatiquement par le système - il regarde simplement les colonnes que vous avez ajoutées dans la requête et prend leurs noms et leurs types de données. Parce que dans la requête basée sur ObjectScript, il se peut qu'il n'y ait aucun champ, vous devez dire vous-même au système à quoi s'attendre.

Ça y est. À la suite de cet assistant, vous obtiendrez 3 nouvelles méthodes de classe et une requête dans votre classe :

/// Get all the names and ages of people whose age is greater or equal than nput parameter
Query GetAllOlderThan(Age As %Integer = 65) As %Query(ROWSPEC = "Name:%Name,Age:%Integer")
{
}

ClassMethod GetAllOlderThanExecute(ByRef qHandle As %Binary, Age As %Integer = 65) As %Status
{
               Quit $$$OK
}

ClassMethod GetAllOlderThanClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
               Quit $$$OK
}

ClassMethod GetAllOlderThanFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
               Quit $$$OK
}

}

Query indique simplement au système qu'il existe une requête dans une classe qui peut être appelée par un nom avec un paramètre et qu'elle renverra 2 colonnes dans le résultat. Tout code que vous entrez dans la requête sera ignoré.

Tout le codage est fait dans les ClassMethods. Ils sont chargés de remplir notre jeu de résultats "virtuel" (<QueryName>Execute - appelé lorsqu'une instruction SQL est exécutée), de renvoyer la ligne suivante (<QueryName>Fetch) et de nettoyer après lui-même (<QueryName>Close). Ils ont un paramètre en commun - ByRef qHandle As %Binary dans lequel les données sont stockées et transmises entre les méthodes. Comme vous pouvez le voir, ce sont des données binaires, vous pouvez donc y mettre n'importe quoi (le bon sens est votre limite). Dans  <QueryName>Execute, il existe également un paramètre d'entrée de la requête : dans ce cas, il s'agit de Age As %Integer = 65. Et dans <QueryName>Fetch, il existe deux paramètres supplémentaires :

  • ByRef Row As %List : il s'agit d'une %List qui contient la ligne actuelle de l'ensemble de résultats "virtuel" avec le nombre et les valeurs des colonnes décrites dans le ROWSPEC de la requête (Name : %Name, Age : %Integer).
  • ByRef AtEnd As %Integer = 0 – il s'agit d'un indicateur qui signale que la ligne actuelle est la dernière ligne (1) ou non (0).

Si vous travaillez dans VSCode, vous devrez écrire vous-même toutes les signatures et essayer de ne pas faire d'erreur 😊

Maintenant que nous savons quel paramètre est responsable de quoi, regardons le code :

/// Obtenir tous les noms et âges des personnes dont l'âge est supérieur ou égal au paramètre Input
Query GetAllOlderThan(Age As %Integer = 65) As %Query(ROWSPEC = "Name:%Name,Age:%Integer") [ SqlProc ]
{
}

ClassMethod GetAllOlderThanExecute(ByRef qHandle As %Binary, Age As %Integer = 65) As %Status
{
     &sql(DECLARE C CURSOR FOR
        SELECT Name, Age
          FROM Sample.Human 
         WHERE Age >= :Age
     )
     &sql(OPEN C)
    Quit $$$OK
}

ClassMethod GetAllOlderThanClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
    &sql(CLOSE C)
    Quit $$$OK
}

ClassMethod GetAllOlderThanFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
    &sql(FETCH C INTO :Name, :Age)
    If (SQLCODE'=0) {
        Set AtEnd = 1
        Set Row = ""
        Quit $$$OK
    }
    Set Row = $Lb(Name, Age)
    Quit $$$OK
}

Ce code déclare et ouvre un Embedded Cursor qui sélectionne les noms et les âges des personnes de plus de Age dans <QueryName>Execute, puis récupère le résultat du curseur et forme une liste dans <QueryName>Fetch et la ferme dans <QueryName>Close.

Nous pouvons l'appeler en utilisant le code :

 SET tStatement = ##class(%SQL.Statement).%New()
 SET rstatus = tStatement.%PrepareClassQuery("Sample.Human","GetAllOlderThan")
 SET rset = tStatement.%Execute()
 DO rset.%Display()

Très agréable et soigné. Et probablement pas ce que vous cherchiez.

Comme exemple d'utilisation sans données stockées dans la base de données, disons que pour une raison quelconque, nous n'avons pas accès aux tables réelles, mais nous devons vérifier que notre application fonctionne comme il se doit. Nous avons besoin de la requête pour renvoyer des données de test. Nous pouvons réécrire cet exemple afin que les données soient générées automatiquement lors de la récupération d'une nouvelle ligne.

Évidemment, nous n'avons pas besoin d'enregistrer les données dans la mémoire, il n'est donc pas nécessaire de remplir la variable qHandle dans <QueryName>Execute - nous pouvons créer des données dans <QueryName>Fetch. Et dans qHandle nous stockerons la taille du resultset à renvoyer (un nombre aléatoire inférieur à 200, par exemple) et le numéro de la ligne courante que nous incrémenterons dans <QueryName>Fetch. Au final, nous obtiendrons le code :

Query GetAllOlderThan(Age As %Integer = 65) As %Query(ROWSPEC = "Name:%Name,Age:%Integer") [ SqlProc ]
{
}

ClassMethod GetAllOlderThanExecute(ByRef qHandle As %Binary, Age As %Integer = 65) As %Status
{
    set qHandle = $lb($random(200), 0, Age)
    Quit $$$OK
}

ClassMethod GetAllOlderThanClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
    set qHandle = ""
    Quit $$$OK
}

ClassMethod GetAllOlderThanFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = GetAllOlderThanExecute ]
{
	if $ListGet(qHandle, 2) = $ListGet(qHandle, 1)
	{
		Set Row = ""
		set AtEnd = 1
	} else
	{
    	Set Row = $Lb(##class(%PopulateUtils).Name(), ##class(%PopulateUtils).Integer($ListGet(qHandle, 3), 90))
    	set $list(qHandle, 2) = $list(qHandle, 2) + 1
	}
    Quit $$$OK
}

}

Comme vous pouvez le voir, je génère des données ad hoc. Cela signifie que vous pouvez obtenir vos données de n'importe où, les intégrer dans un ensemble de résultats et les rendre accessibles à partir de votre application qui utilise ODBC/JDBC en dehors de la base de données IRIS. Ce qui signifie en retour que vous pouvez utiliser l'accès relationnel habituel pour obtenir des données non relationnelles si vous parvenez à les structurer.

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer