36.17. Empaquetar objetos relacionados en una extensión #

36.17.1. Archivos de extensión
36.17.2. Reubicación de extensiones
36.17.3. Tablas de configuración de extensiones
36.17.4. Actualizaciones de extensiones
36.17.5. Instalación de extensiones utilizando scripts de actualización
36.17.6. Consideraciones de seguridad para extensiones
36.17.7. Ejemplo de extensión

Una extensión útil para PostgreSQL típicamente incluye múltiples objetos SQL; por ejemplo, un nuevo tipo de datos requerirá nuevas funciones, nuevos operadores y probablemente nuevas clases de operadores de índices. Es útil reunir todos estos objetos en un solo paquete para simplificar la gestión de la base de datos. PostgreSQL llama a dicho paquete una extensión. Para definir una extensión, necesitas al menos un archivo de script que contenga los comandos SQL para crear los objetos de la extensión, y un archivo de control que especifique algunas propiedades básicas de la propia extensión. Si la extensión incluye código en C, normalmente también habrá un archivo de biblioteca compartida en el que se ha compilado el código en C. Una vez que tienes estos archivos, un simple comando CREATE EXTENSION carga los objetos en tu base de datos.

La ventaja principal de utilizar una extensión, en lugar de simplemente ejecutar el script SQL para cargar un montón de objetos sueltos en tu base de datos, es que entonces PostgreSQL entenderá que los objetos de la extensión van juntos. Puedes eliminar todos los objetos con un único comando DROP EXTENSION (sin necesidad de mantener un script de desinstalación separado). Aún más útil, pg_dump sabe que no debe volcar los objetos miembros individuales de la extensión; en su lugar, simplemente incluirá un comando CREATE EXTENSION en los volcados. Esto simplifica enormemente la migración a una nueva versión de la extensión que podría contener más objetos o diferentes a la versión antigua. Ten en cuenta, sin embargo, que debes tener disponibles los archivos de control, script y otros archivos de la extensión al cargar dicho volcado en una nueva base de datos.

PostgreSQL no te permitirá eliminar un objeto individual contenido en una extensión, excepto eliminando toda la extensión. Además, aunque puedes cambiar la definición de un objeto miembro de la extensión (por ejemplo, a través de CREATE OR REPLACE FUNCTION para una función), ten en cuenta que la definición modificada no será volcada por pg_dump. Dicho cambio normalmente solo tiene sentido si realizas simultáneamente el mismo cambio en el archivo de script de la extensión. (Pero existen disposiciones especiales para tablas que contienen datos de configuración; consulta la sección Section 36.17.3). En situaciones de producción, generalmente es mejor crear un script de actualización de extensión para realizar cambios en los objetivos miembros de la extensión.

El script de la extensión puede establecer privilegios sobre los objetos que forman parte de la extensión, utilizando sentencias GRANT y REVOKE. El conjunto final de privilegios para cada objeto (si se establece alguno) se almacenará en el catálogo del sistema pg_init_privs. Cuando se utiliza pg_dump, el comando CREATE EXTENSION se incluirá en el volcado, seguido de las sentencias GRANT y REVOKE necesarias para establecer los privilegios en los objetos a como estaban en el momento en que se realizó el volcado.

PostgreSQL no admite actualmente que los scripts de extensión emitan sentencias CREATE POLICY o SECURITY LABEL. Se espera que se configuren después de que se haya creado la extensión. Todas las políticas RLS y etiquetas de seguridad en los objetos de la extensión se incluirán en los volcados creados por pg_dump.

El mecanismo de extensión también tiene disposiciones para empaquetar scripts de modificación que ajustan las definiciones de los objetos SQL contenidos en una extensión. Por ejemplo, si la versión 1.1 de una extensión añade una función y cambia el cuerpo de otra función en comparación con la 1.0, el autor de la extensión puede proporcionar un script de actualización que realiza solo esos dos cambios. El comando ALTER EXTENSION UPDATE se puede utilizar entonces para aplicar estos cambios y realizar un seguimiento de qué versión de la extensión está instalada realmente en una base de datos determinada.

