Dans Section 35.3, « Exécuter des Commandes SQL » vous avez vu comment exécuter des ordres SQL dans un programme SQL embarqué. Certains de ces ordres n'ont utilisé que des valeurs constantes et ne fournissaient pas de moyen pour insérer des valeurs fournies par l'utilisateur dans des ordres ou pour permettre au programme de traiter les valeurs retournées par la requête. Ces types d'ordres ne sont pas très utiles dans des applications réelles. Cette section explique en détail comment faire passer des données entre votre programme en C et les ordres SQL embarqués en utilisant un simple mécanisme appelé variables hôtes. Dans un programme SQL embarqué nous considérons que les ordres SQL sont des invités dans le code du programme C qui est le langage hôte. Par conséquent, les variables du programme C sont appelées variables hôtes.
Une autre façon d'échanger des valeurs entre les serveurs PostgreSQL et les applications ECPG est l'utilisation de descripteurs SQL, décrits dans Section 35.7, « Utiliser les Zones de Descripteur ».
Passer des données entre le programme en C et les ordres SQL est particulièrement simple en SQL embarqué. Plutôt que d'avoir un programme qui conne des données dans un ordre SQL, ce qui entraîne des complications variées, comme protéger correctement la valeur, vous pouvez simplement écrire le nom d'une variable C dans un ordre SQL, préfixée par un deux-points. Par exemple:
EXEC SQL INSERT INTO unetable VALUES (:v1, 'foo', :v2);
Cet ordre fait référence à deux variables C appelées v1 et v2 et utilise aussi une chaîne SQL classique, pour montrer que vous n'êtes pas obligé de vous cantonner à un type de données ou à l'autre.
Cette façon d'insérer des variables C dans des ordres SQL fonctionne partout où une expression de valeur est attendue dans un ordre SQL.
Pour passer des données du programme à la base, par exemple comme paramètres d'une requête, ou pour passer des données de la base vers le programme, les variables C qui sont prévues pour contenir ces données doivent être déclarées dans des sections spécialement identifiées, afin que le préprocesseur SQL embarqué puisse s'en rendre compte.
Cette section commence par:
EXEC SQL BEGIN DECLARE SECTION;
et se termine par:
EXEC SQL END DECLARE SECTION;
Entre ces lignes, il doit y avoir des déclarations de variables C normales, comme:
int x = 4; char foo[16], bar[16];
Comme vous pouvez le voir, vous pouvez optionnellement assigner une valeur initiale à une variable. La portée de la variable est déterminée par l'endroit où se trouve la section de déclaration dans le programme. Vous pouvez aussi déclarer des variables avec la syntaxe suivante, qui crée une section declare implicite:
EXEC SQL int i = 4;
Vous pouvez avoir autant de sections de déclaration que vous voulez dans un programme.
Ces déclarations sont aussi envoyées dans le fichier produit comme des variables C normales, il n'est donc pas nécessaire de les déclarer une seconde fois. Les variables qui n'ont pas besoin d'être utilisées dans des commandes SQL peuvent être déclarées normalement à l'extérieur de ces sections spéciales.
La définition d'une structure ou d'un union doit aussi être présente dans une section DECLARE. Sinon, le préprocesseur ne peut pas traiter ces types, puisuq'il n'en connait pas la définition.
Maintenant, vous devriez être capable de passer des données générées par votre programme dans une commande SQL. Mais comment récupérer les résultats d'une requête? À cet effet, le SQL embarqué fournit certaines variantes spéciales de commandes SELECT et FETCH habituelles. Ces commandes ont une clause spéciale INTO qui spécifie dans quelles variables hôtes les valeurs récupérées doivent être stockées. SELECT est utilisé pour une requête qui ne retourne qu'un seul enregistrement, et FETCH est utilisé pour une requête qui retourne plusieurs enregistrement, en utilisant un curseur.
Voici un exemple:
/* * Avec cette table: * CREATE TABLE test1 (a int, b varchar(50)); */ EXEC SQL BEGIN DECLARE SECTION; int v1; VARCHAR v2; EXEC SQL END DECLARE SECTION; ... EXEC SQL SELECT a, b INTO :v1, :v2 FROM test;
La clause INTO apparaît entre la liste de sélection et la clause FROM. Le nombre d'éléments dans la liste SELECT et dans la liste après INTO (aussi appelée la liste cible) doivent être égaux.
Voici un exemple utilisant la commande FETCH:
EXEC SQL BEGIN DECLARE SECTION; int v1; VARCHAR v2; EXEC SQL END DECLARE SECTION; ... EXEC SQL DECLARE truc CURSOR FOR SELECT a, b FROM test; ... do { ... EXEC SQL FETCH NEXT FROM truc INTO :v1, :v2; ... } while (...);
Ici, la clause INTO apparaît après toutes les clauses normales.
Quand les applications ECPG échangent des valeurs entre le serveur PostgreSQL et l'application C, comme quand elles récupèrent des résultats de requête venant du serveur, ou qu'elles exécutent des ordres SQL avec des paramètres d'entrée, les valeurs doivent être converties entre les types de données PostgreSQL et les types du language hôte (ceux du langage C). Une des fonctionnalités les plus importantes d'ECPG est qu'il s'occupe de cela automatiquement dans la plupart des cas.
De ce point de vue, il y a deux sortes de types de données: des types de données PostgreSQL simples, comme des integer et text, qui peuvent être lus et écrits directement par l'application. Les autres types PostgreSQL, comme timestamp ou numeric ne peuvent être accédés qu'à travers des fonctions spéciales de librairie; voyez Section 35.4.4.2, « Accéder à des Types de Données Spéciaux ».
Tableau 35.1, « Correspondance Entre les Types PostgreSQL et les Types de Variables C » montre quels types de données de PostgreSQL correspondent à quels types C. Quand vous voulez envoyer ou recevoir une valeur d'un type PostgreSQL donné, vous devriez déclarer une variable C du type C correspondant dans la section declare.
Tableau 35.1. Correspondance Entre les Types PostgreSQL et les Types de Variables C
type de données PostgreSQL | type de variable hôte |
---|---|
smallint | short |
integer | int |
bigint | long long int |
decimal | decimal[a] |
numeric | numeric[b] |
real | float |
double precision | double |
smallserial | short |
serial | int |
bigserial | long long int |
oid | unsigned int |
character(n), varchar(n), text | char[n+1], VARCHAR[n+1][c] |
name | char[NAMEDATALEN] |
timestamp | timestamp[d] |
interval | interval[e] |
date | date[f] |
boolean | bool[g] |
bytea | char * |
[a] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 35.4.4.2, « Accéder à des Types de Données Spéciaux ». [b] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 35.4.4.2, « Accéder à des Types de Données Spéciaux ». [c] déclaré dans ecpglib.h [d] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 35.4.4.2, « Accéder à des Types de Données Spéciaux ». [e] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 35.4.4.2, « Accéder à des Types de Données Spéciaux ». [f] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 35.4.4.2, « Accéder à des Types de Données Spéciaux ». [g] déclaré dans ecpglib.h si non natif |
Pour manipuler des types chaînes de caractères SQL, comme varchar et text, il y a deux façons de déclarer les variables hôtes.
Une façon est d'utiliser char[], un tableau de char, qui est la façon la plus habituelle de gérer des données texte en C.
EXEC SQL BEGIN DECLARE SECTION; char str[50]; EXEC SQL END DECLARE SECTION;
Notez que vous devez gérer la longueur vous-même. Si vous utilisez cette variable he comme variable cible d'une requête qui retourne une chaîne de plus de 49 caractères, un débordement de tampon se produira. occurs.
L'autre façon est d'utiliser le type VARCHAR, qui est un type spécial fourni par ECPG. La définition d'un tableau de type VARCHAR est convertie dans un struct nommé pour chaque variable. Une déclaration comme:
VARCHAR var[180];
est convertie en:
struct varchar_var { int len; char arr[180]; } var;
Le membre arr contient la chaîne terminée par un octet à zéro. Par conséquent, la variable hôte doit être déclarée avec la longueur incluant le terminateur de chaîne. Le membre len stocke la longueur de la chaîne stockée dans arr sans l'octet zéro final. Quand une variable hôte est utilisé comme entrée pour une requête, si strlen et len sont différents, le plus petit est utilisé.
VARCHAR peut être écrit en majuscule ou en minuscule, mais pas dans un mélange des deux.
Les variables hôtes char et VARCHAR peuvent aussi contenir des valeurs d'autres types SQL, qui seront stockés dans leur forme chaîne.
ECPG contient des types spéciaux qui vous aident intéragir facilement avec des types de données spéciaux du serveur PostgreSQL. En particulier, sont supportés les types numeric, decimal, date, timestamp, et interval. Ces types de données ne peuvent pas être mis de façon utile en correspondance avec des types primitifs du langage hôtes (tels que int, long long int, ou char[]), parce qu'ils ont une structure interne complexe. Les applications manipulent ces types en déclarant des variables hôtes dans des types spéciaux et en y accédant avec des fonctions de la librairie pgtypes. La librairie pgtypes, décrite en détail dans Section 35.6, « Librairie pgtypes » contient des fonctions de base pour traiter ces types, afin que vous n'ayez pas besoin d'envoyer une requête au serveur SQL juste pour additionner un interval à un timestamp par exemple.
Les sous-sections suivantes décrivent ces types de données spéciaux. Pour plus de détails à propos des fonctions de librairie pgtype, voyez Section 35.6, « Librairie pgtypes ».
Voici une méthode pour manipuler des variables timestamp dans l'application hôte ECPG.
Tout d'abord, le programme doit inclure le fichier d'en-tête pour le type timestamp:
#include <pgtypes_timestamp.h>
Puis, déclarez une variable hôte comme type timestamp dans la section declare:
EXEC SQL BEGIN DECLARE SECTION; timestamp ts; EXEC SQL END DECLARE SECTION;
Et après avoir lu une valeur dans la variable hôte, traitez la en utilisant les fonctions de la librairie pgtypes. Dans l'exemple qui suit, la valeur timestamp est convertie sous forme texte (ASCII) avec la fonction PGTYPEStimestamp_to_asc():
EXEC SQL SELECT now()::timestamp INTO :ts; printf("ts = %s\n", PGTYPEStimestamp_to_asc(ts));
Cet exemple affichere des résultats de ce type:
ts = 2010-06-27 18:03:56.949343
Par ailleurs, le type DATE peut être manipulé de la même façon. Le programme doit inclure pgtypes_date.h, déclarer une variable hôte comme étant du type date et convertir une valeur DATE dans sa forme texte en utilisant la fonction PGTYPESdate_to_asc(). Pour plus de détails sur les fonctions de la librairie pgtypes, voyez Section 35.6, « Librairie pgtypes ».
La manipulation du type interval est aussi similaire aux types timestamp et date. Il est nécessaire, par contre, d'allouer de la mémoire pour une valeur de type interval de façon explicite. Ou dit autrement, l'espace mémoire pour la variable doit être allouée du tas, et non de la pile.
Voici un programme de démonstration:
#include <stdio.h> #include <stdlib.h> #include <pgtypes_interval.h> int main(void) { EXEC SQL BEGIN DECLARE SECTION; interval *in; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT TO testdb; in = PGTYPESinterval_new(); EXEC SQL SELECT '1 min'::interval INTO :in; printf("interval = %s\n", PGTYPESinterval_to_asc(in)); PGTYPESinterval_free(in); EXEC SQL COMMIT; EXEC SQL DISCONNECT ALL; return 0; }
La manipulation des types numeric et decimal est similaire au type interval: elle requiert de définir d'un pointeur, d'allouer de la mémoire sur le tas, et d'accéder la variable au mouyen des fonctions de librairie pgtypes. Pour plus de détails sur les fonctions de la librairie pgtypes, voyez Section 35.6, « Librairie pgtypes ».
Aucune fonction n'est fournie spécifiquement pour le type decimal. Une application doit le convertir vers une variable numeric en utilisant une fonction de la librairie pgtypes pour pouvoir le traiter.
Voici un programme montrant la manipulation des variables de type numeric et decimal.
#include <stdio.h> #include <stdlib.h> #include <pgtypes_numeric.h> EXEC SQL WHENEVER SQLERROR STOP; int main(void) { EXEC SQL BEGIN DECLARE SECTION; numeric *num; numeric *num2; decimal *dec; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT TO testdb; num = PGTYPESnumeric_new(); dec = PGTYPESdecimal_new(); EXEC SQL SELECT 12.345::numeric(4,2), 23.456::decimal(4,2) INTO :num, :dec; printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 0)); printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 1)); printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 2)); /* Convertir le decimal en numeric pour montrer une valeur décimale. */ num2 = PGTYPESnumeric_new(); PGTYPESnumeric_from_decimal(dec, num2); printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 0)); printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 1)); printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 2)); PGTYPESnumeric_free(num2); PGTYPESdecimal_free(dec); PGTYPESnumeric_free(num); EXEC SQL COMMIT; EXEC SQL DISCONNECT ALL; return 0; }
Vous pouvez aussi utiliser des tableaux, typedefs, structs et pointeurs comme variables hôtes.
Il y a deux cas d'utilisations pour des tableaux comme variables hôtes. Le premier est une façon de stocker des chaînes de texte dans des char[] ou VARCHAR[], comme expliqué Section 35.4.4.1, « Manipuler des Chaînes de Caractères ». Le second cas d'utilisation est de récupérer plusieurs enregistrements d'une requête sans utiliser de curseur. Sans un tableau, pour traiter le résultat d'une requête de plusieurs lignes, il est nécessaire d'utiliser un curseur et la commande FETCH. Mais avec une variable hôte de type variable, plusieurs enregistrements peuvent être récupérés en une seule fois. La longueur du tableau doit être définie pour pouvoir recevoir tous les enregistrements d'un coup, sans quoi un buffer overflow se produira probablement.
Les exemples suivants parcourent la table système pg_database et montrent tous les OIDs et noms des bases de données disponibles:
int main(void) { EXEC SQL BEGIN DECLARE SECTION; int dbid[8]; char dbname[8][16]; int i; EXEC SQL END DECLARE SECTION; memset(dbname, 0, sizeof(char)* 16 * 8); memset(dbid, 0, sizeof(int) * 8); EXEC SQL CONNECT TO testdb; /* Récupérer plusieurs enregistrements dans des tableaux d'un coup. */ EXEC SQL SELECT oid,datname INTO :dbid, :dbname FROM pg_database; for (i = 0; i < 8; i++) printf("oid=%d, dbname=%s\n", dbid[i], dbname[i]); EXEC SQL COMMIT; EXEC SQL DISCONNECT ALL; return 0; }
Cet exemple affiche le résultat suivant. (Les valeurs exactes dépendent de votre environnement.)
oid=1, dbname=template1 oid=11510, dbname=template0 oid=11511, dbname=postgres oid=313780, dbname=testdb oid=0, dbname= oid=0, dbname= oid=0, dbname=
Une structure dont les noms des membres correspondent aux noms de colonnes du résultat d'une requête peut être utilisée pour récupérer plusieurs colonnes d'un coup. La structure permet de gérer plusieurs valeurs de colonnes dans une seule variable hôte.
L'exemple suivant récupère les OIDs, noms, et tailles des bases de données disponibles à partir de la table système pg_database, et en utilisant la fonction pg_database_size(). Dans cet exemple, une variable structure dbinfo_t avec des membres dont les noms correspondent à chaque colonnes du résultat du SELECT est utilisée pour récupérer une ligne de résultat sans avoir besoin de mettre plusieurs variables hôtes dans l'ordre FETCH.
EXEC SQL BEGIN DECLARE SECTION; typedef struct { int oid; char datname[65]; long long int size; } dbinfo_t; dbinfo_t dbval; EXEC SQL END DECLARE SECTION; memset(&dbval, 0, sizeof(dbinfo_t)); EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database; EXEC SQL OPEN cur1; /* quand la fin du jeu de données est atteint, sortir de la boucle while */ EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* Récupérer plusieurs colonnes dans une structure. */ EXEC SQL FETCH FROM cur1 INTO :dbval; /* Afficher les membres de la structure. */ printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, dbval.size); } EXEC SQL CLOSE cur1;
Cet exemple montre le résultat suivant. (Les valeurs exactes dépendent du contexte.)
oid=1, datname=template1, size=4324580 oid=11510, datname=template0, size=4243460 oid=11511, datname=postgres, size=4324580 oid=313780, datname=testdb, size=8183012
Les variables hôtes structures « absorbent » autant de colonnes que la structure a de champs. Des colonnes additionnelles peuvent être assignées à d'autres variables hôtes. Par exemple, le programme ci-dessus pourrait être restructuré comme ceci, avec la variable size hors de la structure:
EXEC SQL BEGIN DECLARE SECTION; typedef struct { int oid; char datname[65]; } dbinfo_t; dbinfo_t dbval; long long int size; EXEC SQL END DECLARE SECTION; memset(&dbval, 0, sizeof(dbinfo_t)); EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database; EXEC SQL OPEN cur1; /* quand la fin du jeu de données est atteint, sortir de la boucle while */ EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* Récupérer plusieurs colonnes dans une structure. */ EXEC SQL FETCH FROM cur1 INTO :dbval, :size; /* Afficher les membres de la structure. */ printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size); } EXEC SQL CLOSE cur1;
Utilisez le mot clé typedef pour faire correspondre de nouveaux types aux types existants.
EXEC SQL BEGIN DECLARE SECTION; typedef char mychartype[40]; typedef long serial_t; EXEC SQL END DECLARE SECTION;
Notez que vous pourriez aussi utiliser:
EXEC SQL TYPE serial_t IS long;
Cette déclaration n'a pas besoin de faire partie d'une section declare.
Vous pouvez déclarer des pointeurs vers les types les plus communs. Notez toutefois que vous ne pouvez pas utiliser des pointeurs comme variables cibles de requêtes sans auto-allocation. Voyez Section 35.7, « Utiliser les Zones de Descripteur » pour plus d'information sur l'auto-allocation.
EXEC SQL BEGIN DECLARE SECTION; int *intp; char **charp; EXEC SQL END DECLARE SECTION;
Cette section contient des informations sur comment manipuler des types non-scalaires et des types de données définies au niveau SQL par l'utilisateur dans des applications ECPG. Notez que c'est distinct de la manipulation des variables hôtes des types non-primitifs, décrits dans la section précédente.
Les tableaux SQL multi-dimensionnels ne sont pas directement supportés dans ECPG. Les tableaux SQL à une dimension peuvent être placés dans des variables hôtes de type tableau C et vice-versa. Néanmoins, lors de la création d'une instruction, ecpg ne connaît pas le type des colonnes, donc il ne peut pas vérifier si un tableau C est à placer dans un tableau SQL correspondant. Lors du traitement de la sortie d'une requête SQL, ecpg a suffisamment d'informations et, de ce fait, vérifie si les deux sont des tableaux.
Si une requête accède aux éléments d'un tableau séparément, cela évite l'utilisation des tableaux dans ECPG. Dans ce cas, une variable hôte avec un type qui peut être mis en correspondance avec le type de l'élément devrait être utilisé. Par exemple, si le type d'une colonne est un tableau d'integer, une variable hôte de type int peut être utilisée. Par ailleurs, si le type de l'élément est varchar, ou text, une variable hôte de type char[] ou VARCHAR[] peut être utilisée.
Voici un exemple. Prenez la table suivante:
CREATE TABLE t3 ( ii integer[] ); testdb=> SELECT * FROM t3; ii ------------- {1,2,3,4,5} (1 row)
Le programme de démonstration suivant récupère le 4ème élément du tableau et le stocke dans une variable hôte de type int: type int:
EXEC SQL BEGIN DECLARE SECTION; int ii; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[4] FROM t3; EXEC SQL OPEN cur1; EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { EXEC SQL FETCH FROM cur1 INTO :ii ; printf("ii=%d\n", ii); } EXEC SQL CLOSE cur1;
Cet exemple affiche le résultat suivant:
ii=4
Pour mettre en correspondance de multiples éléments de tableaux avec les multiples éléments d'une variable hôte tableau, chaque élément du tableau doit être géré séparément, par exemple; for example:
EXEC SQL BEGIN DECLARE SECTION; int ii_a[8]; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[1], ii[2], ii[3], ii[4] FROM t3; EXEC SQL OPEN cur1; EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { EXEC SQL FETCH FROM cur1 INTO :ii_a[0], :ii_a[1], :ii_a[2], :ii_a[3]; ... }
Notez à nouveau que
EXEC SQL BEGIN DECLARE SECTION; int ii_a[8]; EXEC SQL END DECLARE SECTION; EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii FROM t3; EXEC SQL OPEN cur1; EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* FAUX */ EXEC SQL FETCH FROM cur1 INTO :ii_a; ... }
ne fonctionnerait pas correctement dans ce cas, parce que vous ne pouvez pas mettre en correspondance une colonne de type tableau et une variable hôte de type tableau directement.
Un autre contournement possible est de stocker les tableaux dans leur forme de représentation texte dans des variables hôtes de type char[] ou VARCHAR[]. Pour plus de détails sur cette représentation, voyez Section 8.15.2, « Saisie de valeurs de type tableau ». Notez que cela implique que le tableau ne peut pas être accédé naturellement comme un tableau dans le programme hôte (sans traitement supplémentaire qui transforme la représentation texte).
Les types composite ne sont pas directement supportés dans ECPG, mais un contournement simple est possible. Les contournements disponibles sont similaires à ceux décrits pour les tableaux ci-dessus: soit accéder à chaque attribut séparément, ou utiliser la représentation externe en mode chaîne de caractères.
Pour les exemples suivants, soient les types et table suivants:
CREATE TYPE comp_t AS (intval integer, textval varchar(32)); CREATE TABLE t4 (compval comp_t); INSERT INTO t4 VALUES ( (256, 'PostgreSQL') );
La solution la plus évidente est d'accéder à chaque attribut séparément. Le programme suivant récupère les données de la table exemple en sélectionnant chaque attribut du type comp_t séparément:
EXEC SQL BEGIN DECLARE SECTION; int intval; varchar textval[33]; EXEC SQL END DECLARE SECTION; /* Mettre chaque élément de la colonne de type composite dans la liste SELECT. */ EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4; EXEC SQL OPEN cur1; EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* Récupérer chaque élément du type de colonne composite dans des variables hôtes. */ EXEC SQL FETCH FROM cur1 INTO :intval, :textval; printf("intval=%d, textval=%s\n", intval, textval.arr); } EXEC SQL CLOSE cur1;
Pour améliorer cet exemple, les variables hôtes qui vont stocker les valeurs dans la commande FETCH peuvent être rassemblées sous forme de structure, voyez Section 35.4.4.3.2, « Structures ». Pour passer à la structure, l'exemple peut-être modifié comme ci dessous. Les deux variables hôtes, intval et textval, deviennent membres de comp_t, et la structure est spécifiée dans la commande FETCH.
EXEC SQL BEGIN DECLARE SECTION; typedef struct { int intval; varchar textval[33]; } comp_t; comp_t compval; EXEC SQL END DECLARE SECTION; /* Mettre chaque élément de la colonne de type composite dans la liste SELECT. */ EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4; EXEC SQL OPEN cur1; EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* Mettre toutes les valeurs de la liste SELECT dans une structure. */ EXEC SQL FETCH FROM cur1 INTO :compval; printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr); } EXEC SQL CLOSE cur1;
Bien qu'une structure soit utilisée dans la commande FETCH, les noms d'attributs dans la clause SELECT sont spécifiés un par un. Cela peut être amélioré en utilisant un * pour demander tous les attributs de la valeur de type composite.
... EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).* FROM t4; EXEC SQL OPEN cur1; EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { /* Mettre toutes les valeurs de la liste SELECT dans une structure. */ EXEC SQL FETCH FROM cur1 INTO :compval; printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr); } ...
De cette façon, les types composites peuvent être mis en correspondance avec des structures de façon quasi transparentes, alors qu'ECPG ne comprend pas lui-même le type composite.
Et pour finir, il est aussi possible de stocker les valeurs de type composite dans leur représentation externe de type chaîne dans des variables hôtes de type char[] ou VARCHAR[]. Mais de cette façon, il n'est pas facilement possible d'accéder aux champs de la valeur dans le programme hôte.
Les nouveaux types de base définis par l'utilisateur ne sont pas directement supportés par ECPG. Vous pouvez utiliser les représentations externes de type chaîne et les variables hôtes de type char[] ou VARCHAR[], et cette solution est en fait appropriée et suffisante pour de nombreux types.
Voici un exemple utilisant le type de données complex de l'exemple tiré de Section 37.11, « Types utilisateur ». La représentation externe sous forme de chaîne de ce type est (%lf,%lf), qui est définie dans les fonctions complex_in() et complex_out(). L'exemple suivant insère les valeurs de type complexe (1,1) et (3,3) dans les colonnes a et b, et les sélectionne à partir de la table après cela.
EXEC SQL BEGIN DECLARE SECTION; varchar a[64]; varchar b[64]; EXEC SQL END DECLARE SECTION; EXEC SQL INSERT INTO test_complex VALUES ('(1,1)', '(3,3)'); EXEC SQL DECLARE cur1 CURSOR FOR SELECT a, b FROM test_complex; EXEC SQL OPEN cur1; EXEC SQL WHENEVER NOT FOUND DO BREAK; while (1) { EXEC SQL FETCH FROM cur1 INTO :a, :b; printf("a=%s, b=%s\n", a.arr, b.arr); } EXEC SQL CLOSE cur1;
Cet exemple affiche le résultat suivant:
a=(1,1), b=(3,3)
Un autre contournement est d'éviter l'utilisation directe des types définis par l'utilisateur dans ECPG et à la place créer une fonction ou un cast qui convertit entre le type défini par l'utilisateur et un type primitif que ECPG peut traiter. Notez, toutefois, que les conversions de types, particulièrement les implicites, ne devraient être introduits dans le système de typage qu'avec la plus grande prudence.
Par exemple,
CREATE FUNCTION create_complex(r double, i double) RETURNS complex LANGUAGE SQL IMMUTABLE AS $$ SELECT $1 * complex '(1,0')' + $2 * complex '(0,1)' $$;
Après cette définition, ce qui suit
EXEC SQL BEGIN DECLARE SECTION; double a, b, c, d; EXEC SQL END DECLARE SECTION; a = 1; b = 2; c = 3; d = 4; EXEC SQL INSERT INTO test_complex VALUES (create_complex(:a, :b), create_complex(:c, :d));
a le même effet que
EXEC SQL INSERT INTO test_complex VALUES ('(1,2)', '(3,4)');
Les exemples précédents ne gèrent pas les valeurs nulles. En fait, les exemples de récupération de données remonteront une erreur si ils récupèrent une valeur nulle de la base. Pour être capable de passer des valeurs nulles à la base ou d'un récupérer, vous devez rajouter une seconde spécification de variable hôte à chaque variable hôte contenant des données. Cette seconde variable est appelée l'indicateur et contient un drapeau qui indique si le datum est null, dans quel cas la valeur de la vraie variable hôte est ignorée. Voici un exemple qui gère la récupération de valeurs nulles correctement:
EXEC SQL BEGIN DECLARE SECTION; VARCHAR val; int val_ind; EXEC SQL END DECLARE SECTION: ... EXEC SQL SELECT b INTO :val :val_ind FROM test1;
La variable indicateur val_ind sera zéro si la valeur n'était pas nulle, et sera négative si la valeur était nulle.
L'indicateur a une autre fonction: si la valeur de l'indicateur est positive, cela signifie que la valeur n'est pas nulle, mais qu'elle a été tronquée quand elle a été stockée dans la variable hôte.
Si l'argument -r no_indicator est passée au préprocesseur ecpg, il fonction dans le mode « no-indicator ». En mode no-indicator, si aucune variable indicator n'est spécifiée, les valeurs nulles sont signalées (en entrée et en sortie) pour les types chaînes de caractère comme des chaînes vides et pour les types integer comme la plus petite valeur possible pour le type (par exempple, INT_MIN pour int).