Las vistas y el sistema de reglas.

Implementación de las vistas en Postgres

Las vistas en Postgres se implementan utilizando el sistema de reglas. De hecho, no hay diferencia entre

    CREATE VIEW myview AS SELECT * FROM mytab;
y la secuencia:
    CREATE TABLE myview 
    (la misma lista de atributos de mytab);
    CREATE RULE "_RETmyview" AS ON SELECT TO myview DO INSTEAD
        SELECT * FROM mytab;
Porque esto es exactamente lo que hace internamente el comando CREATE VIEW. Esto tiene algunos efectos colaterales. Uno de ellos es que la información sobre una vista en el sistema de catálogos de Postgres es exactamente el mismo que para una tabla. De este modo, para los traductores de queries, no hay diferencia entre una tabla y una vista, son lo mismo: relaciones. Esto es lo más importante por ahora.

Cómo trabajan las reglas de SELECT

Las reglas ON SELECT se aplican a todas las queries como el último paso, incluso si el comando dado es INSERT, UPDATE o DELETE. Y tienen diferentes semanticas de las otras en las que modifican el arbol de traducción en lugar de crear uno nuevo. Por ello, las reglas SELECT se describen las primeras.

Actualmente, debe haber sólo una acción y debe ser una acción SELECT que es una INSTEAD. Esta restricción se requería para hacer las reglas seguras contra la apertura por usuarios ordinarios, y restringe las reglas ON SELECT a reglas para vistas reales.

El ejemplo para este documento son dos vistas unidas que hacen algunos cálculos y algunas otras vistas utilizadas para ello. Una de estas dos primeras vistas se personaliza más tarde añadiendo reglas para operaciones de INSERT, UPDATE y DELETE de modo que el resultado final será una vista que se comporta como una tabla real con algunas funcionalidades mágicas. No es un ejemplo fácil para empezar, y quizá sea demasiado duro. Pero es mejor tener un ejemplo que cubra todos los puntos discutidos paso a paso que tener muchos ejemplos diferentes que tener que mezclar después.

La base de datos necesitada para ejecutar los ejemplos se llama al_bundy. Verá pronto el porqué de este nombre. Y necesita tener instalado el lenguaje procedural PL/pgSQL, ya que necesitaremos una pequeña función min() que devuelva el menor de dos valores enteros. Creamos esta función como:

    CREATE FUNCTION min(integer, integer) RETURNS integer AS
        'BEGIN
            IF $1 < $2 THEN
                RETURN $1;
            END IF;
            RETURN $2;
        END;'
    LANGUAGE 'plpgsql';

Las tablas reales que necesitaremos en las dos primeras descripciones del sistema de reglas son estas:

    CREATE TABLE shoe_data (      -- datos de zapatos
        shoename   char(10),      -- clave primaria (primary key)
        sh_avail   integer,       -- número de pares utilizables
        slcolor    char(10),      -- color de cordón preferido
        slminlen   float,         -- longitud mínima de cordón
        slmaxlen   float,         -- longitud máxima del cordón
        slunit     char(8)        -- unidad de longitud
    );

    CREATE TABLE shoelace_data (  -- datos de cordones de zapatos
        sl_name    char(10),      -- clave primaria (primary key)
        sl_avail   integer,       -- número de pares utilizables
        sl_color   char(10),      -- color del cordón
        sl_len     float,         -- longitud del cordón
        sl_unit    char(8)        -- unidad de longitud
    );

    CREATE TABLE unit (           -- unidades de longitud
        un_name    char(8),       -- clave primaria (primary key)
        un_fact    float          -- factor de transformación a cm
    );
Pienso que la mayoría de nosotros lleva zapatos, y puede entender que este es un ejemplo de datos realmente utilizables. Bien es cierto que hay zapatos en el mundo que no necesitan cordones, pero nos hará más facil la vida ignorarlos.