Los tipos de objetos SQL que pueden ser miembros de una extensión se muestran en la descripción de ALTER EXTENSION. En particular, los objetos que son a nivel de todo el clúster de la base de datos, como bases de datos, roles y tablespaces, no pueden ser miembros de una extensión dado que una extensión solo se conoce dentro de una base de datos. (Aunque no se prohíbe que un script de extensión cree tales objetos, si lo hace no se rastrearán como parte de la extensión). También ten en cuenta que aunque una tabla puede ser miembro de una extensión, sus objetos subsidiarios, como los índices, no se consideran miembros directos de la extensión. Otro punto importante es que los esquemas pueden pertenecer a extensiones, pero no al revés: una extensión como tal tiene un nombre no cualificado y no existe dentro de ningún esquema. Sin embargo, los objetos miembros de la extensión pertenecerán a esquemas siempre que sea apropiado para sus tipos de objetos. Puede ser o no apropiado que una extensión sea propietaria de los esquemas en los que se encuentran sus objetos miembros.

Si el script de una extensión crea objetos temporales (como tablas temporales), esos objetos se tratan como miembros de la extensión durante el resto de la sesión actual, pero se eliminan automáticamente al final de la sesión, como ocurriría con cualquier objeto temporal. Esta es una excepción a la regla de que los objetos miembros de la extensión no se pueden eliminar sin eliminar toda la extensión.

36.17.1. Archivos de extensión #

El comando CREATE EXTENSION se basa en un archivo de control para cada extensión, que debe llamarse igual que la extensión con el sufijo .control, y debe colocarse en el directorio SHAREDIR/extension de la instalación. También debe haber al menos un archivo de script SQL, que sigue el patrón de nombres extension--version.sql (por ejemplo, foo--1.0.sql para la versión 1.0 de la extensión foo). Por defecto, los archivos de script también se colocan en el directorio SHAREDIR/extension; pero el archivo de control puede especificar un directorio diferente para los archivos de script.

Se pueden configurar ubicaciones adicionales para los archivos de control de extensiones utilizando el parámetro extension_control_path.

El formato de archivo para un archivo de control de extensión es el mismo que para el archivo postgresql.conf, es decir, una lista de asignaciones parameter_name = value, una por línea. Se permiten líneas en blanco y comentarios introducidos por #. Asegúrate de poner entre comillas cualquier valor que no sea una sola palabra o número.

Un archivo de control puede configurar los siguientes parámetros:

directory (string) #

El directorio que contiene los archivos de script SQL de la extensión. A menos que se proporcione una ruta absoluta, el nombre es relativo al directorio donde se encontró el archivo de control. Por defecto, los archivos de script se buscan en el mismo directorio donde se encontró el archivo de control.

default_version (string) #

La versión por defecto de la extensión (la que se instalará si no se especifica ninguna versión en CREATE EXTENSION). Aunque esto se puede omitir, provocará que CREATE EXTENSION falle si no aparece la opción VERSION, por lo que generalmente no querrás hacer eso.

comment (string) #

Un comentario (cualquier cadena) sobre la extensión. El comentario se aplica al crear inicialmente una extensión, pero no durante las actualizaciones de la extensión (dado que eso podría anular los comentarios añadidos por el usuario). Alternativamente, el comentario de la extensión se puede establecer escribiendo un comando COMMENT en el archivo de script.

encoding (string) #

La codificación del conjunto de caracteres utilizada por los archivos de script. Esto debe especificarse si los archivos de script contienen caracteres no ASCII. De lo contrario, se asumirá que los archivos están en la codificación de la base de datos.

module_pathname (string) #

El valor de este parámetro se sustituirá por cada aparición de MODULE_PATHNAME en los archivos de script. Si no está configurado, no se realiza ninguna sustitución. Típicamente, esto se configura simplemente como shared_library_name y luego se utiliza MODULE_PATHNAME en los comandos CREATE FUNCTION para funciones en lenguaje C, de modo que los archivos de script no necesiten tener cableado el nombre de la biblioteca compartida.

requires (string) #

Una lista de nombres de extensiones de las que depende esta extensión, por ejemplo requires = 'foo, bar'. Esas extensiones deben estar instaladas antes de que esta se pueda instalar.

