PostgreSQL: programación del lado del servidor en lenguaje humano (PL / Perl, PL / Python, PL / v8)

Postgres es conocido por su extensibilidad, que también se aplica a la compatibilidad con lenguajes de procedimiento (PL). Nadie puede presumir de un idioma con una lista de idiomas de esta longitud, y potencialmente esta lista no está del todo limitada: para conectar el idioma al servidor, no se requiere ningún esfuerzo adicional. Incluso puede crear su propio idioma y convertirlo en un lenguaje de procedimiento del servidor. Las modificaciones en el DBMS no requerirán esto. Como mucho más, esta extensibilidad se ha incorporado a la arquitectura de Postgres desde el principio.

Es posible ya veces necesario escribir idiomas PL para tareas. Mejor aún, si alguien escribe dicho marco para escribir lenguajes para que pueda escribir no en C, sino elegir un lenguaje que sea más cómodo para un desarrollador de lenguaje. Al igual que con FDW, que se puede escribir en Python .

Este artículo fue escrito sobre la base de una serie de informes y clases magistrales sobre este tema realizadas por el autor en las conferencias PgConf.Russia 2019 , PgConf.Russia 2018 y DevConf 2017 .

No se trata de exóticos, sino de los lenguajes de procedimiento más comunes PL / Perl, PL / Python y PL / V8 (es decir, JavaScript) y de comparar sus capacidades con PL / pgSQL.

¿Cuándo vale la pena usar estos idiomas? ¿Cuándo faltan SQL y PL / pgSQL?

  • Luego, cuando necesite trabajar con estructuras complejas, con algoritmos: atravesar árboles, por ejemplo, o cuando se requiera el análisis HTML o XML, especialmente al extraerlos de archivos;
  • Cuando necesita generar dinámicamente SQL complejo (informes, ORM). En PL / pgSQL, no solo es inconveniente, sino que también funcionará más lentamente en algunos casos;
  • Perl Python, C/C++, Perl Python . . , Oracle. , Postgres . Perl Python .
  • — . , , untrusted- ( — . ), Perlu Python(3)u, PL/V8. Postgres , , FDW, , . . !
  • Y una cosa más: si vas a escribir algo en C, entonces puedes hacer un prototipo en estos lenguajes que estén más adaptados al desarrollo rápido.

Cómo incrustar un idioma en Postgres


Para implementar el lenguaje que necesita: escriba en C de una a tres funciones:

  • HANDLER: un manejador de llamadas que ejecutará una función en el idioma (esta es una parte obligatoria);
  • INLINE - controlador de bloque anónimo (si desea que el idioma admita bloques anónimos);
  • VALIDADOR: función de verificación de código al crear una función (si desea que se realice esta verificación).

Esto se describe en detalle en la documentación aquí y aquí .

"Idiomas listos para usar" y otros idiomas


Solo hay cuatro idiomas que son compatibles " listos para usar ": PL / pgSQL , PL / Perl , PL / Python y PL / Tcl , pero el cosquilleo es más bien un tributo a la historia: pocas personas lo usan ahora, ya no hablaremos más.
PL / Perl, PL / Python y, por supuesto, PL / pgSQL son compatibles con la comunidad de Postgres. El soporte para otros idiomas listos para usar recae en sus mantenedores : empresas, comunidades o desarrolladores específicos interesados ​​en hacer que el lenguaje funcione dentro del DBMS. PL / V8 promueve Google. Pero de vez en cuando hay razonesduda del futuro sin nubes de PL / V8. El actual responsable del proyecto PL / V8 de Google, Jerry Sievert, está considerando el soporte JS de Postgres basado en el servidor basado en un motor diferente (como QuickJS), ya que PL / V8 es difícil de construir y requiere 3-5 GB todo tipo de cosas en Linux al compilar, y esto a menudo genera problemas en diferentes sistemas operativos. Pero el PL / V8 es ampliamente utilizado y probado exhaustivamente. Es posible que PL / JS aparezca como una alternativa a otro motor JS, o por ahora solo como un nombre, al que nos acostumbraremos durante el período de transición.

PL / Java rara vez se usa. Personalmente no tenía necesidad de escribir en PL / Java porque en PL / Perl y en PL / V8 hay suficiente funcionalidad para casi todas las tareas. Incluso Python no agrega características particularmente. PL / RÚtil para quienes gustan de las estadísticas y aman este lenguaje. No hablaremos de él aquí tampoco.

Los lenguajes populares no son necesariamente populares con los almacenes de escritura: hay PL / PHP, pero ahora prácticamente no es compatible con nadie; hay pocos que quieran escribir procedimientos de servidor en él. Para el lenguaje PL / Ruby, la imagen es de alguna manera la misma, aunque el lenguaje parece ser más moderno.

Se está desarrollando un lenguaje de procedimiento basado en Go, ver PL / Go y también, al parecer, PL / Lua . Será necesario estudiarlos. Para los fanáticos obstinados de la concha, incluso hay PL / Sh , es difícil incluso imaginar para qué podría ser.

Hay al menos un lenguaje de procedimiento (DSL) específico de dominio que está estrechamente especializado para su tarea: PL / Proxy, que solía ser muy popular para proxy y equilibrar la carga del servidor.

En este artículo, cubriremos los idiomas principales más utilizados. Esto, por supuesto, es PL / PgSQL, PL / Perl, PL / Python y PL / V8, los llamaremos PL / * a continuación .

Los idiomas "listos para usar" se instalan casi de manera literal, por lo general, la instalación es sencilla. Pero para instalar PL / V8, si no encontró un paquete con la versión necesaria en el repositorio de su sistema operativo, esto es casi una hazaña, porque para esto tendrá que construir todo el V8 o, en otras palabras, Chromium. Al mismo tiempo, toda la infraestructura de desarrollo se descargará de google.com junto con el propio V8: cuente con un par de gigabytes de tráfico. Para Postgres 11 en Ubuntu, el paquete PL / V8 aún no ha aparecido, solo V8 para PG 10 está disponible en el repositorio hasta ahora. Si lo desea, armelo a mano. También es importante que la versión que encontrará en el repositorio sea bastante antigua. En el momento de la publicación del artículo, la última versión es 2.3.14.

Después de instalar el idioma en sí, también debe "crear" el idioma: registrarlo en el directorio del sistema. Esto debe ser realizado por el equipo.