Las vistas las crearemos como:

    CREATE VIEW shoe AS
        SELECT sh.shoename,
               sh.sh_avail,
               sh.slcolor,
               sh.slminlen,
               sh.slminlen * un.un_fact AS slminlen_cm,
               sh.slmaxlen,
               sh.slmaxlen * un.un_fact AS slmaxlen_cm,
               sh.slunit
          FROM shoe_data sh, unit un
         WHERE sh.slunit = un.un_name;

    CREATE VIEW shoelace AS
        SELECT s.sl_name,
               s.sl_avail,
               s.sl_color,
               s.sl_len,
               s.sl_unit,
               s.sl_len * u.un_fact AS sl_len_cm
          FROM shoelace_data s, unit u
         WHERE s.sl_unit = u.un_name;

    CREATE VIEW shoe_ready AS
        SELECT rsh.shoename,
               rsh.sh_avail,
               rsl.sl_name,
               rsl.sl_avail,
               min(rsh.sh_avail, rsl.sl_avail) AS total_avail
          FROM shoe rsh, shoelace rsl
         WHERE rsl.sl_color = rsh.slcolor
           AND rsl.sl_len_cm >= rsh.slminlen_cm
           AND rsl.sl_len_cm <= rsh.slmaxlen_cm;
El comando CREATE VIEW para la vista shoelace (que es la más simple que tenemos) creará una relación shoelace y una entrada en pg_rewrite que dice que hay una regla de reescritura que debe ser aplicada siempre que la relación shoelace sea referida en la tabla de rango de una query. La regla no tiene cualificación de regla (discutidas en las reglas no SELECT, puesto que las reglas SELECT no pueden tenerlas) y es de tipo INSTEAD (en vez de). ¡Nótese que la cualificación de las reglas no son lo mismo que las cualificación de las queries! La acción de las reglas tiene una cualificación.

La acción de las reglas es un árbol de query que es una copia exacta de la instrucción SELECT en el comando de creación de la vista.

NotaNota:
 

Las dos tablas de rango extra para NEW y OLD (llamadas *NEW* y *CURRENT* por razones históricas en el árbol de query escrito) que se pueden ver en la entrada pg_rewrite no son de interes para las reglas de SELECT.

Ahora publicamos unit, shoe_data y shoelace_data y Al (el propietario de al_bundy) teclea su primera SELECT en esta vida.
    al_bundy=> INSERT INTO unit VALUES ('cm', 1.0);
    al_bundy=> INSERT INTO unit VALUES ('m', 100.0);
    al_bundy=> INSERT INTO unit VALUES ('inch', 2.54);
    al_bundy=> 
    al_bundy=> INSERT INTO shoe_data VALUES 
    al_bundy->     ('sh1', 2, 'black', 70.0, 90.0, 'cm');
    al_bundy=> INSERT INTO shoe_data VALUES 
    al_bundy->     ('sh2', 0, 'black', 30.0, 40.0, 'inch');
    al_bundy=> INSERT INTO shoe_data VALUES 
    al_bundy->     ('sh3', 4, 'brown', 50.0, 65.0, 'cm');
    al_bundy=> INSERT INTO shoe_data VALUES 
    al_bundy->     ('sh4', 3, 'brown', 40.0, 50.0, 'inch');
    al_bundy=> 
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl1', 5, 'black', 80.0, 'cm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl2', 6, 'black', 100.0, 'cm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl3', 0, 'black', 35.0 , 'inch');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl4', 8, 'black', 40.0 , 'inch');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl5', 4, 'brown', 1.0 , 'm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl6', 0, 'brown', 0.9 , 'm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl7', 7, 'brown', 60 , 'cm');
    al_bundy=> INSERT INTO shoelace_data VALUES 
    al_bundy->     ('sl8', 1, 'brown', 40 , 'inch');
    al_bundy=> 
    al_bundy=> SELECT * FROM shoelace;
    sl_name   |sl_avail|sl_color  |sl_len|sl_unit |sl_len_cm
    ----------+--------+----------+------+--------+---------
    sl1       |       5|black     |    80|cm      |       80
    sl2       |       6|black     |   100|cm      |      100
    sl7       |       7|brown     |    60|cm      |       60
    sl3       |       0|black     |    35|inch    |     88.9
    sl4       |       8|black     |    40|inch    |    101.6
    sl8       |       1|brown     |    40|inch    |    101.6
    sl5       |       4|brown     |     1|m       |      100
    sl6       |       0|brown     |   0.9|m       |       90
    (8 rows)
Esta es la SELECT más sencilla que Al puede hacer en sus vistas, de modo que nosotros la tomaremos para explicar la base de las reglas de las vistas. 'SELECT * FROM shoelace' fue interpretado por el traductor y produjo un árbol de traducción.
    SELECT shoelace.sl_name, shoelace.sl_avail,
           shoelace.sl_color, shoelace.sl_len,
           shoelace.sl_unit, shoelace.sl_len_cm
      FROM shoelace shoelace;
y este se le dá al sistema de reglas. El sistema de reglas viaja a través de la tabla de rango, y comprueba si hay reglas en pg_rewrite para alguna relación. Cuando se procesa las entradas en la tabla de rango para shoelace (el único hasta ahora) encuentra la regla '_RETshoelace' con el árbol de traducción
    SELECT s.sl_name, s.sl_avail,
           s.sl_color, s.sl_len, s.sl_unit,
           float8mul(s.sl_len, u.un_fact) AS sl_len_cm
      FROM shoelace *OLD*, shoelace *NEW*,
           shoelace_data s, unit u
     WHERE bpchareq(s.sl_unit, u.un_name);
Nótese que el traductor cambió el calculo y la cualificación en llamadas a las funciones apropiadas. Pero de hecho esto no cambia nada. El primer paso en la reescritura es mezclar las dos tablas de rango. El árbol de traducción entonces lee
    SELECT shoelace.sl_name, shoelace.sl_avail,
           shoelace.sl_color, shoelace.sl_len,
           shoelace.sl_unit, shoelace.sl_len_cm
      FROM shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, 
	   shoelace_data s,
           unit u;
En el paso 2, añade la cualificación de la acción de las reglas al árbol de traducción resultante en
    SELECT shoelace.sl_name, shoelace.sl_avail,
           shoelace.sl_color, shoelace.sl_len,
           shoelace.sl_unit, shoelace.sl_len_cm
      FROM shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, shoelace_data s,
           unit u
     WHERE bpchareq(s.sl_unit, u.un_name);
Y en el paso 3, reemplaza todas las variables en el arbol de traducción, que se refieren a entradas de la tabla de rango (la única que se está procesando en este momento para shoelace) por las correspondientes expresiones de la lista objetivo correspondiente a la acción de las reglas. El resultado es la query final:
    SELECT s.sl_name, s.sl_avail,
           s.sl_color, s.sl_len, 
           s.sl_unit, 
	   float8mul(s.sl_len, u.un_fact) AS sl_len_cm
      FROM shoelace shoelace, shoelace *OLD*,
           shoelace *NEW*, shoelace_data s,
           unit u
     WHERE bpchareq(s.sl_unit, u.un_name);
Para realizar esta salida en una instrucción SQL real, un usuario humano debería teclear:
    SELECT s.sl_name, s.sl_avail,
           s.sl_color, s.sl_len,
           s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm
      FROM shoelace_data s, unit u
     WHERE s.sl_unit = u.un_name;
Esta ha sido la primera regla aplicada. Mientras se iba haciendo esto, la tabla de rango iba creciendo. De modo que el sistema de reglas continúa comprobando las entradas de la tabla de rango. Lo siguiente es el el número 2 (shoelace *OLD*). La Relación shoelace tiene una regla, pero su entrada en la tabla de rangos no está referenciada en ninguna de las variables del árbol de traducción, de modo que se ingnora. Puesto que todas las entradas restantes en la tabla de rango, o bien no tienen reglas en pg_rewrite o bien no han sido referenciadas, se alcanza el final de la tabla de rango. La reescritura está completa y el resultado final dado se pasa al optimizador. El optimizador ignora las entradas extra en la tabla de rango que no están referenciadas por variables en el árbol de traducción, y el plan producido por el planificador/optimizador debería ser exactamente el mismo que si Al hubiese tecleado la SELECT anterior en lugar de la selección de la vista.

Ahora enfrentamos a Al al problema de que los Blues Brothers aparecen en su tienda y quieren comprarse zapatos nuevos, y como son los Blues Brothers, quieren llevar los mismos zapatos. Y los quieren llevar inmediatamente, de modo que necesitan también cordones.

Al necesita conocer los zapatos para los que tiene en el almacén cordones en este momento (en color y en tamaño), y además para los que tenga un número igual o superior a 2. Nosotros le enseñamos a realizar la consulta a su base de datos:

    al_bundy=> SELECT * FROM shoe_ready WHERE total_avail >= 2;
    shoename  |sh_avail|sl_name   |sl_avail|total_avail
    ----------+--------+----------+--------+-----------
    sh1       |       2|sl1       |       5|          2
    sh3       |       4|sl7       |       7|          4
    (2 rows)
Al es un guru de los zapatos, y sabe que sólo los zapatos de tipo sh1 le sirven (los cordones sl7 son marrones, y los zapatos que necesitan cordones marrones no son los más adecuados para los Blues Brothers).

La salida del traductor es esta vez el arbol de traducción.

    SELECT shoe_ready.shoename, shoe_ready.sh_avail,
           shoe_ready.sl_name, shoe_ready.sl_avail,
           shoe_ready.total_avail
      FROM shoe_ready shoe_ready
     WHERE int4ge(shoe_ready.total_avail, 2);
Esa será la primera regla aplicada para la relación shoe_ready y da como resultado el árbol de traducción
    SELECT rsh.shoename, 
           rsh.sh_avail,
           rsl.sl_name, 
	   rsl.sl_avail,
           min(rsh.sh_avail, rsl.sl_avail) AS 
	           total_avail
      FROM shoe_ready shoe_ready, shoe_ready *OLD*,
           shoe_ready *NEW*, 
	   shoe rsh,
           shoelace rsl
     WHERE int4ge(min(rsh.sh_avail, rsl.sl_avail), 2)
       AND (bpchareq(rsl.sl_color, rsh.slcolor)
            AND float8ge(rsl.sl_len_cm, rsh.slminlen_cm)
            AND float8le(rsl.sl_len_cm, rsh.slmaxlen_cm)
           );
En realidad, la clausula AND en la cualificación será un nodo de operadores de tipo AND, con una expresión a la izquierda y otra a la derecha. Pero eso la hace menos legible de lo que ya es, y hay más reglas para aplicar. De modo que sólo las mostramos entre paréntesis para agruparlos en unidades lógicas en el orden en que se añaden, y continuamos con las reglas para la relación shoe como está en la entrada de la tabla de rango a la que se refiere, y tiene una regla. El resultado de aplicarlo es
    SELECT sh.shoename, 
           sh.sh_avail,
           rsl.sl_name, rsl.sl_avail,
           min(sh.sh_avail, rsl.sl_avail) 
	           AS total_avail,
      FROM shoe_ready shoe_ready, shoe_ready *OLD*,
           shoe_ready *NEW*, shoe rsh,
           shoelace rsl, shoe *OLD*,
           shoe *NEW*, 
	   shoe_data sh,
           unit un
     WHERE (int4ge(min(sh.sh_avail, rsl.sl_avail), 2)
            AND (bpchareq(rsl.sl_color, sh.slcolor)
                 AND float8ge(rsl.sl_len_cm, 
                    float8mul(sh.slminlen, un.un_fact))
                 AND float8le(rsl.sl_len_cm, 
                    float8mul(sh.slmaxlen, un.un_fact))
                )
           )
       AND bpchareq(sh.slunit, un.un_name);
Y finalmente aplicamos la regla para shoelace que ya conocemos bien (esta vez en un arbol de traducción que es un poco más complicado) y obtenemos
    SELECT sh.shoename, sh.sh_avail,
           s.sl_name, s.sl_avail,
           min(sh.sh_avail, s.sl_avail) AS total_avail
      FROM shoe_ready shoe_ready, shoe_ready *OLD*,
           shoe_ready *NEW*, shoe rsh,
           shoelace rsl, shoe *OLD*,
           shoe *NEW*, shoe_data sh,
           unit un, shoelace *OLD*,
           shoelace *NEW*, 
	   shoelace_data s,
           unit u
     WHERE (  (int4ge(min(sh.sh_avail, s.sl_avail), 2)
       AND (bpchareq(s.sl_color, sh.slcolor)
           AND float8ge(float8mul(s.sl_len, u.un_fact), 
                                   float8mul(sh.slminlen, un.un_fact))
           AND float8le(float8mul(s.sl_len, u.un_fact), 
                                   float8mul(sh.slmaxlen, un.un_fact))
                     )
                )
            AND bpchareq(sh.slunit, un.un_name)
           )
       AND bpchareq(s.sl_unit, u.un_name);
Lo reducimos otra vez a una instrucción SQL real que sea equivalente en la salida final del sistema de reglas:
    SELECT sh.shoename, sh.sh_avail,
           s.sl_name, s.sl_avail,
           min(sh.sh_avail, s.sl_avail) AS total_avail
      FROM shoe_data sh, shoelace_data s, unit u, unit un
     WHERE min(sh.sh_avail, s.sl_avail) >= 2
       AND s.sl_color = sh.slcolor
       AND s.sl_len * u.un_fact >= sh.slminlen * un.un_fact
       AND s.sl_len * u.un_fact <= sh.slmaxlen * un.un_fact
       AND sh.sl_unit = un.un_name
       AND s.sl_unit = u.un_name;