no_relocate (string) #

Una lista de nombres de extensiones de las que depende esta extensión a las que se les debe prohibir cambiar sus esquemas a través de ALTER EXTENSION ... SET SCHEMA. Esto es necesario si el script de esta extensión hace referencia al nombre del esquema de una extensión requerida (utilizando la sintaxis @extschema:name@) de una manera que no puede rastrear los cambios de nombre.

superuser (boolean) #

Si este parámetro es true (que es el valor por defecto), solo los superusuarios pueden crear la extensión o actualizarla a una nueva versión (pero consulta también trusted a continuación). Si se configura como false, solo se requieren los privilegios necesarios para ejecutar los comandos en el script de instalación o actualización. Esto normalmente debería configurarse como true si alguno de los comandos del script requiere privilegios de superuser. (Tales comandos fallarían de todos modos, pero es más amigable para el usuario dar el error de antemano).

trusted (boolean) #

Este parámetro, si se configura como true (que no es el valor por defecto), permite a algunos no superusuarios instalar una extensión que tiene superuser configurado como true. Específicamente, la instalación estará permitida para cualquiera que tenga el privilegio CREATE en la base de datos actual. Cuando el usuario que ejecuta CREATE EXTENSION no es un superusuario pero se le permite instalar por virtud de este parámetro, entonces el script de instalación o actualización se ejecuta como el superusuario de arranque, no como el usuario llamador. Este parámetro es irrelevante si superuser es false. Generalmente, esto no debería configurarse como true para extensiones que podrían permitir el acceso a capacidades que de otro modo serían exclusivas de superusuario, como el acceso al sistema de archivos. Además, marcar una extensión como confiable requiere un esfuerzo adicional significativo para escribir los scripts de instalación y actualización de la extensión de forma segura; consulta la sección Section 36.17.6.

relocatable (boolean) #

Una extensión es reubicable (relocatable) si es posible mover sus objetos contenidos a un esquema diferente después de la creación inicial de la extensión. El valor por defecto es false, es decir, la extensión no es reubicable. Consulta la sección Section 36.17.2 para obtener más información.

schema (string) #

Este parámetro solo se puede configurar para extensiones no reubicables. Fuerza a que la extensión se cargue exactamente en el esquema nombrado y no en otro. El parámetro schema se consulta solo al crear inicialmente una extensión, no durante las actualizaciones de la misma. Consulta la sección Section 36.17.2 para obtener más información.

Además del archivo de control principal extension.control, una extensión puede tener archivos de control secundarios nombrados al estilo extension--version.control. Si se proporcionan, deben ubicarse en el directorio del archivo de script. Los archivos de control secundarios siguen el mismo formato que el archivo de control principal. Cualquier parámetro configurado en un archivo de control secundario anula al archivo de control principal al instalar o actualizar a esa versión de la extensión. Sin embargo, los parámetros directory y default_version no se pueden configurar en un archivo de control secundario.

Los archivos de script SQL de una extensión pueden contener cualquier comando SQL, excepto comandos de control de transacciones (BEGIN, COMMIT, etc.) y comandos que no se pueden ejecutar dentro de un bloque de transacción (como VACUUM). Esto se debe a que los archivos de script se ejecutan implícitamente dentro de un bloque de transacción.

Los archivos de script SQL de una extensión también pueden contener líneas que comiencen con \echo, las cuales serán ignoradas (tratadas como comentarios) por el mecanismo de extensión. Esta disposición se utiliza comúnmente para lanzar un error si el archivo de script se alimenta a psql en lugar de cargarse a través de CREATE EXTENSION (ver el script de ejemplo en Section 36.17.7). Sin eso, los usuarios podrían cargar accidentalmente el contenido de la extensión como objetos sueltos en lugar de como una extensión, una situación de la que es un poco tedioso recuperarse.

Si el script de la extensión contiene la cadena @extowner@, esa cadena se reemplaza por el nombre (debidamente citado) del usuario que llama a CREATE EXTENSION o ALTER EXTENSION. Típicamente, esta característica es utilizada por extensiones que están marcadas como confiables para asignar la propiedad de los objetos seleccionados al usuario llamador en lugar de al superusuario de arranque. (Sin embargo, se debe tener cuidado al hacerlo. Por ejemplo, asignar la propiedad de una función en lenguaje C a un no superusuario crearía una ruta de escalada de privilegios para ese usuario).