CREATE EXTENSION plperl;

(en lugar de plperl, puede sustituir el nombre de otro idioma, hay ciertos matices, ver más abajo).
Nos fijamos en lo que sucedió:

test_langs=# \x
test_langs=# \dL+
List of languages
-[ RECORD 1 ]-----+---------------------------------
Name              | plperl
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plperl_call_handler()
Validator         | plperl_validator(oid)
Inline handler    | plperl_inline_handler(internal)
Access privileges |
Description       | PL/Perl procedural language
-[ RECORD 2 ]-----+---------------------------------
Name              | plpgsql
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plpgsql_call_handler()
Validator         | plpgsql_validator(oid)
Inline handler    | plpgsql_inline_handler(internal)
Access privileges |
Description       | PL/pgSQL procedural language
[ RECORD 3 ]-----+---------------------------------
Name              | plv8
Owner             | postgres
Trusted           | t
Internal language | f
Call handler      | plv8_call_handler()
Validator         | plv8_call_validator(oid)
Inline handler    | plv8_inline_handler(internal)
Access privileges |
Description       |

PL / pgSQL no necesita ser creado especialmente; siempre está en la base de datos.

¡Atención! PL / pgSQL no debe confundirse con SQL. Este es un idioma diferente. Sin embargo, Postgres también puede escribir funciones en SQL simple.

Normas


En el mundo de los DBMS, a menudo hablan sobre el cumplimiento de SQL. Los lenguajes de procedimiento también tienen estándares, aunque no se habla con tanta frecuencia. El estándar SQL / PSM es altamente compatible con el lenguaje de procedimiento de DB2. Su implementación está lejos de PL / pgSQL, aunque conceptualmente están cerca.

SQL / JRT es el estándar para los procedimientos de Java, y PL / Java es una buena combinación.

Idiomas confiables y no confiables


Los lenguajes de procedimiento de Postgres son confiables (CONFIABLES) y no confiables (NO CONFIABLES).
En los lenguajes de CONFIANZA, no existe la posibilidad de trabajar directamente con E / S, incluida la red, y de hecho con los recursos del sistema. Por lo tanto, tales funciones pueden ser creadas por cualquier usuario de la base de datos, estropear algo y no podrá aprender demasiado. Las funciones en idiomas que NO SON DE CONFIANZA solo pueden ser creadas por un supervisor.

Si el intérprete de idiomas admite tales restricciones, puede usarse para crear idiomas CONFIABLES y NO CONFIABLES. Entonces, con Perl, hay diferentes idiomas plperly plperlu. Letra ual final revela el carácter no confiable del lenguaje. Python existe solo en una versión no confiable. PL / v8 - por el contrario, solo en confianza. Como resultado, PL / v8 no puede cargar ningún módulo o biblioteca desde el disco, solo desde la base de datos.

Una función en el idioma NO CONFIANZA puede hacer cualquier cosa: enviar un correo electrónico, hacer ping a un sitio, iniciar sesión en una base de datos extranjera y ejecutar una solicitud HTTP. Los idiomas de CONFIANZA se limitan al procesamiento de datos desde la base de datos.

Por CONFIANZA incluyen: plpgsql, plperl, plv8, pljava.

Por que no se confía incluir: plperlu, pljavau, plpython2u, plpython3u.

Tenga en cuenta: no hay PL / Python como TRUSTED (ya que no puede establecer restricciones en el acceso a los recursos allí), y PLpgSQL y PL / V8 son al revés: no son CONFIABLES.

Pero Perl y Java están disponibles en ambas versiones.

PL / pgSQL vs PL / *


El código PL / pgSQL funciona de forma nativa con todos los tipos de datos que tiene Postgres. Otros idiomas no tienen muchos tipos de Postgres, y el intérprete de idiomas se encarga de convertir los datos en una representación interna del idioma, reemplazando los tipos oscuros con texto. Sin embargo, se le puede ayudar con la ayuda de TRANSFORM, de la que hablaré más cerca del final del artículo.

Las llamadas a funciones en PL / pgSQL suelen ser más caras. Las funciones en otros idiomas pueden acceder a sus bibliotecas sin mirar el catálogo del sistema. PL / pgSQL no puede funcionar así. Algunas consultas en PL / pgSQL funcionan durante mucho tiempo debido al hecho de que se admiten muchos tipos: para agregar dos enteros, el intérprete debe darse cuenta de que está tratando con enteros y no con otros tipos exóticos, luego decide cómo doblarlos, y solo después de eso realmente doblarlos.

Dado que PL / pgSQL es CONFIABLE, no puede trabajar con la red y los discos de ella.

Cuando se trata de trabajar con estructuras de datos anidadas, PL / pgSQL solo tiene herramientas de Postgres para trabajar con JSON, que son muy engorrosas e improductivas, en otros idiomas, trabajar con estructuras anidadas es mucho más simple y económico.

PL / * tiene su propia administración de memoria, y usted necesita monitorear la memoria, o tal vez limitarla.

Debe controlar cuidadosamente el manejo de errores, que también es diferente para todos.

Pero en PL / * hay un contexto de intérprete global, y se puede usar, por ejemplo, para el almacenamiento en caché de datos, incluidos los planes de consulta. Si el idioma NO ES DE CONFIANZA, entonces la red y las unidades están disponibles. Todos estos lenguajes funcionan con la base de datos, por regla general, a través del SPI, pero más sobre eso más adelante.

Echemos un vistazo más de cerca a las características de los idiomas PL / *.

PL / Perl


El intérprete de Perl es un código considerable en la memoria, pero afortunadamente no se crea cuando se abre la conexión, sino solo cuando se inicia el primer procedimiento / función almacenada PL / Perl. Cuando se inicializa, se ejecuta el código especificado en los parámetros de configuración de Postgres. Por lo general, los módulos se cargan y se realizan cálculos previos. Si agregó al archivo de configuración mientras se ejecuta la base de datos, haga que Postgres vuelva a leer la configuración. En este artículo, los ejemplos usan un módulo para visualizar estructuras de datos. Hay parámetros para la inicialización por separado de Perl TRUSTED y UNTRUSTED y, por supuesto, un parámetro . Quienes programan en Perl saben que sin él no es un idioma, sino un malentendido.

plperl.on_init= 'use Data::Dumper;'
plperl.on_plperl_init= ' ... '
plperl.on_plperlu_init= ' ... '
plperl.use_strict= on