El procesado recursivo del sistema de reglas reescribió una SELECT de una vista en un árbol de traducción que es equivalente a exactamente lo que Al hubiese tecleado de no tener vistas.

NotaNota
 

Actualmente no hay mecanismos de parar la recursión para las reglas de las vistas en el sistema de reglas (sólo para las otras reglas). Esto no es muy grave, ya que la única forma de meterlo en un bucle sin fin (bloqueando al cliente hasta que lea el limite de memoria) es crear tablas y luego crearles reglas a mano con CREATE RULE de forma que una lea a la otra y la otra a la una. Esto no puede ocurrir con el comando CREATE VIEW, porque en la primera creación de una vista la segunda aún no existe, de modo que la primera vista no puede seleccionar desde la segunda.

Reglas de vistas en instrucciones diferentes a SELECT

Dos detalles del arbol de traducción no se han tocado en la descripción de las reglas de vistas hasta ahora. Estos son el tipo de comando (commandtype) y la relación resultado (resultrelation). De hecho, las reglas de vistas no necesitan estas informaciones.

Hay sólo unas pocas diferencias entre un árbol de traducción para una SELECT y uno para cualquier otro comando. Obviamente, tienen otros tipos de comandos, y esta vez la relación resultado apunta a la entrada de la tabla de rango donde irá el resultado. Cualquier otra cosa es absolutamente igual. Por ello, teniendo dos tablas t1 y t2, con atributos a y b, los árboles de traducción para las dos instrucciones:

    SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a;

    UPDATE t1 SET b = t2.b WHERE t1.a = t2.a;
son prácticamente idénticos.

La consecuencia es que ambos árboles de traducción dan lugar a planes de ejecución similares. En ambas hay joins entre las dos tablas. Para la UPDATE, las columnas que no aparecen de la tabla t1 son añadidas a la lista objetivo por el optimizador, y el árbol de traducción final se lee como:
    UPDATE t1 SET a = t1.a, b = t2.b WHERE t1.a = t2.a;
Y por ello el ejecutor al correr sobre la join producirá exactamente el mismo juego de resultados que
    SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;
Pero hay un pequeño problema con el UPDATE. El ejecutor no cuidará de que el resultado de la join sea coherente. El sólo produce un juego resultante de filas. La diferencia entre un comando SELECT y un comando UPDATE la manipula el llamador (caller) del ejecutor. El llamador sólo conoce (mirando en el árbol de traducción) que esto es una UPDATE, y sabe que su resultado deberá ir a la tabla t1. Pero ¿cuál de las 666 filas que hay debe ser reemplazada por la nueva fila? El plan ejecutado es una join con una cualificación que potencialmente podría producir cualquier número de filas entre 0 y 666 en un número desconocido.

Para resolver este problema, se añade otra entrada a la lista objetivo en las instrucciones UPDATE y DELETE. Es el identificador de tupla actual (current tuple id, ctid). Este es un atributo de sistema con características especiales. Contiene el bloque y posición en el bloque para cada fila. Conociendo la tabla, el ctid puede utilizarse para encontrar una fila específica en una tabla de 1.5 GB que contiene millones de filas atacando un único bloque de datos. Tras la adición del ctid a la lista objetivo, el juego de resultados final se podría definir como

    SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
Entra ahora en funcionamiento otro detalle de >Postgres. Las filas de la tabla no son reescritas en este momento, y el por ello por lo que ABORT TRANSACTION es muy rápido. En una Update, la nueva fila resultante se inserta en la tabla (tras retirarle el ctid) y en la cabecera de la tupla de la fila cuyo ctid apuntaba a las entradas cmax y zmax, se fija el contador de comando actual y el identificador de transaccion actual (ctid). De este modo, la fila anterior se oculta tras el commit de la transacción, y el limpiador vacuum puede realmente eliminarla.

Conociendo todo eso, podemos simplemente aplicar las reglas de las vistas exactamente en la misma forma en cualquier comando. No hay diferencia.

El poder de las vistas en Postgres

Todo lo anterior demuestra como el sistema de reglas incorpora las definiciones de las vistas en el árbol de traducción original. En el segundo ejemplo, una simple SELECT de una vista creó un árbol de traducción final que es una join de cuatro tablas (cada una se utiliza dos veces con diferente nombre).