Aunque los archivos de script pueden contener cualquier carácter permitido por la codificación especificada, los archivos de control solo deben contener ASCII simple, porque no hay forma de que PostgreSQL sepa en qué codificación está un archivo de control. En la práctica, esto solo es un problema si deseas utilizar caracteres no ASCII en el comentario de la extensión. La práctica recomendada en ese caso es no utilizar el parámetro comment del archivo de control, sino utilizar COMMENT ON EXTENSION dentro de un archivo de script para establecer el comentario.

36.17.2. Reubicación de extensiones #

Los usuarios a menudo desean cargar los objetos contenidos en una extensión en un esquema diferente al que el autor de la extensión tenía en mente. Existen tres niveles de reubicación admitidos:

  • Una extensión totalmente reubicable se puede mover a otro esquema en cualquier momento, incluso después de haber sido cargada en una base de datos. Esto se realiza con el comando ALTER EXTENSION SET SCHEMA, que renombra automáticamente todos los objetos miembros al nuevo esquema. Normalmente, esto solo es posible si la extensión no contiene suposiciones internas sobre en qué esquema se encuentra alguno de sus objetos. Además, los objetos de la extensión deben estar todos en un esquema para empezar (ignorando los objetos que no pertenecen a ningún esquema, como los lenguajes procedimentales). Marca una extensión totalmente reubicable configurando relocatable = true en su archivo de control.

  • Una extensión podría ser reubicable durante la instalación pero no después. Este es típicamente el caso si el archivo de script de la extensión necesita hacer referencia al esquema de destino explícitamente, por ejemplo al establecer propiedades de search_path para funciones SQL. Para tal extensión, configura relocatable = false en su archivo de control, y utiliza @extschema@ para referirse al esquema de destino en el archivo de script. Todas las apariciones de esta cadena se reemplazarán por el nombre real del esquema de destino (entre comillas dobles si es necesario) antes de que se ejecute el script. El usuario puede establecer el esquema de destino utilizando la opción SCHEMA de CREATE EXTENSION.

  • Si la extensión no admite la reubicación en absoluto, configura relocatable = false en su archivo de control, y también establece schema con el nombre del esquema de destino previsto. Esto evitará el uso de la opción SCHEMA de CREATE EXTENSION, a menos que especifique el mismo esquema nombrado en el archivo de control. Esta opción es típicamente necesaria si la extensión contiene suposiciones internas sobre su nombre de esquema que no se pueden reemplazar por usos de @extschema@. El mecanismo de sustitución @extschema@ también está disponible en este caso, aunque es de uso limitado dado que el nombre del esquema está determinado por el archivo de control.

En todos los casos, el archivo de script se ejecutará con search_path inicialmente configurado para apuntar al esquema de destino; es decir, CREATE EXTENSION hace lo equivalente a esto:

SET LOCAL search_path TO @extschema@, pg_temp;

Esto permite que los objetos creados por el archivo de script vayan al esquema de destino. El archivo de script puede cambiar el search_path si lo desea, pero eso generalmente es indeseable. El search_path se restaura a su configuración anterior al finalizar CREATE EXTENSION.

El esquema de destino está determinado por el parámetro schema en el archivo de control si se proporciona, de lo contrario por la opción SCHEMA de CREATE EXTENSION si se proporciona, de lo contrario por el esquema de creación de objetos predeterminado actual (el primero en el search_path del llamador). Cuando se utiliza el parámetro schema del archivo de control, el esquema de destino se creará si no existe ya, pero en los otros dos casos ya debe existir.

Si se enumeran extensiones prerrequisito en requires en el archivo de control, sus esquemas de destino se añaden a la configuración inicial de search_path, después del esquema de destino de la nueva extensión. Esto permite que sus objetos sean visibles para el archivo de script de la nueva extensión.

Por seguridad, pg_temp se añade automáticamente al final de search_path en todos los casos.

