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. La port�e de descripteur est QUOI?. 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 33.7.2, � Zones de Descripteurs SQLDA �.
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 DESCRIPTOR name :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 DESCRIPTOR name VALUE num :hostvar = field;
num peut �tre un integer literal, ou une variable h�te contenant un integer. Les champs possibles sont:
nombres d'enregistrements dans le r�sultat
objet de donn�e proprement dit (par cons�quent, le type de donn�es de ce champ d�pend de la requ�te)
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.
non impl�ment�
l'indicateur (indique une valeur null ou une troncature de valeur)
non impl�ment�
longueur de la donn�e en caract�res
nom de la colonne
non impl�ment�
longueur de la repr�sentation caract�re de la donn�e en octets
pr�cision (pour les types numeric)
longueur de la donn�e en caract�res
longueur de la repr�sentation caract�re de la donn�e en octets
�chelle (pour le type numeric)
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 name 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:
EXEC SQL SET DESCRIPTOR name VALUE num 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 33.7.1, � Zones de Descripteur SQL nomm�es � s'appliqent 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:
Elle contient la cha�ne litt�rale "SQLDA ".
Il contient la taille de l'espace allou� en octets.
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.
Il contient le nombre de champs du r�sultat.
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.
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:
Contient l'identifiant de type du champ. Pour les valeurs, voyez enum ECPGttype dans ecpgtype.h.
Contient la longueur binaire du champ, par exemple 4 octets pour ECPGt_int.
Pointe vers la donn�e. Le format de la donn�e est d�crit dans Section 33.4.4, � Correspondance de Type �.
Pointe vers l'indicateur de nullit�. 0 signifie non nul, -1 signifie nul. null.
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:
Contient la longueur du nom du champ.
Contient le nom du champ proprement dit.
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 alloupe.
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'entrn�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 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 33.1, � Programme de D�monstration SQLDA �.
Exemple 33.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 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) datlastsysoid = 11510 (type: 1) 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) datlastsysoid = 11510 (type: 1) 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)