Beneficios

Los beneficios de implementar las vistas con el sistema de reglas están en que el optimizados tiene toda la información sobre qué tablas tienen que ser revisadas, más las relaciones entre estas tablas, más las cualificaciones restrictivas a partir de la definición de las vistas, más las cualificaciones de la query original, todo en un único árbol de traducción. Y esta es también la situación cuando la query original es ya una join entre vistas. Ahora el optimizador debe decidir cuál es la mejor ruta para ejecutar la query. Cuanta más información tenga el optimizador, mejor será la decisión. Y la forma en que se implementa el sistema de reglas en Postgres asegura que toda la información sobre la query está utilizable.

Puntos delicados a considerar

Hubo un tiempo en el que el sistema de reglas de Postgres se consideraba agotado. El uso de reglas no se recomendaba, y el único lugar en el que trabajaban era las reglas de las vistas. E incluso estas reglas de las vistas daban problemas porque el sistema de reglas no era capaz de aplicarse adecuadamente en más instrucciones que en SELECT (por ejemplo, no trabajaría en una UPDATE que utilice datos de una vista).

Durante ese tiempo, el desarrollo se dirigió hacia muchas características añadidas al traductor y al optimizador. El sistema de reglas fué quedando cada vez más desactualizado en sus capacidades, y se volvió cada vez más dificil de actualizar. Y por ello, nadie lo hizo.

En 6.4, alguien cerró la puerta, respiró hondo, y se puso manos a la obra. El resultado fué el sistema de reglas cuyas capacidades se han descrito en este documento. Sin embargo, hay todavía algunas construcciones no manejadas, y algunas fallan debido a cosas que no son soportadas por el optimizador de queries de Postgres.

  • Las vistas con columnas agregadas tienen malos problemas. Las expresiones agregadas en las cualificaciones deben utilizarse en subselects. Actualmente no es posible hacer una join de dos vistas en las que cada una de ellas tenga una columna agregada, y comparar los dos valores agregados en a cualificación. Mientras tanto, es posible colocar estas expresiones agregadas en funciones con los argumentos apropiados y utilizarlas en la definición de las vistas.

  • Las vistas de uniones no son soportadas. Ciertamente es sencillo reescribir una SELECT simple en una unión, pero es un poco más dificil si la vista es parte de una join que hace una UPDATE.

  • Las clausulas ORDER BY en las definiciones de las vistas no están soportadas.

  • DISTINCT no está soportada en las definiciones de vistas.

No hay una buena razon por la que el optimizador no debiera manipular construcciones de árboles de traducción que el traductor nunca podría producir debido a las limitaciones de la sintaxis de SQL. El autor se alegrará de que estas limitaciones desaparezcan en el futuro.

Efectos colaterales de la implementación

La utilización del sistema de reglas descrito para implementar las vistas tiene algunos efectos colaterales divertidos. Lo siguiente no parece trabajar:

    al_bundy=> INSERT INTO shoe (shoename, sh_avail, slcolor)
    al_bundy->     VALUES ('sh5', 0, 'black');
    INSERT 20128 1
    al_bundy=> SELECT shoename, sh_avail, slcolor FROM shoe_data;
    shoename  |sh_avail|slcolor   
    ----------+--------+----------
    sh1       |       2|black     
    sh3       |       4|brown     
    sh2       |       0|black     
    sh4       |       3|brown     
    (4 rows)
Lo interesante es que el código de retorno para la INSERT nos dió una identificación de objeto, y nos dijo que se ha insertado una fila. Sin embargo no aparece en shoe_data. Mirando en el directorio de la base de datos, podemos ver que el fichero de la base de datos para la relación de la vista shoe parece tener ahora un bloque de datos. Y efectivamente es así.

Podemos también intentar una DELETE, y si no tiene una cualificación, nos dirá que las filas se han borrado y la siguiente ejecución de vacuum limpiará el fichero hasta tamaño cero.

La razon para este comportamiento es que el árbol de la traducción para la INSERT no hace referencia a la relación shoe en ninguna variable. La lista objetivo contiene sólo valores constantes. Por ello no hay reglas que aplicar y se mantiene sin cambiar hasta la ejecución, insertandose la fila. Del mismo modo para la DELETE.

Para cambiar esto, podemos definir reglas que modifiquen el comportamiento de las queries no-SELECT. Este es el tema de la siguiente sección.