Funciones de Lenguaje Compilado (C)

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.

Nota

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.)

Funciones de Lenguaje C sobre Tipos Base

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
abstimeAbsoluteTimeutils/nabstime.h
boolboolinclude/c.h
box(BOX *)utils/geo-decls.h
bytea(bytea *)include/postgres.h
charcharN/A
cidCIDinclude/postgres.h
datetime(DateTime *)include/c.h or include/postgres.h
int2int2include/postgres.h
int2vector(int2vector *)include/postgres.h
int4int4include/postgres.h
float4float32 or (float4 *)include/c.h or include/postgres.h
float8float64 or (float8 *)include/c.h or include/postgres.h
lseg(LSEG *)include/geo-decls.h
name(Name)include/postgres.h
oidoidinclude/postgres.h
oidvector(oidvector *)include/postgres.h
path(PATH *)utils/geo-decls.h
point(POINT *)utils/geo-decls.h
regprocregproc or REGPROCinclude/postgres.h
reltimeRelativeTimeutils/nabstime.h
text(text *)include/postgres.h
tidItemPointerstorage/itemptr.h
timespan(TimeSpan *)include/c.h or include/postgres.h
tintervalTimeIntervalutils/nabstime.h
uint2uint16include/c.h
uint4uint32include/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:

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).

Funciones del Lenguaje C sobre Tipos Compuestos

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';
     
En la consulta anterior, podemos definir c_overpaid como:
         #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.

Escribiendo código

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>
     
al principio del fichero fuente C.

Las reglas básicas para construir funciones C son las siguientes: