Las funciones escritas en C se pueden compilar en objetos que se pueden cargar de forma dinámica, y usar para implementar funciones SQL definidas por el usuario. La primera vez que la función definida por el usuario es llamada dentro del backend, el cargador dinámico carga el código objeto de la función en memoria, y enlaza la función con el ejecutable en ejecución de Postgres. La sintaxis SQL para CREATE FUNCTION enlaza la función SQL a la función en código C de una de dos formas. Si la función SQL tiene el mismo nombre que la función en código C se usa la primera forma. El argumento cadena en la cláusula AS es el nombre de camino (pathname) completo del fichero que contiene el objeto compilado que se puede cargar de forma dinámica. Si el nombre de la función C es diferente del nombre deseado de la función SQL, entonces se usa la segunda forma. En esta forma la cláusula AS toma dos argumentos cadena, el primero es el nombre del camino completo del fichero objeto que se puede cargar de forma dinámica, y el segundo es el símbolo de enlace que el cargador dinámico debería buscar. Este símbolo de enlace es solo el nombre de función en el código fuente C.
Después de que se use por primera vez, una función de usuario, dinámicamente cargada, se retiene en memoria, y futuras llamadas a la función solo incurren en la pequeña sobrecarga de una búsqueda de tabla de símbolos. |
La cadena que especifica el fichero objeto (la cadena en la cláusula AS) debería ser el camino completo del fichero de código objeto para la función, unido por comillas simples. Si un símbolo de enlace se usa en la cláusula AS, el símbolo de enlace se debería unir por comillas simples también, y debería ser exactamente el mismo que el nombre de la función en el código fuente C. En sistemas Unix la orden nm imprimirá todos los símbolos de enlace de un objeto que se puede cargar de forma dinámica. (Postgres no compilará una función automáticamente; se debe compilar antes de que se use en una orden CREATE FUNCTION. Ver abajo para información adicional.)
La tabla siguiente da el tipo C requerido para los parámetros en las funciones C que se cargarán en Postgres. La columna "Defined In" da el fichero de cabecera real (en el directorio .../src/backend/) en el que el tipo C equivalente se define. Sin embargo, si incluye utils/builtins.h, estos ficheros se incluirán de forma automática.
Tabla 1. Tipos de C equivalentes para los tipos internos de Postgres
Built-In Type | C Type | Defined In |
---|---|---|
abstime | AbsoluteTime | utils/nabstime.h |
bool | bool | include/c.h |
box | (BOX *) | utils/geo-decls.h |
bytea | (bytea *) | include/postgres.h |
char | char | N/A |
cid | CID | include/postgres.h |
datetime | (DateTime *) | include/c.h or include/postgres.h |
int2 | int2 | include/postgres.h |
int2vector | (int2vector *) | include/postgres.h |
int4 | int4 | include/postgres.h |
float4 | float32 or (float4 *) | include/c.h or include/postgres.h |
float8 | float64 or (float8 *) | include/c.h or include/postgres.h |
lseg | (LSEG *) | include/geo-decls.h |
name | (Name) | include/postgres.h |
oid | oid | include/postgres.h |
oidvector | (oidvector *) | include/postgres.h |
path | (PATH *) | utils/geo-decls.h |
point | (POINT *) | utils/geo-decls.h |
regproc | regproc or REGPROC | include/postgres.h |
reltime | RelativeTime | utils/nabstime.h |
text | (text *) | include/postgres.h |
tid | ItemPointer | storage/itemptr.h |
timespan | (TimeSpan *) | include/c.h or include/postgres.h |
tinterval | TimeInterval | utils/nabstime.h |
uint2 | uint16 | include/c.h |
uint4 | uint32 | include/c.h |
xid | (XID *) | include/postgres.h |
Internamente, Postgres considera un tipo base como un "blob de memoria". Las funciones definidas por el usuario que usted define sobre un tipo en turn definen la forma en que Postgres puede operar sobre él. Esto es, Postgres solo almacenará y recuperará los datos desde disco y solo usará sus funciones definidas por el usuario para introducir y procesar los datos, así como para obtener la salida de los datos. Los tipos base pueden tener uno de los tres formatos internos siguientes:
pass by value, fixed-length
pass by reference, fixed-length
pass by reference, variable-length
Los tipos por valor solo pueden tener 1, 2 o 4 bytes de longitud (incluso si su computadora soporta tipos por valor de otros tamaños). Postgres mismo solo pasa los tipos entero por valor. Debería tener cuidado al definir sus tipos para que tengan el mismo tamaño (en bytes) en todas las arquitecturas. Por ejemplo, el tipo long es peligroso porque es de 4 bytes en algunas máquinas y de 8 bytes en otras, mientras que el tipo int es de 4 bytes en la mayoría de las máquinas Unix (aunque no en la mayoría de computadores personales). Una implementación razonable del tipo int4 en las máquinas Unix podría ser:
/* 4-byte integer, passed by value */ typedef int int4; |
En el otro lado, los tipos de longitud fija de cualquier tamaño se pueden pasar por referencia. Por ejemplo, aquí se presenta una implementación de ejemplo de un tipo de Postgres:
/* 16-byte structure, passed by reference */ typedef struct { double x, y; } Point; |
Solo los punteros a tales tipos se pueden usar a la hora de pasarlos como argumentos de entrada o de retorno en las funciones de Postgres. Finalmente, todos los tipos de longitud variable se deben pasar también por referencia. Todos los tipos de longitud variable deben comenzar con un campo length de exactamente 4 bytes, y todos los datos que se tengan que almacenar dentro de ese tipo deben estar situados en la memoria inmediatamente a continuación de ese campo length. El campo length es la longitud total de la estructura (es decir, incluye el tamaño del campo length mismo). Podemos definir el tipo texto como sigue:
typedef struct { int4 length; char data[1]; } text; |
Obviamente, el campo data no es suficientemente largo para almacenar todas las cadenas posibles; es imposible declarar tal estructura en C. Al manipular tipos de longitud variable, debemos tener cuidado de reservar la cantidad de memoria correcta y de inicializar el campo length. Por ejemplo, si quisiéramos almacenar 40 bytes en una estructura text, podríamos usar un fragmento de código como este:
#include "postgres.h" ... char buffer[40]; /* our source data */ ... text *destination = (text *) palloc(VARHDRSZ + 40); destination->length = VARHDRSZ + 40; memmove(destination->data, buffer, 40); ... |
Ahora que hemos visto todas las estructuras posibles para los tipos base, podemos mostrar algunos ejemplos de funciones reales. Suponga que funcs.c es así:
#include <string.h> #include "postgres.h" /* By Value */ int add_one(int arg) { return(arg + 1); } /* By Reference, Fixed Length */ Point * makepoint(Point *pointx, Point *pointy ) { Point *new_point = (Point *) palloc(sizeof(Point)); new_point->x = pointx->x; new_point->y = pointy->y; return new_point; } /* By Reference, Variable Length */ text * copytext(text *t) { /* * VARSIZE is the total size of the struct in bytes. */ text *new_t = (text *) palloc(VARSIZE(t)); memset(new_t, 0, VARSIZE(t)); VARSIZE(new_t) = VARSIZE(t); /* * VARDATA is a pointer to the data region of the struct. */ memcpy((void *) VARDATA(new_t), /* destination */ (void *) VARDATA(t), /* source */ VARSIZE(t)-VARHDRSZ); /* how many bytes */ return(new_t); } text * concat_text(text *arg1, text *arg2) { int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ; text *new_text = (text *) palloc(new_text_size); memset((void *) new_text, 0, new_text_size); VARSIZE(new_text) = new_text_size; strncpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1)-VARHDRSZ); strncat(VARDATA(new_text), VARDATA(arg2), VARSIZE(arg2)-VARHDRSZ); return (new_text); } |
On OSF/1 we would type:
CREATE FUNCTION add_one(int4) RETURNS int4 AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c'; CREATE FUNCTION makepoint(point, point) RETURNS point AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c'; CREATE FUNCTION concat_text(text, text) RETURNS text AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c'; CREATE FUNCTION copytext(text) RETURNS text AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c'; |
En otros sistemas, podríamos tener que especificar la extensión del nombre del fichero como .sl (para indicar que es una librería (o biblioteca) compartida).
Los tipos compuestos no tienen un formato fijo como las estructuras de C. Las instancias de un tipo compuesto pueden contener campos null. Además, los tipos compuestos que son parte de una jerarquía de herencia pueden tener campos diferentes respecto a otros miembros de la misma jerarquía de herencia. Por ello, Postgres proporciona una interfaz procedural para acceder a los campos de los tipos compuestos desde C. Cuando Postgres procesa un conjunto de instancias, cada instancia se pasará a su función como una estructura opaca de tipo TUPLE. Suponga que queremos escribir una función para responder a la consulta
* SELECT name, c_overpaid(EMP, 1500) AS overpaid FROM EMP WHERE name = 'Bill' or name = 'Sam'; |
#include "postgres.h" #include "executor/executor.h" /* for GetAttributeByName() */ bool c_overpaid(TupleTableSlot *t, /* the current instance of EMP */ int4 limit) { bool isnull = false; int4 salary; salary = (int4) GetAttributeByName(t, "salary", &isnull); if (isnull) return (false); return(salary > limit); } |
GetAttributeByName es la función de sistema de Postgres que devuelve los atributos fuera de la instancia actual. Tiene tres argumentos: el argumento de tipo TUPLE pasado a la función, el nombre del atributo deseado, y un parámetro de retorno que describe si el atributo es null. GetAttributeByName alineará los datos apropiadamente de forma que usted pueda convertir su valor de retorno al tipo deseado. Por ejemplo, si tiene un atributo name que es del tipo name, la llamada a GetAttributeByName sería así:
char *str; ... str = (char *) GetAttributeByName(t, "name", &isnull) |
La consulta siguiente permite que Postgres conozca a la función c_overpaid:
* CREATE FUNCTION c_overpaid(EMP, int4) RETURNS bool AS 'PGROOT/tutorial/obj/funcs.so' LANGUAGE 'c'; |
Aunque hay formas de construir nuevas instancias o de modificar las instancias existentes desde dentro de una función C, éstas son demasiado complejas para discutirlas en este manual.
Ahora volvemos a la tarea más difícil de escribir funciones del lenguaje de programación. Aviso: esta sección del manual no le hará un programador. Debe tener un gran conocimiento de C (incluyendo el uso de punteros y el administrador de memoria malloc) antes de intentar escribir funciones C para usarlas con Postgres. Aunque sería posible cargar funciones escritas en lenguajes distintos a C en Postgres, eso es a menudo difícil (cuando es posible hacerlo completamente) porque otros lenguajes, tales como FORTRAN y Pascal a menudo no siguen la misma convención de llamada que C. Esto es, otros lenguajes no pasan argumentos y devuelven valores entre funciones de la misma forma. Por esta razón, asumiremos que las funciones de su lenguaje de programación están escritas en C.
Las funciones C con tipos base como argumentos se pueden escribir de una forma sencilla. Los equivalentes C de los tipos internos de Postgres son accesibles en un fichero C si PGROOT/src/backend/utils/builtins.h se incluye como un fichero de cabecera. Esto se puede conseguir escribiendo
#include <utils/builtins.h> |
Las reglas básicas para construir funciones C son las siguientes:
La mayoría de los ficheros cabecera (include) para Postgres deberían estar ya instalados en PGROOT/include (ver Figura 2). Debería incluir siempre
-I$PGROOT/include |
-I$PGROOT/src/backend -I$PGROOT/src/backend/include -I$PGROOT/src/backend/port/<PORTNAME> -I$PGROOT/src/backend/obj |
Al reservar memoria, use las rutinas de Postgres palloc y pfree en vez de las rutinas de la librería de C correspondientes malloc y free. La memoria reservada por palloc se liberará automáticamente al final de cada transacción, previniendo fallos de memoria.
Siempre céntrese en los bytes de sus estructuras usando memset o bzero. Varias rutinas (tales como el método de acceso hash, hash join y el algoritmo sort) computan funciones de los bits puros contenidos en su estructura. Incluso si usted inicializa todos los campos de su estructura, puede haber varios bytes de relleno de alineación (agujeros en la estructura) que pueden contener valores incorrectos o basura.
La mayoría de los tipos internos de Postgres se declaran en postgres.h, por eso es una buena idea incluir siempre ese fichero también. Incluyendo postgres.h incluirá también elog.h y palloc.h por usted.
Compilar y cargar su código objeto para que se pueda cargar dinámicamente en Postgres siempre requiere flags (o banderas) especiales. Ver Enlazando funciones de carga dinámica para una explicación detallada de cómo hacerlo para su sistema operativo concreto.