sql >> Base de Datos >  >> RDS >> PostgreSQL

Descripción general de la programación del lado del servidor en PostgreSQL

Hay muchas formas en las que puede hacer que el servidor de Postgres ejecute código predefinido. A continuación se muestra una lista completa, con ejemplos, de formas en las que puede permitir que el servidor de Postgres almacene lógica predefinida, que puede usar más adelante desde su aplicación.

Funciones SQL

Postgres le permite crear "funciones definidas por el usuario", donde el cuerpo de la función se puede escribir en un idioma compatible. Las "funciones SQL" son funciones definidas por el usuario escritas en SQL normal, que es la forma más sencilla de encapsular consultas complejas y secuencias de instrucciones SQL.

Aquí hay un par de ejemplos:

-- update item price and record the change
CREATE FUNCTION update_price(item text, newprice numeric) RETURNS void AS $$
    UPDATE items SET price=$2 WHERE name=$1;
    INSERT INTO audit (event, new_price, at, item)
      VALUES ('price changed', $2, now(), $1);
$$ LANGUAGE SQL;

-- a function from uuid-osp
CREATE FUNCTION uuid_timestamp_bits(uuid) RETURNS varbit AS
$$ SELECT ('x' || substr($1::text, 15, 4) || substr($1::text, 10, 4) ||
           substr($1::text, 1, 8) || substr($1::text, 20, 4))::bit(80)
          & x'0FFFFFFFFFFFFFFF3FFF' $$
LANGUAGE SQL STRICT IMMUTABLE;

Las funciones SQL pueden aceptar y devolver tipos base, tipos compuestos y filas. También admiten números variables de argumentos, valores predeterminados para argumentos y argumentos polimórficos. Incluso pueden devolver varias filas, imitando un SELECT de una tabla. Tampoco es necesario que devuelvan nada.

Sin embargo, el cuerpo de la función solo puede contener instrucciones SQL. Esto significa que no hay declaraciones de control de flujo (si, mientras, …), variables y similares.

El comando CREATE FUNCTION se utiliza para crear la función. Como de costumbre, puedes ALTERARLOS y ABANDONARLOS.

Este es un gran lugar para comenzar a profundizar más:https://www.postgresql.org/docs/current/xfunc-sql.html

Funciones C

Si bien las funciones de SQL son las más fáciles de escribir y las menos poderosas, en el otro extremo del espectro, las funciones se pueden escribir en C y prácticamente pueden hacer cualquier cosa. Dichas funciones deben codificarse en C y compilarse como una biblioteca compartida que Postgres puede cargar dinámicamente.

Debe indicarle a Postgres dónde cargar la biblioteca compartida, el nombre y la firma de la función:

CREATE FUNCTION sum(integer, integer) RETURNS integer
    AS 'myfuncs', 'sum'
    LANGUAGE C STRICT;

Esto dice que la biblioteca compartida myfuncs.so , presente en una ruta de búsqueda predefinida, contiene puntos de entrada a los que Postgres puede llamar, siendo uno de los puntos de entrada "suma", que se puede invocar como una función.

El código real en C sería demasiado largo para incluirlo aquí, pero puede leerlo todo en los documentos. Combinado con la interfaz de programación del servidor (SPI), es posible realizar casi cualquier operación que pueda realizar de otra manera.

Por ejemplo, con las funciones C definidas aquí, puede realizar solicitudes HTTP:

SELECT status, content_type FROM http_get('https://postgresql.org/');

También es posible escribir dichas bibliotecas compartidas en otros lenguajes como C++ o Go, que pueden crear bibliotecas compartidas con enlaces "C".

Funciones PL/pgSQL

Además de SQL y C, puede escribir funciones en lenguajes de procedimiento . El núcleo de PostgreSQL admite cuatro lenguajes de este tipo:pgSQL, Python, Perl y Tcl. El soporte para cualquier lenguaje de procedimiento proviene de una biblioteca compartida de C y funciona de manera muy similar a mod_perl o mod_python de la era Apache.

pgSQL es el lenguaje similar a SQL canónico, más utilizado, en el que se escriben las funciones almacenadas para PostgreSQL. Está disponible por defecto, gracias a que está instalado en template1 .

PL/pgSQL es un lenguaje completo con variables, expresiones y sentencias de control; e incluye funciones como cursores para trabajar con datos SQL en particular. Está ampliamente documentado aquí.