Aunque una extensión no reubicable puede contener objetos distribuidos en múltiples esquemas, normalmente es deseable colocar todos los objetos destinados al uso externo en un solo esquema, que se considera el esquema de destino de la extensión. Tal disposición funciona convenientemente con la configuración predeterminada de search_path durante la creación de extensiones dependientes.

Si una extensión hace referencia a objetos que pertenecen a otra extensión, se recomienda calificar con el esquema esas referencias. Para hacerlo, escribe @extschema:name@ in el archivo de script de la extensión, donde name es el nombre de la otra extensión (que debe figurar en la lista requires de esta extensión). Esta cadena se reemplazará por el nombre (entre comillas dobles si es necesario) del esquema de destino de esa extensión. Aunque esta notación evita la necesidad de hacer suposiciones cableadas sobre los nombres de los esquemas en el archivo de script de la extensión, su uso puede incrustar el nombre del esquema de la otra extensión en los objetos instalados de esta extensión. (Típicamente, eso sucede cuando @extschema:name@ se utiliza dentro de un literal de cadena, como el cuerpo de una función o una configuración de search_path. En otros casos, la referencia al objeto se reduce a un OID durante el análisis y no requiere búsquedas posteriores). Si el nombre del esquema de la otra extensión está así incrustado, debes evitar que la otra extensión se reubique después de instalar la tuya, añadiendo el nombre de la otra extensión a la lista no_relocate de esta.

36.17.3. Tablas de configuración de extensiones #

Algunas extensiones incluyen tablas de configuración, que contienen datos que el usuario podría añadir o cambiar después de la instalación de la extensión. Normalmente, si una tabla es parte de una extensión, ni la definición de la tabla ni su contenido serán volcados por pg_dump. Pero ese comportamiento es indeseable para una tabla de configuración; cualquier cambio de datos realizado por el usuario debe incluirse en los volcados, o la extensión se comportará de manera diferente después de un volcado y restauración.

Para resolver este problema, el archivo de script de una extensión puede marcar una tabla o una secuencia que ha creado como una relación de configuración, lo que hará que pg_dump incluya el contenido de la tabla o de la secuencia (no su definición) en los volcados. Para hacer eso, llama a la función pg_extension_config_dump(regclass, text) después de crear la tabla o la secuencia, por ejemplo:

CREATE TABLE my_config (key text, value text);
CREATE SEQUENCE my_config_seq;

SELECT pg_catalog.pg_extension_config_dump('my_config', '');
SELECT pg_catalog.pg_extension_config_dump('my_config_seq', '');

Se puede marcar cualquier número de tablas o secuencias de esta manera. Las secuencias asociadas con columnas serial o bigserial también se pueden marcar.

Cuando el segundo argumento de pg_extension_config_dump es una cadena vacía, todo el contenido de la tabla es volcado por pg_dump. Esto normalmente solo es correcto si la tabla está inicialmente vacía tal como la creó el script de la extensión. Si hay una mezcla de datos iniciales y datos proporcionados por el usuario en la tabla, el segundo argumento de pg_extension_config_dump proporciona una condición WHERE que selecciona los datos a volcar. Por ejemplo, podrías hacer:

CREATE TABLE my_config (key text, value text, standard_entry boolean);

SELECT pg_catalog.pg_extension_config_dump('my_config', 'WHERE NOT standard_entry');

y luego asegurarte de que standard_entry sea true solo en las filas creadas por el script de la extensión.

Para las secuencias, el segundo argumento de pg_extension_config_dump no tiene efecto.

Las situaciones más complicadas, como las filas proporcionadas inicialmente que podrían ser modificadas por los usuarios, se pueden manejar creando disparadores (triggers) en la tabla de configuración para asegurar que las filas modificadas se marquen correctamente.

Puedes alterar la condición de filtro asociada con una tabla de configuración llamando a pg_extension_config_dump nuevamente. (Esto normalmente sería útil en un script de actualización de extensión). La única forma de marcar una tabla como que ya no es una tabla de configuración es desasociarla de la extensión con ALTER EXTENSION ... DROP TABLE.

