Programando en C - pg_uname


En este pequeño artículo vamos a ver como instalar en postgreSQL una función programada en C por nosotros.

La posibilidad que tiene PostgreSQL de poder programar nuestras propias funciones en C y usarlas desde nuestra base de datos es una de las muchas características que hacen a esta base de datos tan potente. Una función programada en C podra tener entre otras cosas, acceso a muchas funciones del sistema y a la velocidad de proceso que C nos brinda.

Hay que reconocer que esto no es una tarea apta para principiantes. Se necesitan conocimientos de C y leer un poco de documentación. La documentación sobre la programación de funciones en C para PostgreSQL esta disponible en el Capítulo 34. Extending SQL y en la Sección 34.9. C-Language Functions del manual de PostgreSQL.

Existe un libro magnífico sobre la programación avanzada en C en sistemas Unix, llamado “Advanced Programming in the UNIX Environment, Second Edition (Addison-Wesley Professional Computing Series)” (ISBN-13: 978-0321525949). Totalmente recomendado para los interesados en el tema.

Como ejemplo vamos a crear una función que acceda a la función uname() del sistema operativo. Tambien veremos como acceder a los datos disponibles mediante uname() desde la base de datos.

Lo primero que tenemos que hacer es programar nuestra función. En nuestro caso vamos a crear un función llamada pg_uname() con la que podamos obtener los parametros sysname, nodename, release, version, machine de nuestro sistema operativo Linux.

Creamos un fichero llamado pg_uname.c con el siguiente contenido:

#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include <sys/utsname.h>

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(pg_uname);

Datum
pg_uname(PG_FUNCTION_ARGS)
{
    text *argument = PG_GETARG_TEXT_P(0);
    size_t argumentlen = VARSIZE(argument)-VARHDRSZ;

    text *result = (text *) palloc(256);
    char *option = (char *) palloc(argumentlen+1);
    
    char sysname[] = "sysname";
    char nodename[] = "nodename";
    char release[] = "release";
    char version[] = "version";
    char machine[] = "machine";
    char null[] = "null";

    struct utsname uname_pointer;
    uname(&uname_pointer);

    memcpy(option,VARDATA(argument),argumentlen);
    option[argumentlen] = '\0';

    if (strcmp(option,sysname) == 0){
      SET_VARSIZE(result, strlen(uname_pointer.sysname) + VARHDRSZ);
      memcpy(VARDATA(result),uname_pointer.sysname,strlen(uname_pointer.sysname));
    }
    else if (strcmp(option,nodename) == 0){
      SET_VARSIZE(result, strlen(uname_pointer.nodename) + VARHDRSZ);
      memcpy(VARDATA(result),uname_pointer.nodename,strlen(uname_pointer.nodename));
    }
    else if (strcmp(option,release) == 0){
       SET_VARSIZE(result, strlen(uname_pointer.release) + VARHDRSZ);
       memcpy(VARDATA(result),uname_pointer.release,strlen(uname_pointer.release));
    }
    else if (strcmp(option,version) == 0){
      SET_VARSIZE(result, strlen(uname_pointer.version) + VARHDRSZ);
      memcpy(VARDATA(result),uname_pointer.version,strlen(uname_pointer.version));
 }
    else if (strcmp(option,machine) == 0){
      SET_VARSIZE(result, strlen(uname_pointer.machine) + VARHDRSZ);
      memcpy(VARDATA(result),uname_pointer.machine,strlen(uname_pointer.machine));
    }
    else{
      memcpy(VARDATA(result),null,sizeof(null));
    }

    pfree(option);
    PG_RETURN_TEXT_P(result);
}

A continuación tenemos que compilar la función e instalarla en un directorio donde postgreSQL pueda leerla y cargarla. Para compilarla utilizamos el compilador por defecto en linux, gcc.

root@server:~$ gcc -I /usr/local/include -I /usr/local/include/postgresql/server 
-fpic -cpg_uname.c

root@server:~$ gcc -I /usr/local/include -I /usr/local/include/postgresql/server 
-shared -o pg_uname.so pg_uname.o 

root@server:~$ cp pg_uname.so /usr/local/lib

Una vez que tenemos la función compilada e instalada en nuestro sistema operativo. Utizaremos psql para decirle a PostgreSQL donde encontrarla.

postgres@server:~$ psql 
Welcome to psql 8.3.7, the PostgreSQL interactive terminal.

Type:  \copyright for distribution terms
       \h for help with SQL commands
       \? for help with psql commands
       \g or terminate with semicolon to execute query
       \q to quit

postgres=# CREATE OR REPLACE FUNCTION pg_uname(text) RETURNS text 
AS '/usr/local/lib/pg_uname.so', 'pg_uname'
LANGUAGE c STRICT;

CREATE FUNCTION

Despues de esto podemos empezar a utilizar la función desde comandos SQL:

postgres=# SELECT pg_uname('nodename');
      pg_uname      
--------------------
 server.ejemplo.com
(1 row)

postgres=# SELECT pg_uname('machine');
 pg_uname 
----------
 x86_64
(1 row)

Podriamos crear una VIEW llamada sysinfo que utilize pg_uname() junto con otras funciones de PostgreSQL para obtener información sobre nuestro sistema. Por ejemplo:

postgres=# CREATE OR REPLACE VIEW sysinfo AS 
SELECT pg_uname('sysname') AS sysname,
pg_uname('nodename') AS nodename,
pg_uname('release') AS kernel_release,
pg_uname('version') AS "kernel_version",
pg_uname('machine') AS machine,
substr("version"(), 11, 7) AS pgversion,
to_char(pg_postmaster_start_time(),'YYYY-MM-DD HH24:MM:SS') AS pg_start,
age(now(),pg_postmaster_start_time()) AS pg_uptime;
CREATE VIEW

postgres=# \x
Expanded display is on.
postgres=# SELECT * from sysinfo ;

-[ RECORD 1 ]----------------------------------
sysname           | Linux
nodename          | server.ejemplo.com
kernel_release    | 2.6.9-67.0.15.ELsmp
kernel_version    | #1 SMP Tue Apr 22 13:58:43 EDT 2008
machine           | x86_64
pgversion         | 8.3.6 
pg_start          | 2009-02-12 07:02:17
pg_uptime         | 2 mons 5 days 13:12:30.059856

Bueno esto es todo en este artículo, espero que os haya ayudado a entender mejor como instalar en PostgreSQL nuestras propias funciones en C