Data::Dumper

use_strict=onstrict

PL / Python


En él, el intérprete se crea de la misma manera la primera vez que se accede. Y aquí es importante decidir de inmediato qué python desea: segundo o tercero. Como sabes, Python existe en dos versiones populares (Python 2 y Python 3), pero el problema es que sus shki no se llevan bien en un solo proceso: hay un conflicto por nombre. Si trabajó con v2 en una sesión y luego llamó a v3, Postgres se bloqueará, y para el proceso del servidor (backend) esto será un error fatal. Para acceder a una versión diferente, debe abrir otra sesión.

A diferencia de Perl, a Python no se le puede decir qué hacer durante la inicialización. Otro inconveniente: las líneas simples son inconvenientes.

En todas las funciones de Python, se definen dos diccionarios: estático SDy global GD. Global permiteintercambiar datos con todas las funciones dentro de un backend, lo que es atractivo y peligroso al mismo tiempo. Cada función tiene un diccionario estático.

En PL / Python, puede hacer subtransacciones, que discutiremos a continuación.

PL / V8


Solo es de CONFIANZA.

Convenientemente, los datos JSON se convierten automáticamente en una estructura JS. En PL / V8, como en PL / Python, puede hacer subtransacciones. Hay una interfaz para llamadas a funciones simplificadas. Este es el único lenguaje de procedimiento en cuestión en el que se pueden definir las funciones de la ventana . Sugieren que se pueden definir en PL / R , pero este lenguaje está fuera del alcance de este artículo.

Y solo en PL / V8 hay un tiempo de espera de ejecución. Es cierto que no está activado de forma predeterminada, y si construye PL / V8 a mano, debe decir que se activó durante el ensamblaje, y luego puede establecer tiempos de espera para las llamadas de función con el parámetro de configuración.

La inicialización en PL / V8 parece interesante: dado que es confiable, no puede leer la biblioteca desde el disco, no puede cargar nada desde ningún lugar. Puede tomar todo lo que necesita solo de la base. Por lo tanto, se define una función de inicializador almacenada que se llama cuando se inicia el intérprete de idiomas. El nombre de la función se especifica en un parámetro de configuración especial:

plv8.start_proc=my_init # ( PL/V8-)

Durante la inicialización, se pueden crear variables y funciones globales asignando sus valores a los atributos de esta variable. Por ejemplo, así:

CREATE OR REPLACE FUNCTION my_init()
RETURNS void LANGUAGE plv8 AS $$
     this.get_57 = function() { return 57; }; //   
     this.pi_square = 9.8696044;  //   
$$;
SET plv8.start_proc = 'my_init';
DO LANGUAGE plv8 $$
     plv8.elog(NOTICE, pi_square, get_57() );
$$;

Comparación de PL / Perl vs PL / Python vs PL / V8 en la práctica


Hola Mundo!


Realicemos un ejercicio simple con el resultado de esta frase en los tres idiomas, primero en PL / Perl . Y que haga algo más útil, por ejemplo, le dice a su versión:

DO $$
     elog(NOTICE,"Hello World! $]");
$$ LANGUAGE plperl;

NOTICE:  Hello World!
DO

También puede usar las funciones habituales de Perl warny die.

Ahora en PL / Python . Más precisamente en PL / Python3u (no confiable) - para mayor precisión.

DO $$
     import sys
     plpy.notice('Hello World! ' , hint=" ", detail=sys.version_info)
$$ LANGUAGE plpython3u;


NOTICE:  Hello World! 
DETAIL:  sys.version_info(major=3, minor=6, micro=9, releaselevel='final', serial=0)
HINT:   
DO

Puede usar throw 'Errmsg'. Hay muchas cosas que puede extraer de los mensajes de Postgres: contienen Sugerencia, Detalles, número de línea y muchos otros parámetros. En PL / Python, se pueden pasar, pero no en los otros idiomas considerados: sus medios solo se pueden maldecir con una línea de texto sin formato.

En PL / Python, cada nivel de registro de postgres tiene su propia función: AVISO, ADVERTENCIA, DEPURACIÓN, REGISTRO, INFORMACIÓN, FATAL. Si es ERROR, entonces la transacción ha caído, si es FATAL, todo el backend ha caído. Afortunadamente, el asunto no llegó a PANIC. Puedes leer aquí .

PL / V8

En este idioma, Hello world es muy similar a la perla. Puede dejar de exceptionusar throw, y esto también será un manejo de errores, aunque las herramientas no son tan avanzadas como en Python. Si tú escribesplv8.elog(ERROR), el efecto será, por cierto, el mismo.

DO $$
     plv8.elog(NOTICE, 'Hello World!', plv8.version);
$$ LANGUAGE plv8;

NOTICE:  Hello World! 2.3.14
DO

Trabajar con la base


Ahora veamos cómo trabajar con una base de datos a partir de procedimientos almacenados. Postgres tiene una SPI (interfaz de programación del servidor). Este es un conjunto de funciones C que está disponible para todos los autores de extensiones. Casi todos los idiomas PL proporcionan envoltorios para SPI, pero cada idioma lo hace un poco diferente.

Es improbable que una función escrita en C pero usando SPI dé una ganancia significativa en comparación con PL / PgSQL y otros lenguajes de procedimiento. Pero una función C que omite SPI y funciona con datos sin intermediarios (por ejemplo table_beginscan/heap_getnext) funcionará un orden de magnitud más rápido.

PL / Java también usa SPI. Pero trabajar con la base de datos todavía ocurre al estilo de JDBC y el estándar JDBC. Para el creador de código en PL / Java, todo sucede como si estuviera trabajando desde una aplicación cliente, pero JNI (Java Native Interface) traduce las llamadas a la base de datos en las mismas funciones SPI. Es conveniente, y no hay obstáculos fundamentales para traducir este principio a PL / Perl y PL / Python, pero por alguna razón esto no se ha hecho, y hasta ahora no es visible en los planes.

Por supuesto, si lo desea, puede ir a bases extranjeras de la forma habitual: a través de DBI o Psycopg . Es posible una base de datos local, pero ¿por qué?

Si no entras en el tema holístico "proceso en la base vs proceso en el cliente", e inmediatamente procedes del procesamiento máximo más cerca de los datos (al menos para no conducir muestras gigantes a través de la red), entonces la solución para usar las funciones almacenadas en el servidor se ve naturalmente.

Rendimiento : tenga en cuenta que SPI tiene algunos gastos generales, y las consultas SQL en funciones pueden ser más lentas que sin funciones. El 13er postgres incluyó un parche de Konstantin Knizhnik , que reduce estos costos. Pero, por supuesto, el procesamiento de los resultados de la consulta en una función almacenada no requiere la transferencia del resultado al cliente y, por lo tanto, puede ser beneficioso en términos de rendimiento.

La seguridad: un conjunto de funciones depuradas y probadas aísla la estructura de la base de datos del usuario, protege contra inyecciones SQL y otras travesuras. De lo contrario, seguirá siendo un dolor de cabeza para todos los desarrolladores de aplicaciones. Reutilización de

código : si una gran cantidad de aplicaciones complejas funcionan con la base de datos, es conveniente almacenar funciones útiles en el servidor, en lugar de volver a escribirlas en cada aplicación.

Cómo y de qué forma obtenemos datos de la base de datos


En Perl , todo es simple y claro. La llamada spi_exec_querydevuelve el número de filas procesadas, el estado y la matriz de filas seleccionadas por la consulta SQL:

DO $$ 
     warn Data::Dumper::Dumper(
          spi_exec_query('SELECT 57 AS x')
     )
$$ LANGUAGE plperl;

WARNING:  $VAR1 = {
          'rows' => [
                    {
                      'x' => '57'
                    }
                  ],
          'processed' => 1,
          'status' => 'SPI_OK_SELECT'
        };

En Python, la consulta y el resultado se parecen a esto, pero aquí la función no devuelve una estructura de datos, sino un objeto especial con el que puede trabajar de diferentes maneras. Por lo general, pretende ser una matriz y, en consecuencia, puede extraer cadenas de ella.

DO $$ 
     plpy.notice(
          plpy.execute('SELECT 57 AS x')
     )
$$ LANGUAGE plpython3u;

NOTICE:  <PLyResult status=5 nrows=1 rows=[{'x': 57}]>
DO

Y ahora tomamos la primera línea, salimos de allí X y obtenemos el valor: el número.

DO $$ 
     plpy.notice(
          plpy.execute('SELECT 57 AS x')[0]['x']
      )
$$ LANGUAGE plpython3u;

NOTICE:  57
DO

En PL / V8 :

DO $$ 
     plv8.elog(NOTICE, JSON.stringify(
          plv8.execute('SELECT 57 as x'))
     );
$$ LANGUAGE plv8;

NOTICE:  [{"x":57}]
DO

Para ver la estructura, utilizamos la función de biblioteca JSON.stringify, que no necesita cargarse específicamente, ya está lista para usar como parte de PL / v8 de forma predeterminada.

Blindaje


Para evitar inyecciones SQL maliciosas, se deben escapar algunos caracteres en las consultas. Para hacer esto, en primer lugar, hay funciones SPI y funciones correspondientes (escritas en C) en lenguajes que funcionan como envoltorios SPI. Por ejemplo, en PL / Perl:

quote_literal- toma apóstrofes y dobla 'y \. Diseñado para el cribado de datos de texto.
quote_nullable- igual, pero undefconvertido a NULL.
quote_ident- cita el nombre de la tabla o campo, si es necesario. Útil en el caso cuando está construyendo una consulta SQL y sustituyendo los nombres de los objetos de la base de datos en ella.

PL / Perl

DO $$
     warn "macy's";
     warn quote_literal("macy's");
$$ LANGUAGE plperl;

WARNING:  macy's at line 2.
WARNING:  'macy''s' at line 3.
DO

Tenga en cuenta: el nombre de la tabla no debe escaparse como una línea de texto. Por eso hay una función quote_ident.

Pero en PL / Perl hay otras funciones para proteger datos de tipos individuales de post-gres: una función debe aceptar cualquier tipo y convertir caracteres dudosos atípicos en algo obviamente seguro. Funciona con una gran cantidad de tipos, pero, sin embargo, no con todos. Ella, por ejemplo, no comprenderá los tipos de rango y los percibirá simplemente como cadenas de texto.

encode_bytea
decode_bytea
encode_array_literal
encode_typed_literal
encode_array_constructor


quote_typed_literal

DO $$
     warn encode_typed_literal(
          ["", " "], "text[]"
     );
$$ LANGUAGE plperl;

WARNING:  {," "} at line 2.
DO

Hay tres funciones similares en PL / Python , y funcionan de la misma manera:

plpy.quote_literal
plpy.quote_nullable
plpy.quote_ident


DO $$ plpy.notice(
     plpy.quote_literal("Macy's"));
$$ LANGUAGE plpython3u;
NOTICE:  'Macy''s'
DO

¿Son las funciones en PL / V8 las mismas ?

¡Por supuesto! Todo es igual hasta las características sintácticas.

plv8.quote_literal
plv8.quote_nullable
plv8.quote_ident


DO $$
    plv8.elog(NOTICE, plv8.quote_nullable("Macy's"));
$$ LANGUAGE plv8;

NOTICE:  'Macy''s'

Actuación


¿Qué idioma es el más rápido? Usualmente responden: C. Pero la respuesta correcta es C o SQL. ¿Por qué SQL? El hecho es que una función en este lenguaje no siempre se realiza explícitamente. Puede incrustarse en la solicitud (el programador incorporará la función en el cuerpo de la solicitud principal), se optimizará bien con la solicitud y el resultado será más rápido. Pero, ¿bajo qué condiciones se puede incrustar el código en una solicitud? Hay algunas condiciones simples sobre las que puede leer, por ejemplo, aquí . Por ejemplo, una función no debe ejecutarse con los derechos del propietario (para ser DEFINIDOR DE SEGURIDAD). La mayoría de las funciones simples se ajustarán a estas condiciones.

En este artículo mediremos "en la rodilla", no en serio. Necesitamos una comparación aproximada. Primero encienda el tiempo:

\timing

Probemos con SQL (los tiempos de ejecución de los siguientes comandos son los valores promedio redondeados que el autor recibió en una PC descargada de seis años. Se pueden comparar entre sí, pero no afirman ser científicos):

SELECT count(*) FROM pg_class;
0.5 ms

Funciona muy rapido. En otros idiomas, se pierde tiempo llamando a las funciones del idioma. Por supuesto, la primera vez la solicitud se ejecutará más lentamente debido a la inicialización del intérprete. Entonces se estabiliza.

Probemos PL / pgSQL :

DO $$
     DECLARE a int;
     BEGIN
          SELECT count(*) INTO a FROM pg_class;
     END;
$$ LANGUAGE plpgsql;
0.7 ms

PL / Perl :

DO $$
     my $x = spi_exec_query('SELECT count(*) FROM pg_class');
$$ LANGUAGE plperl;
0.7 ms

PL / Python:

DO $$
     x = plpy.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plpythonu;
0.8 ms

Era Python 2. Ahora Python 3 (recuerde: Python2 y Python3 no viven pacíficamente dentro de la misma sesión, es posible un conflicto de nombres):

DO $$
     x = plpy.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plpython3u;
0.9ms

Y finalmente, PL / V8 :

DO $$
     var x = plv8.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plv8 ;
0.9 ms

Pero de alguna manera es muy rápido. Intentemos ejecutar la consulta 1000 veces o 1 millón de veces, de repente la diferencia será más notable:

PL / pgSQL :

DO $$
     DECLARE a int; i int;
     BEGIN FOR i IN 0..999999 LOOP
          SELECT count(*) INTO a FROM pg_class;
    END LOOP;
END;
$$ LANGUAGE plpgsql;
53s

PL / Perl :

DO $$
     for (0..999999) {
          spi_exec_query('SELECT count(*) FROM pg_class');
     }
$$ LANGUAGE plperl;
102s

PL / Python 3 :

DO $$
     for i in range (0,1000000) :
          plpy.execute('SELECT count(*) FROM pg_class')
$$ LANGUAGE plpython3u;
98s

PL / V8 :

DO $$
     for(var i=0;i<1000;i++)
          plv8.execute('SELECT count(*) FROM pg_class');
$$ LANGUAGE plv8;
100ms

Tenga en cuenta que con PL / V8, el experimento se realizó con mil, no un millón de iteraciones. Con recursos moderados, el PL / V8 en un ciclo de 1 millón de operaciones consumirá toda la memoria y colgará completamente el automóvil. Ya en mil iteraciones, el proceso postgres selecciona 3.5GB de memoria y 100% de escritura en disco. De hecho, postgres lanza el entorno V8 y, por supuesto, consume memoria. Después de ejecutar la solicitud, este monstruo turbo no devolverá la memoria. Para liberar memoria, debe finalizar la sesión.

Vemos que PL / pgSQL ya es 2 veces más rápido que PL / Perl y PL / Python. PL / V8 todavía está ligeramente detrás de ellos, pero hacia el final del artículo está parcialmente rehabilitado.

En general, Perl con Python en estos experimentos muestra aproximadamente los mismos resultados. Perl solía ser ligeramente inferior a Python; en las versiones modernas, es un poco más rápido. La tercera pitón es un poco más lenta que la segunda. Toda la diferencia está dentro del 15%.

Rendimiento con PREPARE


Las personas que saben entenderán: algo está mal. PL / pgSQL puede almacenar automáticamente en caché los planes de consulta , y en PL / *, cada vez que la consulta se programa de nuevo. En el buen sentido, debe preparar solicitudes, crear un plan de solicitud y, de acuerdo con este plan, deben ejecutarse tantas veces como sea necesario. En PL / *, puede trabajar explícitamente con planes de consulta, que intentaremos comenzar con PL / Perl :

DO $$
     my $h = spi_prepare('SELECT count(*) FROM pg_class');
     for (0..999999) {
          spi_exec_prepared($h);
     }
     spi_freeplan($h);
$$ LANGUAGE plperl;
60s

PL / Python 3 :

DO $$
     h = plpy.prepare('SELECT count(*) FROM pg_class')
     for i in range (0,1000000): plpy.execute(h)
$$ LANGUAGE plpython3u;
62s

PL / V8 :

DO $$
     var h=plv8.prepare('SELECT count(*) FROM pg_class');
     for(var i=0;i<1000;i++) h.execute();
$$ LANGUAGE plv8;
53ms

Con preparenuestros dos idiomas, casi alcanzamos PL / pgSQL, mientras que el tercero también quería, pero no llegó a la meta debido a la creciente demanda de memoria.

Pero si no tiene en cuenta la memoria, entonces está claro que todos los idiomas van casi cara a cara, y no por casualidad. Su cuello de botella ahora es común: trabajar con la base de datos a través de SPI.

Rendimiento informático


Vemos que el rendimiento del lenguaje ha descansado en el trabajo con la base de datos. Para comparar idiomas entre sí, intentemos calcular algo sin recurrir a la base de datos, por ejemplo, la suma de los cuadrados.

PL / pgSQL :

DO $$
     DECLARE i bigint; a bigint;
     BEGIN a=0;
     FOR i IN 0..1000000 LOOP
          a=a+i*i::bigint;
     END LOOP;
END;
$$ LANGUAGE plpgsql;
280ms

PL / Perl :

DO $$
     my $a=0;
     for my $i (0..1000000) { $a+=$i*$i; };
     warn $a;
$$ LANGUAGE plperl;
63ms

PL / Python 3 :

DO $$
a=0
for i in range(1,1000001): a=a+i*i
$$ LANGUAGE plpython3u;
73ms

PL / V8 :

DO $$
     var a=0;
     for(var i=0;i<=1000000;i++) a+=i*i;
     plv8.elog(NOTICE, a);
$$ language plv8;
7.5ms

Vemos que PL / Perl y PL / Python superaron y superaron a PL / pgSQL, son 4 veces más rápidos. ¡Y el ocho está destrozando a todos! ¿Pero es realmente por nada? ¿O lo conseguiremos para la cabeza? Si, lo haremos.

El número en JavaScript es flotante, y el resultado es rápido, pero no exacto: 333333833333127550 en lugar de 333333833333500000.

Aquí está la fórmula por la cual se calcula el resultado exacto:

∑ = n*(n+1)*(2n+1)/6

Como ejercicio, puedes probarlo usando inducción matemática.

En el orden de la risa

DO LANGUAGE plv8 $$
plv8.elog(NOTICE, parseInt(33333383333312755033)) $$;

NOTICE:
33333383333312754000

En Javascript, parseInttodavía hace un flotador, no un Int.