Ten en cuenta que las relaciones de clave externa entre estas tablas dictarán el orden en el que pg_dump las volcará. Específicamente, pg_dump intentará volcar la tabla referenciada antes que la tabla que la referencia. Como las relaciones de clave externa se configuran en el momento de CREATE EXTENSION (antes de que se carguen los datos en las tablas), no se admiten dependencias circulares. Cuando existen dependencias circulares, los datos se seguirán volcando pero el volcado no se podrá restaurar directamente y se requerirá la intervención del usuario.

Las secuencias asociadas con columnas serial o bigserial deben marcarse directamente para volcar su estado. Marcar su relación padre no es suficiente para este propósito.

36.17.4. Actualizaciones de extensiones #

Una ventaja del mecanismo de extensión es que proporciona formas convenientes de gestionar las actualizaciones de los comandos SQL que definen los objetos de una extensión. Esto se realiza asociando un nombre o número de versión con cada versión lanzada del script de instalación de la extensión. Además, si deseas que los usuarios puedan actualizar sus bases de datos dinámicamente de una versión a la siguiente, debes proporcionar scripts de actualización que realicen los cambios necesarios para pasar de una versión a la siguiente. Los scripts de actualización tienen nombres que siguen el patrón extension--old_version--target_version.sql (por ejemplo, foo--1.0--1.1.sql contiene los comandos para modificar la versión 1.0 de la extensión foo a la versión 1.1).

Dado que está disponible un script de actualización adecuado, el comando ALTER EXTENSION UPDATE actualizará una extensión instalada a la nueva versión especificada. El script de actualización se ejecuta en el mismo entorno que proporciona CREATE EXTENSION para los scripts de instalación: en particular, el search_path se configura de la misma manera, y cualquier objeto nuevo creado por el script se añade automáticamente a la extensión. Además, si el script decide eliminar objetos miembros de la extensión, estos se desasocian automáticamente de la extensión.

Si una extensión tiene archivos de control secundarios, los parámetros de control que se utilizan para un script de actualización son los asociados con la versión de destino (nueva) del script.

ALTER EXTENSION puede ejecutar secuencias de archivos de scripts de actualización para lograr una actualización solicitada. Por ejemplo, si solo están disponibles foo--1.0--1.1.sql y foo--1.1--2.0.sql, ALTER EXTENSION los aplicará en secuencia si se solicita una actualización a la versión 2.0 cuando 1.0 está instalada actualmente.

PostgreSQL no asume nada sobre las propiedades de los nombres de las versiones: por ejemplo, no sabe si 1.1 sigue a 1.0. Simplemente hace coincidir los nombres de versión disponibles y sigue la ruta que requiere aplicar el menor número de scripts de actualización. (Un nombre de versión puede ser en realidad cualquier cadena que no contenga -- ni un - inicial o final).

A veces es útil proporcionar scripts de retroceso (downgrade), por ejemplo foo--1.1--1.0.sql para permitir revertir los cambios asociados con la versión 1.1. Si haces eso, ten cuidado con la posibilidad de que se aplique inesperadamente un script de retroceso porque produce una ruta más corta. El caso de riesgo es donde existe un script de actualización de ruta rápida que salta varias versiones, así como un script de retroceso al punto de inicio de la ruta rápida. Podría requerir menos pasos aplicar el retroceso y luego la ruta rápida que avanzar una versión a la vez. Si el script de retroceso elimina objetos irremplazables, esto producirá resultados indeseables.

Para verificar rutas de actualización inesperadas, utiliza este comando:

SELECT * FROM pg_extension_update_paths('extension_name');

Esto muestra cada par de nombres de versión conocidos distintos para la extensión especificada, junto con la secuencia de la ruta de actualización que se tomaría para ir de la versión de origen a la versión de destino, o NULL si no hay una ruta de actualización disponible. La ruta se muestra en formato de texto con separadores --. Puedes utilizar regexp_split_to_array(path,'--') si prefieres un formato de array.

36.17.5. Instalación de extensiones utilizando scripts de actualización #