Aquí hay un ejemplo:

CREATE FUNCTION repeat(times integer, s text)
    RETURNS text
    AS $$
DECLARE
    result text;
BEGIN
    result := '';
    FOR i IN 1..times LOOP
        result := result || s;
    END LOOP;
    RETURN result;
END;
$$
LANGUAGE plpgsql
IMMUTABLE;

-- psql> SELECT repeat(10, '*');
--    repeat
-- ------------
--  **********
-- (1 row)

Otros lenguajes de procedimiento básicos

Los otros lenguajes de procedimiento (Python, Perl, Tcl) permiten a los desarrolladores usar un lenguaje con el que ya se sienten cómodos. Aunque el soporte para estos lenguajes se encuentra en el árbol de código fuente de Postgres, las distribuciones no suelen instalar los binarios de forma predeterminada. Por ejemplo, en Debian puede que tenga que hacer:

sudo apt install postgresql-plpython-11

para instalar el soporte PL/Python para PostgreSQL 11.

Independientemente del idioma que esté utilizando para escribir una función, la persona que llama no percibe ninguna diferencia en su uso.

Pitón

La extensión PL/Python admite funciones de escritura en Python 2 y Python 3. Para instalarlo, haga lo siguiente:

CREATE EXTENSION plpythonu;

Aquí hay una función escrita en PL/Python:

CREATE FUNCTION pymax (a integer, b integer)
  RETURNS integer
AS $$
  if a > b:
    return a
  return b
$$ LANGUAGE plpythonu;

El entorno de Python en el que se ejecuta el cuerpo de la función tiene un módulo llamado plpy importado automáticamente en él. Este módulo contiene métodos que le permiten preparar y ejecutar consultas, manejar transacciones y trabajar con cursores.

Puede encontrar más información en el capítulo 46 de los documentos de Postgres.

Perl

Bueno, sí, Perla. Los procesos de desarrollo, prueba y construcción de Postgres utilizan Perlextensively, y también se admite como lenguaje de procedimientos. Para comenzar a usarlo, asegúrese de que todos los paquetes binarios relevantes para su distribución estén instalados (por ejemplo, "postgresql-plperl-nn" para Debian) e instale la extensión "plperl".

Aquí hay una función escrita en PL/Perl:

CREATE FUNCTION perl_max (integer, integer) RETURNS integer AS $$
    my ($x, $y) = @_;
    if (not defined $x) {
        return undef if not defined $y;
        return $y;
    }
    return $x if not defined $y;
    return $x if $x > $y;
    return $y;
$$ LANGUAGE plperl;

Documentación completa aquí.

Tcl

Tcl es otro PL compatible con el núcleo de Postgres. Aquí hay un ejemplo:

CREATE FUNCTION tcl_max(integer, integer) RETURNS integer AS $$
    if {[argisnull 1]} {
        if {[argisnull 2]} { return_null }
        return $2
    }
    if {[argisnull 2]} { return $1 }
    if {$1 > $2} {return $1}
    return $2
$$ LANGUAGE pltcl;

Para obtener más información, consulte los documentos aquí.

Lenguajes de procedimiento no centrales

Más allá de estos lenguajes, existen proyectos de código abierto que desarrollan y mantienen soporte para otros como Java, Lua, R, etc.

Hay una lista aquí:https://www.postgresql.org/docs/current/external-pl.html

Funciones agregadas

Las funciones agregadas operan sobre un conjunto de valores y devuelven un solo resultado. PostgreSQL tiene un montón de funciones agregadas integradas (vea una lista completa aquí). Por ejemplo, para obtener la desviación estándar de la población de todos los valores en una columna, debe puede:

SELECT stddev_pop(grade) FROM students;

Puede definir sus propias funciones agregadas que se comportan de manera similar. Un agregado definido por el usuario se ensambla a partir de unas pocas funciones independientes individuales que funcionan en el estado interno (por ejemplo, el estado interno de un agregado que calcula el promedio podría ser variables de "suma" y "recuento").

Aquí hay un agregado definido por el usuario que calcula la mediana de un conjunto de valores:

-- from https://wiki.postgresql.org/wiki/Aggregate_Median
CREATE OR REPLACE FUNCTION _final_median(NUMERIC[])
   RETURNS NUMERIC AS
