Dans Section 34.3 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 34.7.
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 34.4.4.2.
Tableau 34.1 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 34.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( , varchar( , text | char[ , VARCHAR[ |
name | char[NAMEDATALEN] |
timestamp | timestamp [c] |
interval | interval [d] |
date | date [e] |
boolean | bool [f] |
bytea | char * , bytea[ |
[a] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 34.4.4.2. [b] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 34.4.4.2. [c] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 34.4.4.2. [d] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 34.4.4.2. [e] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 34.4.4.2. [f] déclaré dans |
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.
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 interagir 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 34.6 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 34.6.
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 34.6.
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; EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT; 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 34.6.
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; EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT; 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; }
La gestion du type bytea
est aussi similaire au type
VARCHAR
. La définition d'un tableau de type
bytea
est convertie en une structure nommée pour chaque
variable. Une déclaration comme :
bytea var[180];
est traduit en :
struct bytea_var { int len; char arr[180]; } var;
Le membre arr
accueille des données au format
binaire. Il peut même gérer '\0'
comme faisant partie
des données, contrairement à VARCHAR
. La donnée est
converti de/vers le format hexadécimal et envoyé/reçu par ecpglib.
Une variable bytea
peut être utilisé uniquement quand
bytea_output est configuré à
hex
.
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 34.4.4.1. 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; EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT; /* 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 ; c'est-à-dire que vous pouvez aussi écrire les typedefs comme des instructions C normales.
Tout mot que vous déclarez comme un typedef
ne peut
pas être utilisé
comme un mot-clé SQL dans les commandes EXEC SQL
plus tard dans le même programme. Par exemple, ceci ne fonctionnera
pas :
EXEC SQL BEGIN DECLARE SECTION; typedef int start; EXEC SQL END DECLARE SECTION; ... EXEC SQL START TRANSACTION;
ECPG renverra une erreur de syntaxe pour START
TRANSACTION
car il ne reconnaît pas
START
comme un mot-clé SQL, seulement comme un
typedef.
(Si vous avez un tel conflit et que renommer le typedef ne semble
pas pratique, vous pourriez écrire la commande SQL en utilisant le
SQL dynamique.)
Pour les versions de PostgreSQL antérieures à la 16, l'utilisation des mots-clés SQL comme noms de typedef allait probablement résulter en des erreurs de syntaxe associées à l'utilisation du typedef, plutôt que d'utiliser le nom d'un mot-clé SQL. Le nouveau comportement devrait moins poser de problèmes quand une application ECPG existante est recompilée pour une nouvelle version de PostgreSQL avec de nouveaux mots-clés.
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 34.7 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
:
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 :
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. 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 tables 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 34.4.4.3.2.
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 36.13. 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.
(Voir Section 34.16 pour activer un comportement
spécifique à Oracle.)
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 exemple, INT_MIN
pour
int
).