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