$$
   SELECT AVG(val)
   FROM (
     SELECT val
     FROM unnest($1) val
     ORDER BY 1
     LIMIT  2 - MOD(array_upper($1, 1), 2)
     OFFSET CEIL(array_upper($1, 1) / 2.0) - 1
   ) sub;
$$
LANGUAGE 'sql' IMMUTABLE;
 
CREATE AGGREGATE median(NUMERIC) (
  SFUNC=array_append,
  STYPE=NUMERIC[],
  FINALFUNC=_final_median,
  INITCOND='{}'
);

que se puede invocar como:

SELECT median(grade) FROM students;

Para obtener más información, consulte los documentos sobre agregados y la instrucción CREATE AGGREGATE.

Tipos definidos por el usuario

Las bibliotecas compartidas escritas en C que vimos anteriormente no solo pueden definir funciones, sino también tipos de datos. Estos tipos definidos por el usuario se pueden usar como tipos de datos para columnas al igual que los tipos integrados. Puede definir funciones para trabajar con los valores de sus tipos definidos por el usuario.

Se necesita un poco de código para definir un nuevo tipo. Consulte los documentos aquí que lo guiarán a través de la creación de un nuevo tipo para representar números complejos. La fuente de Postgres también contiene un código tutorial para esto.

Operadores

Los operadores facilitan el uso de las funciones (por ejemplo, escribir 1 + 2 en lugar de sum(1, 2) ), y puede definir operadores para tipos definidos por el usuario utilizando la sentencia CREATE OPERATOR.

Aquí hay un ejemplo para crear un + operador que se asigna a la función complex_add que suma dos complex números:

CREATE OPERATOR + (
    leftarg = complex,
    rightarg = complex,
    function = complex_add,
    commutator = +
);

Más información aquí y aquí.

Clases de operadores y familias de operadores

Las clases de operador permiten que su tipo de datos funcione con el B-Tree incorporado y otros métodos de indexación. Por ejemplo, si desea crear un índice B-Tree en una columna de tipo "complejo", deberá indicarle a Postgres cómo comparar dos valores de este tipo para determinar si uno es menor, igual o mayor que el otro.

Sería bueno que los tipos complejos se compararan con números enteros o valores de punto flotante, que es donde entran las familias de operadores.

Puede leer todo sobre clases y familias de operadores aquí.

Desencadenadores

Los disparadores son un mecanismo poderoso para crear efectos secundarios para las operaciones normales, aunque pueden ser peligrosos si se usan en exceso o se abusa de ellos. Esencialmente, los disparadores conectan eventos a funciones. La función a la que se hace referencia se puede invocar:

  • antes o después de la inserción/actualización/eliminación de una fila de una tabla
  • en truncado de una tabla
  • en lugar de insertar/actualizar/eliminar una fila de una vista

La función se puede invocar para cada fila afectada por una declaración, o una vez por declaración. Y hay aún más cosas, como la cascada de activadores, que se explican aquí.

Las funciones de activación se pueden escribir en C o en cualquiera de las funciones de PL, pero no en SQL. Aquí hay un ejemplo para insertar una fila en una auditoría tabla, por cada actualización hecha al precio de un elemento .

-- first create the function
CREATE FUNCTION log_update() RETURNS TRIGGER AS $$
    INSERT INTO audit (event, new_price, at, item)
      VALUES ('price changed', NEW.price, now(), OLD.item);
$$
LANGUAGE plpgsql;

-- then create the trigger
CREATE TRIGGER audit_price_changes
    AFTER UPDATE ON items
    FOR EACH ROW
    WHEN (OLD.price IS DISTINCT FROM NEW.price)
    EXECUTE FUNCTION log_update();

Lea todo sobre los disparadores aquí, junto con la documentación CREATE TRIGGER.

Activadores de eventos

Mientras que dispara responder a eventos DML en una sola tabla, desencadenadores de eventos puede responder a eventos DDL en una base de datos particular. Los eventos incluyen la creación, modificación y eliminación de una variedad de objetos, como tablas, índices, esquemas, vistas, funciones, tipos, operadores, etc.

Aquí hay un disparador de eventos que evita la eliminación de objetos del esquema de "auditoría":