Una extensión que ha existido durante algún tiempo probablemente existirá en varias versiones, para las cuales el autor necesitará escribir scripts de actualización. Por ejemplo, si has lanzado una extensión foo en las versiones 1.0, 1.1 y 1.2, debería haber scripts de actualización foo--1.0--1.1.sql y foo--1.1--1.2.sql. Antes de PostgreSQL 10, era necesario crear también nuevos archivos de script foo--1.1.sql y foo--1.2.sql que construyeran directamente las versiones más recientes de la extensión, o de lo contrario las versiones más recientes no se podían instalar directamente, sino únicamente instalando la 1.0 y luego actualizando. Eso era tedioso y duplicado, pero ahora es innecesario, porque CREATE EXTENSION puede seguir las cadenas de actualización automáticamente. Por ejemplo, si solo están disponibles los archivos de script foo--1.0.sql, foo--1.0--1.1.sql y foo--1.1--1.2.sql, entonces una solicitud para instalar la versión 1.2 se cumple ejecutando esos tres scripts en secuencia. El procesamiento es el mismo que si hubieras instalado primero la 1.0 y luego hubieras actualizado a la 1.2. (Al igual que con ALTER EXTENSION UPDATE, si hay múltiples rutas disponibles, se prefiere la más corta). Organizar los archivos de script de una extensión de este estilo puede reducir la cantidad de esfuerzo de mantenimiento necesario para producir pequeñas actualizaciones.

Si utilizas archivos de control secundarios (específicos de la versión) con una extensión mantenida en este estilo, ten en cuenta que cada versión necesita un archivo de control incluso si no tiene un script de instalación independiente, ya que ese archivo de control determinará cómo se realiza la actualización implícita a esa versión. Por ejemplo, si foo--1.0.control especifica requires = 'bar' pero los otros archivos de control de foo no lo hacen, la dependencia de la extensión sobre bar se eliminará al actualizar de la 1.0 a otra versión.

36.17.6. Consideraciones de seguridad para extensiones #

Las extensiones ampliamente distribuidas deben asumir poco sobre la base de datos que ocupan. Por lo tanto, es apropiado escribir las funciones proporcionadas por una extensión en un estilo seguro que no pueda ser comprometido por ataques basados en search-path.

Una extensión que tiene la propiedad superuser establecida en true también debe considerar los riesgos de seguridad para las acciones realizadas dentro de sus scripts de instalación y actualización. No es terriblemente difícil para un usuario malicioso crear objetos caballo de Troya que comprometerán la ejecución posterior de un script de extensión escrito descuidadamente, permitiendo a ese usuario adquirir privilegios de superusuario.

Si una extensión está marcada como trusted, entonces su esquema de instalación puede ser seleccionado por el usuario instalador, quien podría utilizar intencionadamente un esquema inseguro con la esperanza de obtener privilegios de superusuario. Por lo tanto, una extensión de confianza está extremadamente expuesta desde el punto de vista de la seguridad, y todos los comandos de sus scripts deben ser examinados cuidadosamente para asegurar que ningún compromiso sea posible.

Los consejos sobre cómo escribir funciones de forma segura se proporcionan en la sección Section 36.17.6.1 a continuación, y los consejos sobre cómo escribir scripts de instalación de forma segura se proporcionan en la sección Section 36.17.6.2.

36.17.6.1. Consideraciones de seguridad para funciones de extensión #

Las funciones en lenguaje SQL y PL proporcionadas por las extensiones corren el riesgo de sufrir ataques basados en search-path cuando se ejecutan, dado que el análisis de estas funciones ocurre en el momento de la ejecución, no en el de la creación.

La página de referencia de CREATE FUNCTION contiene consejos sobre cómo escribir funciones SECURITY DEFINER de forma segura. Es una buena práctica aplicar esas técnicas para cualquier función proporcionada por una extensión, ya que la función podría ser llamada por un usuario con altos privilegios.

Si no puedes configurar el search_path para que contenga únicamente esquemas seguros, asume que cada nombre no cualificado podría resolverse a un objeto que un usuario malicioso ha definido. Ten cuidado con las construcciones que dependen implícitamente de search_path; por ejemplo, IN y CASE expression WHEN siempre seleccionan un operador utilizando el search path. En su lugar, utiliza OPERATOR(schema.=) ANY y CASE WHEN expression.

