Une zone de descripteur SQL (SQL Descriptor Area ou SQLDA)
est une méthode plus sophistiquée pour traiter le
résultat d'un ordre SELECT
, FETCH
ou
DESCRIBE
. Une zone de descripteur SQL regroupe les données
d'un enregistrement avec ses métadonnées dans une seule structure. Ces
métadonnées sont particulièrement utiles quand on exécute des ordres SQL
dynamiques, où_la nature des colonnes résultat ne sont pas forcément
connues à l'avance. PostgreSQL fournit deux façons d'utiliser des Zones
de Descripteur: les Zones de Descripteur SQL nommée et les structures C SQLDA.
Une zone descripteur SQL nommé est composée d'un entête, qui contient des données concernant l'ensemble du descripteur, et une ou plusieurs zones de descriptions d'objets, qui en fait décrivent chaque colonne de l'enregistrement résultat.
Avant que vous puissiez utiliser une zone de descripteur SQL, vous devez en allouer une :
EXEC SQL ALLOCATE DESCRIPTOR identifiant
;
L'identifiant sert de « nom de variable » de la zone de descripteur. Quand vous n'avez plus besoin du descripteur, vous devriez le désallouer:
EXEC SQL DEALLOCATE DESCRIPTOR identifiant
;
Pour utiliser une zone de descripteur, spécifiez le comme cible de
stockage dans une clause INTO
, à la place
d'une liste de variables hôtes :
EXEC SQL FETCH NEXT FROM mycursor INTO SQL DESCRIPTOR mydesc;
Si le jeu de données retourné est vide, la zone de descripteur contiendra tout de même les métadonnées de la requête, c'est à dire les noms des champs.
Pour les requêtes préparées mais pas encore exécutées, l'ordre
DESCRIBE
peut être utilisé pour récupérer les
métadonnées du résultat :
EXEC SQL BEGIN DECLARE SECTION; char *sql_stmt = "SELECT * FROM table1"; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE stmt1 FROM :sql_stmt; EXEC SQL DESCRIBE stmt1 INTO SQL DESCRIPTOR mydesc;
Avant PostgreSQL 9.0, le mot clé SQL
était optionnel,
par conséquent utiliser DESCRIPTOR
et
SQL DESCRIPTOR
produisaent les mêmes zones de
descripteur SQL. C'est maintenant obligatoire, et oublier le mot clé
SQL
produit des zones de descripteurs SQLDA,
voyez Section 36.7.2.
Dans les ordres DESCRIBE
et FETCH
,
les mots-clés INTO
et USING
peuvent
être utilisés de façon similaire : ils produisent le jeu de données
et les métadonnées de la zone de descripteur.
Maintenant, comment récupérer les données de la zone de descripteur ? Vous pouvez voir la zone de descripteur comme une structure avec des champs nommés. Pour récupérer la valeur d'un champ à partir de l'entête et le stocker dans une variable hôte, utilisez la commande suivante :
EXEC SQL GET DESCRIPTORname
:hostvar
=field
;
À l'heure actuelle, il n'y a qu'un seul champ d'entête défini:
COUNT
, qui dit combien il y a de zones
de descripteurs d'objets (c'est à dire, combien de colonnes il y a dans le résultat).
La variable hôte doit être de type integer. Pour récupérer un champ de la
zone de description d'objet, utilisez la commande suivante :
EXEC SQL GET DESCRIPTORname
VALUEnum
:hostvar
=field
;
num
peut être un integer literal, ou une
variable hôte contenant un integer. Les champs possibles sont :
CARDINALITY
(integer) #nombres d'enregistrements dans le résultat
DATA
#objet de donnée proprement dit (par conséquent, le type de données de ce champ dépend de la requête)
DATETIME_INTERVAL_CODE
(integer) #
Quand TYPE
est 9
,
DATETIME_INTERVAL_CODE
aura une valeur de
1
pour DATE
,
2
pour TIME
,
3
pour TIMESTAMP
,
4
pour TIME WITH TIME ZONE
, or
5
pour TIMESTAMP WITH TIME ZONE
.
DATETIME_INTERVAL_PRECISION
(integer) #non implémenté
INDICATOR
(integer) #l'indicateur (indique une valeur null ou une troncature de valeur)
KEY_MEMBER
(integer) #non implémenté
LENGTH
(integer) #longueur de la donnée en caractères
NAME
(string) #nom de la colonne
NULLABLE
(integer) #non implémenté
OCTET_LENGTH
(integer) #longueur de la représentation caractère de la donnée en octets
PRECISION
(integer) #
précision (pour les types numeric
)
RETURNED_LENGTH
(integer) #longueur de la donnée en caractères
RETURNED_OCTET_LENGTH
(integer) #longueur de la représentation caractère de la donnée en octets
SCALE
(integer) #
échelle (pour le type numeric
)
TYPE
(integer) #code numérique du type de données de la colonne
Dans les ordres EXECUTE
, DECLARE
and OPEN
, l'effet des mots clés
INTO
and USING
est différent. Une zone de descripteur peut aussi être
construite manuellement pour fournir les paramètres d'entré
pour une requête ou un curseur et USING SQL DESCRIPTOR
est la façon de passer
les paramètres d'entrée à une requête paramétrisée. L'ordre
pour construire une zone de descripteur SQL est ci-dessous:
name
EXEC SQL SET DESCRIPTORname
VALUEnum
field
= :hostvar
;
PostgreSQL supporte la récupération de plus d'un enregistrement
dans un ordre FETCH
et les variables hôtes dans
ce cas doivent être des tableaux. Par exemple :
EXEC SQL BEGIN DECLARE SECTION; int id[5]; EXEC SQL END DECLARE SECTION; EXEC SQL FETCH 5 FROM mycursor INTO SQL DESCRIPTOR mydesc; EXEC SQL GET DESCRIPTOR mydesc VALUE 1 :id = DATA;
Une zone de descripteur SQLDA est une structure C qui peut aussi être utilisé pour récupérer les résultats et les métadonnées d'une requête. Une structure stocke un enregistrement du jeu de résultat.
EXEC SQL include sqlda.h; sqlda_t *mysqlda; EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;
Netez que le mot clé SQL
est omis. Les paragraphes qui parlent
des cas d'utilisation de INTO
and USING
dans Section 36.7.1 s'appliquent aussi ici, avec un point
supplémentaire. Dans un ordre DESCRIBE
le mot clé
DESCRIPTOR
peut être complètement omis si le mot clé
INTO
est utilisé :
EXEC SQL DESCRIBE prepared_statement INTO mysqlda;
Le déroulement général d'un programme qui utilise des SQLDA est:
Préparer une requête, et déclarer un curseur pour l'utiliser.
Déclarer une SQLDA pour les lignes de résultat.
Déclarer une SQLDA pour les paramètres d'entrées, et les initialiser (allocation mémoire, positionnement des paramètres).
Ouvrir un curseur avec la SQLDA d'entrée.
Récupérer les enregistrements du curseur, et les stocker dans une SQLDA de sortie.
Lire les valeurs de la SQLDA de sortie vers les variables hôtes (avec conversion si nécessaire).
Fermer le curseur.
Libérer la zone mémoire allouée pour la SQLDA d'entrée.
Les SQLDA utilisent 3 types de structures de données:
sqlda_t
, sqlvar_t
,
et struct sqlname
.
La structure de la SQLDA de PostgreSQL est similaire à celle de DB2 Universal Database d'IBM, des informations techniques sur la SQLDA de DB2 peuvent donc aider à mieux comprendre celle de PostgreSQL.
Le type de structure sqlda_t
est le type de la SQLDA
proprement dit. Il contient un enregistrement. Et deux ou plus
sqlda_t
peuvent être connectées par une liste
chaînée par le pointeur du champ desc_next
,
représentant par conséquent une collection ordonnée d'enregistrements.
Par conséquent, quand deux enregistrements ou plus sont récupérés,
l'application peut les lire en suivant le pointeur
desc_next
dans chaque nœud sqlda_t
.
La définition de sqlda_t
est :
struct sqlda_struct { char sqldaid[8]; long sqldabc; short sqln; short sqld; struct sqlda_struct *desc_next; struct sqlvar_struct sqlvar[1]; }; typedef struct sqlda_struct sqlda_t;
La signification des champs est :
sqldaid
#
Elle contient la chaîne littérale "SQLDA "
.
sqldabc
#Il contient la taille de l'espace alloué en octets.
sqln
#
Il content le nombre de paramètres d'entrée pour une requête paramétrique, dans
le cas où il est passé à un ordre OPEN
, DECLARE
ou
EXECUTE
utilisant le mot clé USING
.
Dans le cas où il sert de sortie à un ordre SELECT
,
EXECUTE
ou FETCH
statements,
sa valeur est la même que celle du champ sqld
.
sqld
#Il contient le nombre de champs du résultat.
desc_next
#
Si la requête retourne plus d'un enregistrement, plusieurs structures
SQLDA chaînées sont retournées, et desc_next
contient
un pointeur vers l'élément suivant (enregistrement) de la liste.
sqlvar
#C'est le tableau des colonnes du résultat.
Le type structure sqlvar_t
contient la valeur
d'une colonne et les métadonnées telles que son type et sa longueur.
La définition du type est :
struct sqlvar_struct { short sqltype; short sqllen; char *sqldata; short *sqlind; struct sqlname sqlname; }; typedef struct sqlvar_struct sqlvar_t;
La signification des champs est :
sqltype
#
Contient l'identifiant de type du champ. Pour les valeurs,
voyez enum ECPGttype
dans ecpgtype.h
.
sqllen
#
Contient la longueur binaire du champ, par exemple 4 octets pour
ECPGt_int
.
sqldata
#Pointe vers la donnée. Le format de la donnée est décrit dans Section 36.4.4.
sqlind
#Pointe vers l'indicateur de nullité. 0 signifie non nul, -1 signifie nul. null.
sqlname
#Le nom du champ.
Une structure struct sqlname
contient un nom de colonne.
Il est utilisé comme membre de la structure sqlvar_t
.
La définition de la structure est :
#define NAMEDATALEN 64 struct sqlname { short length; char data[NAMEDATALEN]; };
La signification des champs est :
Les étapes générales pour récupérer un jeu de données au moyen d'une SQLDA sont :
Déclarer une structure sqlda_t
pour recevoir le jeu de données.
Exécuter des commandes FETCH
/EXECUTE
/DESCRIBE
pour traiter une requête en spécifiant la SQLDA déclarée.
Vérifier le nombre d'enregistrements dans le résultat en inspectant sqln
, un membre de la structure sqlda_t
.
Récupérer les valeurs de chaque colonne des membres sqlvar[0]
, sqlvar[1]
, etc., de la structure sqlda_t
.
Aller à l'enregistrement suivant (sqlda_t
structure) en suivant le pointeur desc_next
, un membre de la structure sqlda_t
.
Répéter l'étape ci-dessus au besoin.
Voici un exemple de récupération d'un jeu de résultats au moyen d'une SQLDA.
Tout d'abord, déclarer une structure sqlda_t
pour recevoir le jeu de résultats.
sqlda_t *sqlda1;
Puis, spécifier la SQLDA dans une commande. Voici
un exemple avec une commande FETCH
.
EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
Faire une boucle suivant la liste chaînée pour récupérer les enregistrements.
sqlda_t *cur_sqlda; for (cur_sqlda = sqlda1; cur_sqlda != NULL; cur_sqlda = cur_sqlda->desc_next) { ... }
Dans la boucle, faire une autre boucle pour récupérer chaque colonne
de données (sqlvar_t
) de l'enregistrement.
for (i = 0; i < cur_sqlda->sqld; i++) { sqlvar_t v = cur_sqlda->sqlvar[i]; char *sqldata = v.sqldata; short sqllen = v.sqllen; ... }
Pour récupérer une valeur de colonne, vérifiez la valeur de
sqltype
. Puis, suivant le type de la colonne,
basculez sur une façon appropriée de copier les données du champ
sqlvar
vers une variable hôte.
char var_buf[1024]; switch (v.sqltype) { case ECPGt_char: memset(&var_buf, 0, sizeof(var_buf)); memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf) - 1 : sqllen)); break; case ECPGt_int: /* integer */ memcpy(&intval, sqldata, sqllen); snprintf(var_buf, sizeof(var_buf), "%d", intval); break; ... }
La méthode générale pour utiliser une SQLDA pour passer des paramètres d'entrée à une requête préparée sont :
Créer une requête préparée (prepared statement)
Déclarer une structure sqlda_t comme SQLDA d'entrée.
Allouer une zone mémorie (comme structure sqlda_t) pour la SQLDA d'entrée.
Positionner (copier) les valeurs d'entrée dans la mémoire allouée.
Ouvrir un curseur en spécifiant la SQLDA d'entrée.
Voici un exemple.
D'abord, créer une requête préparée.
EXEC SQL BEGIN DECLARE SECTION; char query[1024] = "SELECT d.oid, * FROM pg_database d, pg_stat_database s WHERE d.oid = s.datid AND (d.datname = ? OR d.oid = ?)"; EXEC SQL END DECLARE SECTION; EXEC SQL PREPARE stmt1 FROM :query;
Puis, allouer de la mémoire pour une SQLDA, et positionner
le nombre de paramètres d'entrée dans sqln
,
une variable membre de la structure<sqlda_t>. Quand deux paramètres
d'entrée ou plus sont requis pour la requête préparée, l'application
doit allouer de la mémoire supplémentaire qui est calculée par
(nombre de paramètres -1) * sizeof<sqlvar_t>. Les exemples affichés
ici allouent de l'espace mémoire pour deux paramètres d'entrée.
sqlda_t *sqlda2; sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t)); memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t)); sqlda2->sqln = 2; /* nombre de variables d'entrée */
Après l'allocation mémoire, stocker les valeurs des paramètres dans
le tableausqlvar[]
. (C'est le même tableau que
celui qui est utilisé quand la SQLDA reçoit un jeu de résultats.)
Dans cet exemple, les paramètres d'entrée sont
"postgres"
, de type chaîne, et 1
,
de type integer.
sqlda2->sqlvar[0].sqltype = ECPGt_char; sqlda2->sqlvar[0].sqldata = "postgres"; sqlda2->sqlvar[0].sqllen = 8; int intval = 1; sqlda2->sqlvar[1].sqltype = ECPGt_int; sqlda2->sqlvar[1].sqldata = (char *) &intval; sqlda2->sqlvar[1].sqllen = sizeof(intval);
En ouvrant un curseur et en spécifiant la SQLDA qui a été positionné auparavant, les paramètres d'entrée sont passés à la requête préparée.
EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
Et pour finir, après avoir utilisé les SQLDAs d'entrée, la mémoire allouée doit être libérée explicitement, contrairement aux SQLDAs utilisé pour recevoir le résultat d'une requête.
free(sqlda2);
Voici un programme de démonstration, qui montre comment récupérer des statistiques d'accès des bases, spécifiées par les paramètres d'entrée, dans les catalogues systèmes.
Cette application joint deux tables systèmes, pg_database et
pg_stat_database sur l'oid de la base, et récupère et affiche
aussi les statistiques des bases qui sont spécifiées par
deux paramètres d'entrées (une base postgres
et
un OID 1
).
Tout d'abord, déclarer une SQLDA pour l'entrée et une SQLDA pour la sortie.
EXEC SQL include sqlda.h; sqlda_t *sqlda1; /* un descripteur de sortie */ sqlda_t *sqlda2; /* un descripteur d'entrée */
Puis, se connecter à la base, préparer une requête, et déclarer un curseur pour la requête préparée.
int main(void) { EXEC SQL BEGIN DECLARE SECTION; char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )"; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT TO testdb AS con1 USER testuser; EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT; EXEC SQL PREPARE stmt1 FROM :query; EXEC SQL DECLARE cur1 CURSOR FOR stmt1;
Puis, mettre des valeurs dans la SQLDA d'entrée pour les
paramètres d'entrée. Allouer de la mémoire pour la SQL d'entrée,
et positionner le nombre de paramètres d'entrée dans
sqln
. Stocker le type, la valeur et la longueur
de la valeur dans sqltype
, sqldata
et sqllen
dans la structure sqlvar
.
/* Créer une structure SQLDA pour les paramètres d'entrée. */ sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t)); memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t)); sqlda2->sqln = 2; /* number of input variables */ sqlda2->sqlvar[0].sqltype = ECPGt_char; sqlda2->sqlvar[0].sqldata = "postgres"; sqlda2->sqlvar[0].sqllen = 8; intval = 1; sqlda2->sqlvar[1].sqltype = ECPGt_int; sqlda2->sqlvar[1].sqldata = (char *)&intval; sqlda2->sqlvar[1].sqllen = sizeof(intval);
Après avoir positionné la SQLDA d'entrée, ouvrir un curseur avec la SQLDA d'entrée.
/* Ouvrir un curseur avec les paramètres d'entrée. */ EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
Récupérer les enregistrements dans la SQLDA de sortie à partir
du curseur ouvert. (En général, il faut appeler FETCH
de façon répétée dans la boucle, pour récupérer tous les enregistrements
du jeu de données.)
while (1) { sqlda_t *cur_sqlda; /* Assigner le descripteur au curseur */ EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
Ensuite, récupérer les enregistrements du FETCH de la SQLDA,
en suivant la liste chaînée de la structure sqlda_t
.
for (cur_sqlda = sqlda1 ; cur_sqlda != NULL ; cur_sqlda = cur_sqlda->desc_next) { ...
Lire chaque colonne dans le premier enregistrement. Le nombre de colonnes
est stocké dans sqld
, les données réelles
de la première colonne sont stockées dans sqlvar[0]
,
tous deux membres de la structuresqlda_t
.
/* Afficher toutes les colonnes d'un enregistrement. */ for (i = 0; i < sqlda1->sqld; i++) { sqlvar_t v = sqlda1->sqlvar[i]; char *sqldata = v.sqldata; short sqllen = v.sqllen; strncpy(name_buf, v.sqlname.data, v.sqlname.length); name_buf[v.sqlname.length] = '\0';
Maintenant, la donnée de la colonne est stockée dans la variable
v
.
Copier toutes les données dans les variables host, en inspectant
v.sqltype
pour connaître le type de la colonne.
switch (v.sqltype) { int intval; double doubleval; unsigned long long int longlongval; case ECPGt_char: memset(&var_buf, 0, sizeof(var_buf)); memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf)-1 : sqllen)); break; case ECPGt_int: /* integer */ memcpy(&intval, sqldata, sqllen); snprintf(var_buf, sizeof(var_buf), "%d", intval); break; ... default: ... } printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype); }
Fermer le curseur après avoir traité tous les enregistrements, et se déconnecter de la base de données.
EXEC SQL CLOSE cur1; EXEC SQL COMMIT; EXEC SQL DISCONNECT ALL;
Le programme dans son entier est visible dans Exemple 36.1.
Exemple 36.1. Programme de Démonstration SQLDA
#include <stdlib.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> EXEC SQL include sqlda.h; sqlda_t *sqlda1; /* descripteur pour la sortie */ sqlda_t *sqlda2; /* descripteur pour l'entrée */ EXEC SQL WHENEVER NOT FOUND DO BREAK; EXEC SQL WHENEVER SQLERROR STOP; int main(void) { EXEC SQL BEGIN DECLARE SECTION; char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )"; int intval; unsigned long long int longlongval; EXEC SQL END DECLARE SECTION; EXEC SQL CONNECT TO uptimedb AS con1 USER uptime; EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT; EXEC SQL PREPARE stmt1 FROM :query; EXEC SQL DECLARE cur1 CURSOR FOR stmt1; /* Créer une structure SQLDB pour let paramètres d'entrée */ sqlda2 = (sqlda_t *)malloc(sizeof(sqlda_t) + sizeof(sqlvar_t)); memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t)); sqlda2->sqln = 2; /* a number of input variables */ sqlda2->sqlvar[0].sqltype = ECPGt_char; sqlda2->sqlvar[0].sqldata = "postgres"; sqlda2->sqlvar[0].sqllen = 8; intval = 1; sqlda2->sqlvar[1].sqltype = ECPGt_int; sqlda2->sqlvar[1].sqldata = (char *) &intval; sqlda2->sqlvar[1].sqllen = sizeof(intval); /* Ouvrir un curseur avec les paramètres d'entrée. */ EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2; while (1) { sqlda_t *cur_sqlda; /* Assigner le descripteur au curseur */ EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1; for (cur_sqlda = sqlda1 ; cur_sqlda != NULL ; cur_sqlda = cur_sqlda->desc_next) { int i; char name_buf[1024]; char var_buf[1024]; /* Afficher toutes les colonnes d'un enregistrement. */ for (i=0 ; i<cur_sqlda->sqld ; i++) { sqlvar_t v = cur_sqlda->sqlvar[i]; char *sqldata = v.sqldata; short sqllen = v.sqllen; strncpy(name_buf, v.sqlname.data, v.sqlname.length); name_buf[v.sqlname.length] = '\0'; switch (v.sqltype) { case ECPGt_char: memset(&var_buf, 0, sizeof(var_buf)); memcpy(&var_buf, sqldata, (sizeof(var_buf)<=sqllen ? sizeof(var_buf)-1 : sqllen) ); break; case ECPGt_int: /* integer */ memcpy(&intval, sqldata, sqllen); snprintf(var_buf, sizeof(var_buf), "%d", intval); break; case ECPGt_long_long: /* bigint */ memcpy(&longlongval, sqldata, sqllen); snprintf(var_buf, sizeof(var_buf), "%lld", longlongval); break; default: { int i; memset(var_buf, 0, sizeof(var_buf)); for (i = 0; i < sqllen; i++) { char tmpbuf[16]; snprintf(tmpbuf, sizeof(tmpbuf), "%02x ", (unsigned char) sqldata[i]); strncat(var_buf, tmpbuf, sizeof(var_buf)); } } break; } printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype); } printf("\n"); } } EXEC SQL CLOSE cur1; EXEC SQL COMMIT; EXEC SQL DISCONNECT ALL; return 0; }
L'exemple suivant devrait ressembler à quelque chose comme ce qui suit (des nombres seront différents).
oid = 1 (type: 1) datname = template1 (type: 1) datdba = 10 (type: 1) encoding = 0 (type: 5) datistemplate = t (type: 1) datallowconn = t (type: 1) datconnlimit = -1 (type: 5) datfrozenxid = 379 (type: 1) dattablespace = 1663 (type: 1) datconfig = (type: 1) datacl = {=c/uptime,uptime=CTc/uptime} (type: 1) datid = 1 (type: 1) datname = template1 (type: 1) numbackends = 0 (type: 5) xact_commit = 113606 (type: 9) xact_rollback = 0 (type: 9) blks_read = 130 (type: 9) blks_hit = 7341714 (type: 9) tup_returned = 38262679 (type: 9) tup_fetched = 1836281 (type: 9) tup_inserted = 0 (type: 9) tup_updated = 0 (type: 9) tup_deleted = 0 (type: 9) oid = 11511 (type: 1) datname = postgres (type: 1) datdba = 10 (type: 1) encoding = 0 (type: 5) datistemplate = f (type: 1) datallowconn = t (type: 1) datconnlimit = -1 (type: 5) datfrozenxid = 379 (type: 1) dattablespace = 1663 (type: 1) datconfig = (type: 1) datacl = (type: 1) datid = 11511 (type: 1) datname = postgres (type: 1) numbackends = 0 (type: 5) xact_commit = 221069 (type: 9) xact_rollback = 18 (type: 9) blks_read = 1176 (type: 9) blks_hit = 13943750 (type: 9) tup_returned = 77410091 (type: 9) tup_fetched = 3253694 (type: 9) tup_inserted = 0 (type: 9) tup_updated = 0 (type: 9) tup_deleted = 0 (type: 9)