mercoledì 19 marzo 2008

Una veloce introduzione alle rules di PostgreSQL

PostgreSQL fornisce un potentissimo strumento per il query rewriting: le rules. Una rule è una regola che specifica come una particolare query DML debba essere riscritta al volo. L'utilizzo più comune delle rules si ha nelle viste: di fatto le viste sono delle informazioni su come riscrivere una query SELECT per ottenere le informazioni ricercate.
Ma altri utilizzi delle rules sono possibili: ad esempio è possibile intercettare un INSERT ed eseguire, contestualmente, un altro INSERT (es. log dei dati in inserimento) o addirittura ridirezionare l'inserimento su altre tabelle, o inibirlo totalmente. La stessa cosa si può fare per una query di UPDATE o DELETE. Va tenuto presente che le rules, seppur possano avere utilizzi molto simili a quelli di un trigger, hanno un notevole valore aggiunto rispetto a questi ultimi: esse sono infatti analizzate prima della effettiva esecuzione della query, e quindi intervengono prima che i dati siano effettivamenti letti/scritti sul database.

Come semplice esempio didattico, si consideri di avere una tabella con articoli e relativi prezzi:

CREATE TABLE articoli_con_prezzo
(
pk serial NOT NULL,
descrizione character varying,
prezzo double precision,
CONSTRAINT articolo_con_prezzo_pk PRIMARY KEY (pk)
)


sulle quali si andrà ad agire con query di inserimento simili alla seguente:

insert into articoli_con_prezzo(descrizione,prezzo) values('articolo001', 10.16);



Si supponga ora di voler rendere più modulare il design della struttura dati relativa agli articoli, separando i dati anagrafici da quelli relativi al prezzo:

CREATE TABLE articoli
(
articolipk serial NOT NULL,
descrizione text,
CONSTRAINT articoli_pkey PRIMARY KEY (articolipk)
)


CREATE TABLE prezzo
(
pk serial NOT NULL,
articoli_pk integer,
prezzo double precision,
CONSTRAINT prezzo_pk PRIMARY KEY (pk),
CONSTRAINT articoli_prezzi_fk FOREIGN KEY (articoli_pk)
REFERENCES articoli (articolipk) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)



A questo punto la tabella articoli_con_prezzo non ha più validità, e quindi ogni query eseguita su di essa deve essere riscritta per considerare il nuovo schema a due tabelle. Ovviamente cambiare tutte le query in tutti i possibili client applicativi potrebbe essere non fattibile (anche se consigliato per correttezza), quindi si può ovviare utilizzando delle rules che riscrivano al volo le query per tenere conto delle due tabelle.

Si consideri il caso della query di inserimento: la regola deve separare i dati anagrafici da quelli di prezzo e inserire i primi nella tabella articoli, e i secondi nella tabella prezzo:

CREATE OR REPLACE RULE articoli_con_prezzo_insert_rule AS
ON INSERT TO articoli_con_prezzo DO INSTEAD
(
INSERT INTO articoli (descrizione) VALUES( NEW.descrizione );
INSERT INTO prezzo( articoli_pk, prezzo ) SELECT articolipk,NEW.prezzo FROM articoli WHERE descrizione=NEW.descrizione;
)


Come si può notare, la regola si applica ad un evento di tipo INSERT e inserisce la descrizione (NEW.descrizione) nella tabella articoli e il prezzo (NEW.prezzo) nella tabella prezzo. Come è facile intuire, la pseudo-relazione NEW referenzia i dati passati alla query effettiva, e quindi con una query:


insert into articoli_con_prezzo(descrizione,prezzo) values('articolo001', 10.16);

si avrà che:

NEW.descrizione = 'articolo001'
NEW.prezzo=10.16


Si noti che la descrizione di un articolo potrebbe non essere univoca, e quindi la tecnica usata per reperire la primary key del record inserito nella tabella articoli non è particolarmente elegante e precisa, ma è sufficiente per i fini didattici di questo post.

Avendo cambiato la query di inserimento, è possibile modificare anche la query di selezione. Purtroppo è necessario creare una tabella con la stessa struttura di quella originale ed effettuare le query su di essa, avendo cura di creare una regola apposita:

CREATE TABLE articoli_con_prezzo_tmp(pk integer, descrizione varchar, prezzo float);

CREATE RULE "_RETURN" AS ON SELECT TO articoli_con_prezzo_tmp
DO INSTEAD
SELECT a.articolipk as pk, a.descrizione::varchar, p.prezzo
FROM articoli a, prezzo p
WHERE a.articolipk = p.articoli_pk

La tabella articoli_con_prezzo_tmp non deve essere riempita con i dati, serve solo come entry nel catalogo di sistema per associare ad essa la rule. Si noti che il nome della rule per il caso SELECT deve essere obbligatoriamente _RETURN (solo una rule per SELECT è possibile su ogni tabella) e che i campi in uscita devono avere lo stesso layout, nome e tipo di quelli della tabella alla quale la rule è applicata.

Nessun commento: