¿Esquema temporal por conexión?

8

Estoy tratando de migrar mis pruebas unitarias de H2 a Postgresql.

Actualmente, H2 me proporciona un esquema en memoria de modo que cada conexión se asigna a un esquema único, crea las tablas, ejecuta la prueba y descarta el esquema. La creación y destrucción del esquema es manejada automáticamente por H2.

Las pruebas unitarias se ejecutan simultáneamente.

¿Cuál es la mejor manera de hacer esto en Postgresql? Específicamente,

  1. ¿Cómo obtengo un esquema único por conexión?
    • ¿Debería el marco de prueba generar nombres únicos o hay un mecanismo incorporado para hacerlo?
  2. ¿Cómo me aseguro de que se caiga el esquema cuando se cae la conexión?
    • No quiero terminar con esquemas colgantes cuando se matan las pruebas unitarias.
  3. ¿Qué enfoque producirá el mayor rendimiento?
    • Necesito crear / soltar decenas de esquemas por segundo.

ACTUALIZACIÓN : Encontré una respuesta relacionada aquí, pero no deja caer los esquemas en caso de que el proceso que ejecuta las pruebas unitarias se elimine.

Gili
fuente

Respuestas:

13

pg_temp es un alias para el esquema temporal de la sesión actual.

Si lo hace SET search_path TO pg_tempantes de ejecutar sus pruebas, todo debería funcionar (siempre y cuando nada haga referencia explícita a un esquema).

Si no desea cambiar su script en absoluto, configure search_pathen el usuario que las pruebas inicien sesión como:

> ALTER ROLE testuser SET search_path = pg_temp;

Entonces, todo lo que el usuario cree estará en pg_temp a menos que se especifique explícitamente.

Aquí hay un ejemplo de psql, que muestra el esquema real (para esta conexión) que el alias resuelve:

> SET search_path TO pg_temp;
SET
> create table test();
CREATE TABLE
> \dt test
          List of relations
  Schema   | Name | Type  |  Owner
-----------+------+-------+----------
 pg_temp_4 | test | table | postgres
(1 row)

Y, como era de esperar, ese esquema es diferente para cada conexión concurrente, y desaparece después de que se cierra la conexión.

Tenga en cuenta que esto también funciona para las funciones, aunque tendrá que hacer referencia explícita al esquema pg_temp al llamarlas.

hbn
fuente
Pero pg_tempes un esquema único, ¿verdad? Entonces, cuando ejecuto pruebas unitarias concurrentes, ¿no se golpearán entre sí las tablas / datos?
Gili
1
No. Es un alias para el esquema temporal de la sesión actual. Actualizaré la respuesta con un ejemplo.
hbn
Tenga en cuenta que si cierra y abre una conexión, puede terminar con el mismo esquema temporal, pero se habrá vaciado. Abra 2 simultáneamente para ver que se asignen diferentes. No puede ver el esquema temporal de otra sesión a menos que sea un superusuario.
hbn
Claro que vi un comentario tuyo preguntando cuándo establecer esto. De todos modos, se establece por sesión si solo haces un SET search_path; use SET LOCAL search_pathpara configurar por subtransacción, o si lo desea, puede configurar a nivel de usuario con ALTER USER mytestuser SET search_path = 'pg_temp', o a nivel de base de datos conALTER DATABASE mytestdb SET search_path = 'pg_temp'
hbn
Por curiosidad, ¿hay alguna forma de hacer que esto funcione para funciones sin una referencia de esquema explícito? ¿O es esto imposible para el pg_tempesquema?
Gili
3

Usted puede obtener el nombre del esquema temporal actual (después de crear la primera tabla temporal), como se establece en el enlace que ha añadido:

SELECT nspname
FROM   pg_namespace
WHERE  oid = pg_my_temp_schema();

Pero su plan actual todavía no tendría mucho sentido. Para crear tablas en el esquema temporal actual, solo cree tablas temporales. Eso es todo. Por defecto, search_pathse define de modo que las tablas temporales sean visibles primero. Uno nunca necesita calificar esquemáticamente tablas temporales. Nunca debería tener que abordar el esquema temporal actual directamente de ninguna manera, eso es un detalle de implementación.

Erwin Brandstetter
fuente
Convino en que es un truco, pero puede ser significativamente más sencillo que parametrizar el código de creación para permitir la creación de tablas temporales.
hbn
Buen punto, excepto como @hbn mencionó que quiero que las pruebas unitarias y el código de producción ejecuten el mismo script SQL. El primero debería ejecutarse contra un esquema temporal, mientras que el segundo no.
Gili
@hbn, por curiosidad, ¿cómo sería el código de creación parametrizado? Estoy usando flywaydb.org y solo ejecuta archivos SQL simples (sin variables). Probablemente no quiero ir por este camino. Tengo curiosidad por saber qué implica.
Gili
Nunca he usado flywaydb. En un nivel muy básico, puede usar un lenguaje de plantillas de texto (por ejemplo, Jinja2 en Python) para preprocesar sus scripts de creación, opcionalmente agregando un "temporal" cuando crea una tabla. Si está creando funciones explícitamente, el truco de obtener el esquema temporal es probablemente inevitable (hasta donde yo sé), no puede crear directamente una función temporal.
hbn
@hbn If you're explicitly sequences ...: Creo que tu último comentario contenía un error tipográfico. ¿Qué quisiste decir entre explicitlyy sequences?
Gili
1

¿Sus pruebas involucran transacciones? DDL es transaccional en PostgreSQL, por lo que si crea su esquema y tablas, luego ejecuta sus pruebas, todo dentro de una sola transacción que luego se revierte, el esquema nunca se confirma y es visible para otras sesiones.

Aún necesitaría usar un nombre probablemente único para su esquema (tal vez incluir nombre de host y PID), ya CREATE SCHEMAque fallará inmediatamente si ya existe un esquema con nombre idéntico y se bloqueará si otra sesión ha creado un esquema con nombre idéntico en Una transacción no comprometida.

Una alternativa posiblemente sería usar tablas temporales, si puede modificar sus scripts de creación de base de datos para hacerlo.

hbn
fuente
Buen truco, pero no funcionaría en mi caso porque una sola prueba opera en múltiples transacciones. Cada método de prueba es un cliente web que dispara múltiples transacciones del lado del servidor. Por ejemplo, crea, consulta y elimina un usuario. Cada llamada es una solicitud HTTP separada y se ejecuta en su propia transacción.
Gili
Bastante justo, mi enfoque fue muy limitado.
hbn
@Gili: Tenga en cuenta que esta técnica de nunca cometer CREATE SCHEMAes la única que puede garantizar que desaparezcan cuando se mata la prueba de la unidad.
Daniel Vérité
0

Acabo de tener una idea.

Postgresql garantiza que una sesión no puede ver las tablas temporales de otra. Supongo que esto significa que cuando crea una tabla temporal, crea un esquema temporal. Entonces quizás podría hacer lo siguiente:

  1. Cree una tabla temporal (ficticia) y busque su esquema.
  2. Use este esquema para la prueba (cree las tablas, ejecute la prueba).
  3. Cuando se cierra la conexión, Postgresql descartará el esquema.

No me gusta confiar en los detalles de implementación, pero en este caso esto parece bastante seguro.

Gili
fuente