Una extensión de propósito general normalmente no debería asumir que ha sido instalada en un esquema seguro, lo que significa que incluso las referencias cualificadas por esquema a sus propios objetos no están completamente libres de riesgo. Por ejemplo, si la extensión ha definido una función myschema.myfunc(bigint), entonces una llamada como myschema.myfunc(42) podría ser capturada por una función hostil myschema.myfunc(integer). Ten cuidado de que los tipos de datos de los parámetros de funciones y operadores coincidan exactamente con los tipos de argumentos declarados, utilizando conversiones explícitas donde sea necesario.

36.17.6.2. Consideraciones de seguridad para scripts de extensión #

Un script de instalación o actualización de extensión debe escribirse para protegerse contra los ataques basados en search-path que ocurren cuando se ejecuta el script. Si se puede hacer que una referencia a un objeto en el script se resuelva a algún otro objeto diferente al que el autor del script pretendía, entonces podría ocurrir un compromiso de inmediato, o más tarde cuando se utilice el objeto de extensión mal definido.

Los comandos DDL como CREATE FUNCTION y CREATE OPERATOR CLASS son generalmente seguros, pero ten cuidado con cualquier comando que tenga una expresión de propósito general como componente. Por ejemplo, CREATE VIEW necesita ser examinado, al igual que una expresión DEFAULT en CREATE FUNCTION.

A veces, el script de una extensión puede necesitar ejecutar SQL de propósito general, por ejemplo para realizar ajustes de catálogo que no son posibles a través de DDL. Ten cuidado de ejecutar tales comandos con un search_path seguro; no confíes en que la ruta proporcionada por CREATE/ALTER EXTENSION sea segura. La mejor práctica es configurar temporalmente search_path como pg_catalog, pg_temp e insertar referencias al esquema de instalación de la extensión explícitamente donde sea necesario. (Esta práctica también podría ser útil para crear vistas). Se pueden encontrar ejemplos en los módulos contrib en la distribución del código fuente de PostgreSQL.

Las referencias seguras entre extensiones normalmente requieren calificar con el esquema los nombres de los objetos de la otra extensión, utilizando la sintaxis @extschema:name@, además de una coincidencia cuidadosa de los tipos de argumentos para funciones y operadores.

36.17.7. Ejemplo de extensión #

Aquí hay un ejemplo completo de una extensión únicamente de SQL, un tipo compuesto de dos elementos que puede almacenar cualquier tipo de valor en sus ranuras (slots), las cuales se denominan k y v. Los valores que no son de texto se fuerzan automáticamente a texto para su almacenamiento.

El archivo de script pair--1.0.sql se ve así:

-- quejarse si el script se genera en psql, en lugar de a través de CREATE EXTENSION
\echo Use "CREATE EXTENSION pair" to load this file. \quit

CREATE TYPE pair AS ( k text, v text );

CREATE FUNCTION pair(text, text)
RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::@[email protected];';

CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, FUNCTION = pair);

-- "SET search_path" es fácil de hacer bien, pero los nombres cualificados funcionan mejor.
CREATE FUNCTION lower(pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW(lower($1.k), lower($1.v))::@[email protected];'
SET search_path = pg_temp;

CREATE FUNCTION pair_concat(pair, pair)
RETURNS pair LANGUAGE SQL
AS 'SELECT ROW($1.k OPERATOR(pg_catalog.||) $2.k,
               $1.v OPERATOR(pg_catalog.||) $2.v)::@[email protected];';

El archivo de control pair.control se ve así:

# extensión pair
comment = 'A key/value pair data type'
default_version = '1.0'
# no puede ser reubicable debido al uso de @extschema@
relocatable = false

Aunque apenas necesitas un archivo makefile para instalar estos dos archivos en el directorio correcto, podrías utilizar un Makefile que contenga esto:

EXTENSION = pair
DATA = pair--1.0.sql

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

Este makefile se basa en PGXS, que se describe en la sección Section 36.18. El comando make install instalará los archivos de control y de script en el directorio correcto según lo informado por pg_config.

Una vez instalados los archivos, utiliza el comando CREATE EXTENSION para cargar los objetos en cualquier base de datos en particular.