Sin embargo, BigInt apareció en V8 en 2018 , y ahora se puede contar con seguridad, pero en detrimento de la velocidad, ya que no es un entero de 64 bits, sino un entero de profundidad de bits arbitraria. Sin embargo, en PL / V8 esta innovación aún no ha llegado. En otros lenguajes de procedimiento, los números de bits arbitrarios (análogos de SQL numeric) son compatibles con bibliotecas especiales.

En Perl, hay un módulo Math :: BigFloat para aritmética con precisión arbitraria, y en Python, el paquete Bigfloat es un contenedor de Cython alrededor de la biblioteca GNU MPFR .

Funciones de rendimiento para ordenar


Aquí hay un ejemplo práctico, que muestra la diferencia en el rendimiento de la clasificación por función, si esta función está escrita en diferentes idiomas. Tarea: ordenar los campos de texto que contienen los números de los números de la revista, que pueden ser los siguientes:

1
2
3
4-5
6
6A
6
11
12

Aquellos. en realidad es una cadena, pero comienza con un número y debes ordenar por estos números. Por lo tanto, para ordenar correctamente como cadenas, complementamos la parte numérica con ceros a la izquierda para obtener:

0000000001
0000000002
0000000003
0000000004-5
0000000006
0000000006A
0000000006
0000000011
0000000012

Sí, sé que esta no es la única solución al problema (y ni siquiera del todo correcto). Pero, por ejemplo, lo hará.

Para solicitar un tipo, SELECT ... ORDER BY nsort(n)escribimos funciones en PL / Perl, SQL, PL / Python y PL / V8 que convierten los números de diario a este formulario:

CREATE OR REPLACE FUNCTION nsort(text) RETURNS text 
   LANGUAGE PLPERL IMMUTABLE AS $$
    my $x = shift;
    return ($x =~ /^\s*(\d+)(.*)$/)
        ? sprintf("%010d", $1).$2
        : $x;
$$;

CREATE OR REPLACE FUNCTION _nsort(x text) RETURNS text
     LANGUAGE SQL  IMMUTABLE  AS $$
 WITH y AS (
    SELECT regexp_match(x,'^\s*(\d*)(.*)$') as z
 )
 SELECT CASE WHEN z[1] = '' THEN x ELSE lpad(z[1],10,'0') || z[2] END FROM y;
$$;

CREATE OR REPLACE FUNCTION py_nsort(x text) RETURNS text 
   LANGUAGE plpython2u IMMUTABLE AS $$
import re
r = re.match('^\s*(\d+)(.*)$', x)
return x if r == None else ('%010d' % int(r.group(1))) + r.group(2)
$$;

CREATE OR REPLACE FUNCTION js_nsort(x text) RETURNS text 
   LANGUAGE plv8 IMMUTABLE AS $$
var m = x.match(/^\s*(\d+)(.*)$/);
if(m) { return m[1].padStart(10-m[1].length,'0') + m[2]; }
else { return x; } 
$$;

En mi biblioteca de 15.5 mil artículos de revistas, una consulta que usa una función en PL / Perl toma aproximadamente 64 ms contra 120 ms en PL / Python y 200 ms en PL / PgSQL. Pero el más rápido: PL / v8: 54 ms.

Nota: cuando experimente con la ordenación, proporcione la cantidad necesaria de memoria de trabajo para que la ordenación se guarde en la memoria (se mostrará EXPLAIN Sort Method: quicksort). La cantidad de memoria se establece mediante el parámetro work_mem:

set work_mem = '20MB';

Memoria


A Perl no le gustan las estructuras en bucle; no sabe cómo limpiarlas. Si atiene un puntero a b, y un bpuntero a a, entonces el contador de referencia nunca se restablecerá y la memoria no se liberará.

Los idiomas de recolección de basura tienen otros problemas. No se sabe, por ejemplo, cuándo se liberará la memoria o si se liberará en absoluto. O, si no se ocupa de esto a propósito, los recolectores irán a recoger la basura en el momento más inoportuno.

Pero también hay funciones de administración de memoria directamente relacionadas con Postgres. Hay estructuras que SPI asigna, y Perl no siempre se da cuenta de que necesitan ser liberadas.

PL / Perl

NO es así:

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     return spi_exec_query(
           'SELECT count(*) FROM pg_class'
     )->{rows}->[0]->{count};
$$;

Y así continúa:

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     my $h = spi_prepare(
          'SELECT count(*) FROM pg_class'
     );
     return spi_exec_prepared($h)->{rows}->[0]->{count};
$$;

Después de la ejecución, el controlador $hseguirá vivo, a pesar del hecho de que no queda un solo vínculo vivo con él.

Está bien, solo necesita recordar la necesidad de liberar recursos explícitamente con spi_freeplan($h):

CREATE OR REPLACE function cr()
RETURNS int LANGUAGE plperl AS
$$
     my $h = spi_prepare(
          'select count(*) from pg_class'
     );
     my $res = spi_exec_prepared($h)->{rows}->[0]->{count};
     spi_freeplan($h);
     return $res;
$$;

PL / Python:

Python nunca fluye , el plan se lanza automáticamente:

CREATE OR REPLACE function cr3() RETURNS int
LANGUAGE plpythonu as
$$
     return plpy.execute(
           'select count(*) from pg_class'
     )[0]['count']
$$;

PL / V8

Misma historia que Perl. No fluye así:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     return plv8.execute(
          'select count(*) from pg_class‘
     )[0].count;
$$;

Y así continúa:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     var h = plv8.prepare(
          'select count(*) from pg_class'
     );
     return h.execute()[0].count;
$$;

Nuevamente: no te olvides de liberar recursos. Aquí lo hace. h.free();

No fluye:

CREATE OR REPLACE FUNCTION crq() RETURNS int
LANGUAGE plv8 AS
$$
     var h = plv8.prepare(
          'select count(*) from pg_class'
     );
     var r = h.execute()[0].count;
     h.free();
     return r;
$$;

Parámetros


Es hora de entender cómo se pasan los argumentos a las funciones. En los ejemplos, pasaremos 4 parámetros con tipos a la función:

  • todo;
  • una matriz;
  • bytea y
  • jsonb

¿Cómo entran en PL / Perl ?

CREATE OR REPLACE FUNCTION crq(a int, b
bytea, c int[], d jsonb ) RETURNS void
LANGUAGE plperl AS
$$
    warn Dumper(@_);
$$;

