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