-- create function first
CREATE FUNCTION nodrop() RETURNS event_trigger LANGUAGE plpgsql AS $$
BEGIN
    IF EXISTS(
      SELECT 1
      FROM pg_event_trigger_dropped_objects() AS T
      WHERE T.schema_name = 'audit')
    THEN
      RAISE EXCEPTION 'not allowed to drop objects in audit schema';
    END IF;
END $$;

-- create event trigger
CREATE EVENT TRIGGER trigger_nodrop
    ON sql_drop
    EXECUTE FUNCTION nodrop();

Puede encontrar más información aquí y en la documentación CREATE EVENT TRIGGER.

Reglas

PostgreSQL viene con una función que le permite reescribir consultas antes de que lleguen al planificador de consultas. La operación es algo similar a configurar Nginx o Apache para reescribir una URL entrante antes de procesarla.

Aquí hay dos ejemplos que afectan las instrucciones INSERT en una tabla y hacen que hagan otra cosa:

-- make inserts into "items" table a no-op
CREATE RULE rule1 AS ON INSERT TO items DO INSTEAD NOTHING;

-- make inserts go elsewhere
CREATE RULE rule2 AS ON INSERT TO items DO INSTEAD
    INSERT INTO items_pending_review VALUES (NEW.name, NEW.price);

Este capítulo de la documentación tiene más información sobre las reglas.

Procedimientos almacenados

A partir de Postgres 11, es posible crear procedimientos almacenados también. En comparación con las funciones almacenadas, solo hay una cosa adicional que los procedimientos pueden hacer:el control de transacciones.

Aquí hay un ejemplo:

CREATE PROCEDURE check_commit(v integer)
LANGUAGE plpgsql AS $$
BEGIN
    IF v % 2 = 0 THEN
        COMMIT;
    ELSE
        ROLLBACK;
    END IF;
END $$;

-- call it
CALL check_commit(10);

Consulte aquí y aquí para obtener más información.

Otras cosas exoticas

Envolturas de datos extranjeros

Los contenedores de datos externos (FDW) le permiten hablar con otras fuentes de datos, como otro servidor Postgres, MySQL, Oracle, Cassandra y más. Toda la lógica para acceder al servidor externo está escrita en C, como una biblioteca compartida.

Incluso hay una tienda en columnas llamada cstore_fdw basada en FDW.

Puede encontrar una lista de implementación de FDW en Postgres Wiki y más documentación aquí.

Métodos de acceso al índice

PostgreSQL viene con tipos de índice como B-Tree, hash, GIN y más. Es posible escribir su propio tipo de índice similar a este, como una biblioteca compartida de C. Más detalles aquí.

Métodos de acceso a tablas

Con el próximo PostgreSQL 12, será posible crear su propia estructura de almacenamiento de datos. Mediante la implementación de la interfaz descrita aquí, puede almacenar la tupla de datos físicamente en el disco de la manera que elija.

Complementos de replicación lógica

En PostgreSQL, la replicación lógica se implementa mediante una "descodificación" del contenido del registro de escritura anticipada (WAL) en un formato arbitrario (como texto SQL o json) y se publica para los suscriptores a través de las ranuras de replicación. Esta decodificación se realiza a través del complemento de salida de decodificación lógica , que se puede implementar como una biblioteca compartida de C como se describe aquí. La biblioteca compartida "test_decoding" es uno de esos complementos, y puede crear la suya propia.

Controlador de lenguaje procedimental

También puede agregar soporte para su lenguaje de programación favorito como Postgres PL creando un controlador, nuevamente como una biblioteca compartida de C. ¡Empiece aquí para crear PL/Go o PL/Rust!

Extensiones

Las extensiones son la forma de gestión de paquetes de Postgres. Digamos que tiene una función C que hace algo útil, y un par de sentencias SQL que hacen las sentencias "CREATE FUNCTION" necesarias para configurarla. Puede agruparlos como una "extensión" que Postgres puede instalar (y desinstalar) en un solo paso (llamando a "CREAR EXTENSIÓN"). Cuando publica una nueva versión, también puede incluir pasos de actualización en la extensión.

Si bien no es una programación del lado del servidor en sí, las extensiones son la forma estándar y muy eficiente de empaquetar y distribuir su código del lado del servidor.

Puede encontrar más información sobre las extensiones aquí y aquí.