SELECT crq(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  $VAR1 = '1';
$VAR2 = '\\x61626364';
$VAR3 = bless( {
                 'array' => [
                              '1',
                              '2',
                              '3'
                            ],
                 'typeoid' => 1007
               }, 'PostgreSQL::InServer::ARRAY' );
$VAR4 = '{"a": 2, "b": 3}';
 crq 
-----
(1 row)

¿Será JSON o JSONB? En este caso, no hay diferencia: todavía tienen la forma de una cadena. Esta es una tarifa para la versatilidad: Postgres tiene muchos tipos, de diversos grados de "incrustación". Exigirle al desarrollador que con el nuevo tipo que proporciona de inmediato y funciones de conversión para todos los PL / * sería demasiado. Por defecto, muchos tipos se pasan como cadenas. Pero esto no siempre es conveniente, debe analizar estos términos. Por supuesto, me gustaría que los datos de Postgres se conviertan inmediatamente en las estructuras Perl apropiadas. Por defecto, esto no sucede, pero a partir de 9.6, apareció el mecanismo TRANSFORM - la capacidad de definir funciones de conversión de tipo: CREATE TRANSFORM .

Para crear TRANSFORMAR, debe escribir dos funciones en C: una convertirá datos de cierto tipo a un lado y el otro a la inversa. Tenga en cuenta que TRANSFORM funciona en cuatro lugares:

  • Al pasar parámetros a una función;
  • Al devolver un valor de función;
  • Al pasar parámetros a una llamada SPI dentro de una función;
  • Al recibir el resultado de la llamada SPI dentro de la función.

TRANSFORM JSONB para Perl y Python, desarrollado por Anton Bykov, apareció en la 11ª versión de Postgres. Ahora no necesita analizar JSONB, entra en Perl de inmediato como la estructura correspondiente. Debe crear la extensión jsonb_plperl, y luego puede usar TRANSFORMAR:

CREATE EXTENSION jsonb_plperl;
CREATE OR REPLACE FUNCTION crq2(d jsonb)
RETURNS void LANGUAGE plperl
TRANSFORM FOR TYPE jsonb AS $$
     warn Dumper(@_);
$$;

Puede llamar a esta función para verificar que JSONB se haya convertido en un hash de perlas:

SELECT crq2( '{"a":2,"b":3}');


WARNING:  $VAR1 = {
          'a' => '2',
          'b' => '3'
        };
 crq2 
------
(1 row)

¡Un asunto completamente diferente!

El autor de este artículo también participó en el desarrollo de TRANSFORMAS. Resultó que un tipo de datos tan simple, como se booleanpasa a PL / Perl en una forma inconveniente, como cadenas de texto 't'o 'f'. Pero en el entendimiento de Perl, la cadena 'f' es verdadera. Para eliminar las molestias, se inventó un parche que definía la conversión para el tipo booleano . Este parche llegó a PostgreSQL 13 y estará disponible pronto. Debido a su simplicidad, bool_plperl puede servir como un modelo inicial mínimo para escribir cualquier otra conversión.

Espero que alguien desarrolle TRANSFORM para otros tipos de datos (bytea, matrices, fechas, numéricos).

Ahora veamos cómo se pasan los parámetros en Python .

CREATE EXTENSION jsonb_plpython3u;
CREATE OR REPLACE FUNCTION pdump(a int, b bytea, c int[], d jsonb ) RETURNS void
LANGUAGE plpython3u
TRANSFORM FOR TYPE jsonb AS $$
      plpy.warning(a,b,c,d)
$$;

SELECT pdump(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  (1, b'abcd', [1, 2, 3], {'a': Decimal('2'), 'b': Decimal('3')})
 pdump 
-------
(1 row)

Una matriz se convierte en una matriz; esto es bueno (ya que las matrices multidimensionales de la versión PG10 también se transfieren correctamente a Python). En Perl, una matriz se convirtió en un objeto de una clase especial. Bueno, jsonbtransformado. Sin TRANSFORMAR, jsonb se pasará como una cadena.

Ahora veamos de qué forma los parámetros entran en JS .

CREATE OR REPLACE FUNCTION jsdump(a int, b bytea, c int[], d jsonb) RETURNS void
LANGUAGE plv8 AS $$
     plv8.elog(WARNING,a,b,c,d)
$$;

SELECT jsdump(1,'abcd', ARRAY[1,2,3],'{"a":2,"b":3}');


WARNING:  1 97,98,99,100 1,2,3 [object Object]
jsdump 
-------
(1 row)

JSONB convertido a un objeto JavaScript sin ninguna TRANSFORMACIÓN! Los tipos temporales de Postgres también se convierten al tipo Fecha JS. Lo mismo con boolean. Todas las transformaciones ya están integradas en PL / V8.

Trabajar con infinito


La constante INFINITY no se usa muy a menudo, pero el trabajo descuidado con ella es peligroso. En PostgreSQL, Infinity e -Infinity existen como valores especiales para algunos tipos temporales y de punto flotante. Pero la transferencia de Infinity a los lenguajes de procedimiento y viceversa debe discutirse en detalle, ya que trabajar con ellos puede depender no solo del idioma, sino también de las bibliotecas, el sistema operativo e incluso el hardware.

Python tiene un módulo Numpy que define el infinito numérico:

import numpy as nm
a = nm.inf
b = -nm.inf
print(a, b)

inf -inf

Perl también tiene infinito, usa una cadena "infinity"que se puede acortar "inf". Por ejemplo, podrías decir:

perl -e 'print 1 * "inf"'

Inf

o

perl -e 'print 1/"inf"'

0

En PL / Perl, PL / Python, PL / v8, el infinito numérico de Postgres se pasa correctamente, pero una fecha infinita no es del todo correcta. Por el contrario, en PL / Perl y PL / Python no hay un tipo de datos incorporado para el tiempo, una cadena llega allí. En PL / V8, hay una fecha de tipo incorporada, y la fecha habitual de un postgres se convierte en ella. Pero el V8 no conoce la fecha sin fin, y cuando se transfiere, se convierte en Invalid Date.

Pasar parámetros a solicitudes preparadas


De vuelta a prepare, considere cómo se pasan los parámetros allí. Los diferentes idiomas tienen mucho en común, ya que todos están basados ​​en SPI.

Cuando prepara una consulta en PL / Perl , debe determinar el tipo de parámetros que se transfieren, y al ejecutar la consulta, solo especifica los valores de estos parámetros (los parámetros se transfieren a PL / pgSQL de la misma manera).

DO LANGUAGE plperl $$
     my $h= spi_prepare('SELECT * FROM pg_class WHERE
          relname ~ $1', 'text' );                     #   
     warn Dumper(spi_exec_prepared($h, 'pg_language')); #   
     spi_freeplan($h);
$$;

En PL / Python, la esencia es la misma, pero la sintaxis es ligeramente diferente:

DO LANGUAGE plpython3u $$
     h= plpy.prepare('SELECT relname FROM pg_class WHERE relname ~ $1', ['text'] )
     plpy.notice(.execute (['pg_language']))
$$;

En PL / V8, las diferencias son mínimas:

DO LANGUAGE plv8 $$
    var h= plv8.prepare('SELECT relname FROM pg_class WHERE relname ~ $1', ['text'] );
    plv8.elog(NOTICE, h.execute (['pg_language']));
    h.free();
$$;

En PL / Java, todo es diferente. Allí, SPI claramente no se usa, pero se forma una conexión pseudo-JDBC a la base de datos. Para un programador PL / Java, todo sucede como si estuviera creando una aplicación cliente. Esto es conveniente, y también se podría abordar el diseño de PL / Perl y PL / Python, pero por alguna razón esto no se hizo (sin embargo, nadie prohíbe crear un par de implementaciones más de PL / Perl y PL / Python).

Trabaja con cursor


Todas las funciones SPI que utilizamos cuando fuimos a la base de datos, spi_exec_query()y otras, tienen un parámetro que limita el número de filas devueltas. Si necesita muchas filas devueltas, no puede prescindir de un cursor para levantarlas un poco.

Los cursores funcionan en todos estos idiomas. En PL / Perl,
spi_exec_query devuelve un cursor desde el que puede extraer cadenas de una en una. No es necesario cerrar el cursor; se cerrará solo. Pero si desea redescubrirlo nuevamente, puede cerrarlo explícitamente con un comando close().

DO LANGUAGE plperl $$
    my $cursor = spi_query('SELECT * FROM pg_class');
    my $row;
    while(defined($row = spi_fetchrow($cursor))) {
         warn $row->{relname};
    }
$$;

WARNING:  pg_statistic at line 5.
WARNING:  pg_toast_2604 at line 5.
WARNING:  pg_toast_2604_index at line 5.
WARNING:  pg_toast_2606 at line 5.
WARNING:  pg_toast_2606_index at line 5.
WARNING:  pg_toast_2609 at line 5.
WARNING:  pg_toast_2609_index at line 5.
...

En PL / Python, todo es muy similar, pero el cursor se presenta como un objeto que puede recorrer:

h = plpy.prepare('SELECT ...');
cursor = plpy.cursor(h);
for row in cursor:
...
cursor.close() //  

En PL / v8, todo también es muy similar, pero no olvide liberar el plan de consulta preparado:

var h = plv.prepare('SELECT ...');
var cursor = h.cursor();
var row;
while(row = cursor.fetch()) {
...
}
cursor.close();
h.free();

PL / V8: acceso rápido a las funciones


En PL / V8, puede llamar a una función no desde un SELECT normal, sino encontrarla por su nombre e iniciarla inmediatamente con plv8.find_function(name);. Pero tenga en cuenta que en JS una función no puede ser polimórfica, como en PostgreSQL, en la que pueden coexistir funciones con el mismo nombre pero con diferentes parámetros. En PL / v8, por supuesto, podemos crear funciones polimórficas, pero find_functionhabrá un error al intentar usarlo .

ERROR:  Error: more than one function named "jsdump"

Si una función por nombre no es ambigua, entonces se puede invocar sin SPI y conversiones de tipo, es decir mucho mas rápido. Por ejemplo, así:

DO LANGUAGE plv8 $$
plv8.find_function('jsdump')(1, 'abc');
$$;

Actas


Postgres 11 se divierte mucho: han aparecido procedimientos reales . Postgres solía tener solo características. La alegría no solo se debe a la compatibilidad y el cumplimiento del estándar SQL, sino también por qué: dentro de los procedimientos, puede confirmar y revertir las transacciones.

PL / Perl y PL / Python ya tienen funciones SPI para administrar transacciones, mientras que PL / V8 todavía no. En PL / Perl, estas funciones se llaman spi_commit()y spi_rollback(), y un ejemplo de uso se encuentra en la documentación . En PL / Python, esto es plpy.commit()y plpy.rollback().

Subtransacción


Las subtransacciones son convenientes para el manejo correcto de errores en la lógica compleja de múltiples niveles.

En PL / pgSQL dentro de una transacción, cada bloque con la palabra clave EXCEPTION es una subtransacción. Puede leer sobre algunos problemas de rendimiento y confiabilidad que pueden surgir en este caso, por ejemplo, aquí .

No hay subtransacciones explícitas en PL / Perl , pero se pueden simular a través de savaepoints. Aparentemente, si lo desea, es fácil escribir un módulo perla que implemente subtransacciones de forma explícita.

En PL / Python, las sub-transacciones aparecieron hace mucho tiempo: desde 9.5 explícito , antes de eso había implícitas . Puede definir una transacción, envolverla entry-y ejecutar Si la subtransacción se cae, entonces caemos en el bloque except, si no se cae, entonces en el bloque elsey seguimos adelante.

try:
     with plpy.subtransaction():
          plpy.execute("...")
          plpy.execute("...")
except plpy.SPIError, e:
. . .
else:
. . .

Existe un diseño similar en PL / V8 , solo en sintaxis JS.

try {
plv8.subtransaction(function() {
plv8.execute('UPDATE...');
plv8.execute('UPDATE...');
});
}
catch(e) {
...
}

Conclusión


Intente, pero no abuse :) El conocimiento de PL / * puede traer algunos beneficios. Como cualquier herramienta, les encanta ser utilizados para los fines previstos.

PL / v8 es muy prometedor, pero a veces se comporta inesperadamente y tiene varios problemas. Por lo tanto, es mejor sacar los idiomas de la caja si son adecuados para su tarea.

Quiero agradecer a Igor Levshin (Igor_Le), quien me ayudó mucho con la preparación del material para el artículo y arrojó algunas ideas útiles, así como a Yevgeny Sergeyev y Alexey Fadeev por las correcciones que propusieron.

All Articles