Il teatro lirico “social” Un teatro lirico gestisce gli spettacoli tramite un sistema di row-level trigger. I visitatori del sito si iscrivono e registrano le parole chiave di loro interesse. Quando (a) viene pubblicato un nuovo spettacolo (descritto con parole chiave) i visitatori interessati ricevono una di avviso, e alcuni potranno acquistare un biglietto. In caso di (b) cancellazioni o (c) cambi di orario, chi ha acquistato un biglietto riceve una notifica. Si scrivano solo i trigger relativi agli eventi descritti (a,b,c). Si assuma disponibile una funzione send-mail( Dest, Subj,... AltriAttr...) con opportuni parametri utili a comporre il messaggio. VISITATORE( Id-Vis, Nome, ) INTERESSE( Id-Vis, Keyword ) SPETTACOLO( Id-Spet, Titolo, Data, OraInizio ) DESCRIZIONE( Id-Spet, Keyword ) ACQUISTO( Id-Vis, Id-Spet, DescrizionePosti )
Il teatro lirico “social” Assumiamo che l’inserimento delle descrizioni sia contestuale a quello dello spettacolo create rule NuovoSpettacolo after insert into SPETTACOLO for each row send-mail( ( select from ( VISITATORE V join INTERESSE I on V.Id-Vis = I.Id-Vis ) join DESCRIZIONE D on D.Keyword = I.Keyword where D.Id-Spet = new.Id-Spet ), “Nuovo spettacolo”, new.Titolo, new.Data, new.OraInizio )
Il teatro lirico “social” create rule CancellazioneSpettacolo after delete from SPETTACOLO for each row send-mail( ( select from VISITATORE V join ACQUISTO A on V.Id-Vis = A.Id-Vis where A.Id-Spet = old.Id-Spet), “Cancellazione”, old.Titolo, old.Data, old.OraInizio )
Il teatro lirico “social” create rule NuovoSpettacolo after update of OraInizio on SPETTACOLO for each row send-mail( ( select from VISITATORE V join ACQUISTO A on V.Id-Vis = A.Id-Vis where A.Id-Spet = old.Id-Spet), “Cambio Orario”, old.Titolo, old.Data, new.OraInizio )
I have a drink <!ELEMENT Cocktail ( Nome, Componente+, TipoBicchiere, Guarniture, Procedimento )> Nel DTD soprastante, relativo ai dati del sito di un corso online per barman, gli elementi non ulteriormente specificati contengono solo PCData. La quantità è espressa in millilitri o grammi (per liquidi e solidi rispettivamente), e si riferisce alla dose per una persona. I nomi degli ingredienti sono univoci.
I have a drink 1) Estrarre in XQuery, per ogni ingrediente, la lista dei nomi dei cocktail che lo contengono. (2 p.) { for $i in //Ingrediente/Nome return { for $n in //Cocktail[ Componente/NomeIngr = $i ]/Nome return { $n } } }
I have a drink 1) Estrarre in XQuery, per ogni ingrediente, la lista dei nomi dei cocktail che lo contengono. (2 p.) { for $i in //Ingrediente/Nome return { //Cocktail[ Componente/NomeIngr = $i ]/Nome } }
I have a drink 2) Costruire in XQuery un documento “riassuntivo” che, per ogni cocktail, indichi: - le calorie totali del cocktail (considerando il contributo di tutti gli ingredienti) e - la lista dei nomi dei 4 ingredienti presenti in quantità maggiore (considerando equivalenti 1 ml e 1 g). In caso di parimerito sulla quantità, si prendano comunque solo 4 ingredienti, discriminando in base all’ordine alfabetico). (5 p.) <!ELEMENT Cocktail ( Nome, Componente+, TipoBicchiere, Guarniture, Procedimento )>
I have a drink { for $c in //cocktail let $compord := for $ingr in $c/Componente order by $ingr/Quantità, $ingr/NomeIngr return $ingr let $cals := for $i in $compord return $i/Quantità * //Ingrediente[Nome = $i/NomeIngr]/CaloriePerGr return { sum( $cals ) } { $compord[position() }
I have a drink 3) Definire uno schema O-O per gli stessi dati ed esprimere la query 1) in OQL. (4 p.) L’ingrediente ha le calorie/gr come proprietà: non vogliamo ripeterle nelle ricette dei cocktail: Class Cocktail ( Nome: String, Prezzo: Decimal, Ingredienti: list-of( struct( Ingrediente: * Ingrediente, Quantità: Integer ) ) ) Class Ingrediente ( Nome: String, CaloriePerGrammo: Integer ) Query1: select struct( Ingrediente: I, CocktailsCheLoContengono: ( select C.Nome from C in Cocktail where I in C.Ingredienti.Ingrediente.Nome ) ) from I in Ingrediente
Locking gerarchico Il seguente schedule è relativo a una gerarchia in cui una pagina PagA contiene le tuple t 1 e t 2 : r 1 ( PagA ), w 2 ( t 1 ), w 1 ( t 2 ) Il sistema usa il lock gerarchico. Si mostri una sequenza di richieste di lock, unlock, lock escalation e lock downgrade delle transazioni T1 e T2, tenendo conto che lo schedule deve essere compatibile con la classe 2PL.
Una sequenza compatibile è: PagA t2 t1 SIXL1(PagA)SIXL1 - - XL1(t2) [*] SIXL1 XL1 - r1(PagA) U-SL1(PagA) [lock downgrade] IXL1 XL1 - IXL2(PagA) IXL1, IXL2 XL1 - XL2(t1)IXL1, IXL2 XL1 XL2 w2(t1) U-XL2(t1)IXL1, IXL2 XL1 - U-IXL2(PagA)IXL1 XL1 - Commit(T2) w1(t2) U-XL1(t2)IXL1 - - U-ISL1(PagA) Commit(T1) [*] il “trucco” per rendere la sequenza 2PL è che T1 acquisisca da subito il diritto specifico su t2, anche se w 1 (t2) è la sua ultima operazione, di modo che quel lock resti dopo il rilascio di quello in lettura su tutta la pagina (downgrade di SIXL in IXL)
Play with me Si consideri il seguente schema relativo a un sistema di noleggio di sale prove per gruppi musicali. Le indicazioni orarie di inizio e fine sono relative a ore intere (8:00, 21:00,..., ma non 8:15). L’uso effettivo può avvenire solo in corrispondenza di una prenotazione, e al limite iniziare dopo o terminare prima degli orari della prenotazione. Inoltre, tutte le sale prove aprono alle 7:00 e chiudono tassativamente alle 24:00. Cliente (CodiceFiscale, Nome, Cognome, Tipo) Prenotazione (CodFisCliente, CodiceSala, Giorno, OraInizio, OraFine) UsoEffettivo (CodFisCliente, CodiceSala, Giorno, OraInizio, OraFine, Costo) Sala (Codice, CostoOrario) 1) Si scriva un trigger che impedisce di prenotare una sala già prenotata
Play with me Si deve intercettare l’esistenza di una prenotazione per la stessa sala che sia temporalmente sovrapposta. Possiamo usare una semantica “before” per imporre da subito il rollback della transazione. N.B.: due intervalli di tempo sono sovrapposti se uno inizia prima della fine e finisce dopo l’inizio dell’altro. create trigger TuNonPuoiPrenotare before insert into Prenotazione for each row when exists ( select * from Prenotazione where CodiceSala = new.CodiceSala and Giorno = new.Giorno and OraInizio new.OraInizio ) do rollback
Play with me Si supponga che i dati sull’uso effettivo siano inseriti solo al termine dell’uso della sala. Si propongano un arricchimento dello schema per tener traccia del numero di ore prenotate e non utilizzate da ogni cliente, e un insieme di regole che assegni il valore "Inaffidabile" al campo "tipo" dei clienti all’accumulo di 50 ore prenotate e non usate. Teoricamente si può ricalcolare il numero di ore “sprecate” con una query ad ogni verifica, senza alterare lo schema. Considerazioni di efficienza però suggeriscono di calcolare incrementalmente il numero di sprechi di ogni cliente. Ad esempio aggiungendo un attributo “OreSprecate” alla tabella Cliente. La corrispondenza tra usi effettivi e prenotazioni si sancisce assumndo che il trigger precedente garantisca la correttezza delle prenotazioni e che gli usi effettivi non violino mai i vincoli delle prenotazioni (solo chi ha prenotato può presentarsi, la prenotazione c’è di sicuro, e l’uso non inizia prima e non termina dopo gli orari indicati).
Play with me create trigger AggiornaOreSprecate after insert into UsoEffettivo for each row update Cliente set OreSprecate = OreSprecate + ( select OraFine – OraInizio – ( new.OraFine – new.OraInizio ) from Prenotazione where CodiceSala=new.CodiceSala and Giorno = new.Giorno and OraInizio>= new.OraInizio and OraFine <= new.OraFine ) where CodiceFiscale = new.CodFisCliente Questa è la soluzione più semplice: aggiorna sempre il campo, eventualmente con un contributo pari a 0 se l’utilizzatore è stato puntuale. Si può aggiungere una clausola when per intercettare i casi di contributo nullo e non effettuare l’aggiornamento
Play with me Resta poi solo da intercettare il limite di 50 ore: create trigger AggiornaTipoCliente after update of OreSprecate on Cliente for each row when old.OreSprecate = 50 do update Cliente set Tipo = “Inaffidabile” where CodiceFiscale = old.CodiceFiscale ATTENZIONE: manca del tutto il contributo di chi non si presenta affatto
Replay with me Si consideri il seguente DTD, facendo riferimento all’esercizio precedente per l’interpretazione degli elementi. Ogni elemento non ulteriormente specificato contiene solo PCData. Estrarre in XQuery, per ogni cliente che abbia qualche prenotazione poi non interamente sfruttata, la lista di tutte le sue prenotazioni non sfruttate appieno. (4 punti)
Replay with me L’unica “difficoltà” rispetto al caso relazionale è l’imposizione del vincolo che la prenotazione e l’uso fossero relativi alla stessa sala, che si esprime attraverso una relazione strutturale nel documento XML (prenotazioni e usi figli dello stesso elemento, assenza di un “codice sala” nella prenotazione). for $cli in //Cliente let $listPrenSottoutilizzate := ( for $sal in //Sala, $pre in $sal/Prenotazione[CodFisCliente = $cli/CodiceFiscale], $uso in $sal/UsoEffettivo[CodFisCliente = $cli/CodiceFiscale] where $pre/Data = $uso/Data and $pre/OraInizio >= $uso.OraInizio and $pre/OraFine <= $uso/OraFine and $pre/Data < fn:current-date() return $pre ) where count($listPrenSottoutilizzate) > 0 return { $listPrenSottoutilizzate }
La soluzione precedente ha, a rigore, il difetto di non considerare le prenotazioni totalmente dimenticate (prive di uso effettivo!). Si può perfezionare modificando la definizione di $listPrenSottoutilizzate, concatenando alle prenotazioni letteralmente sottoutilizzate la lista di quelle quelle totalmente disertate: let $listPrenSottoutilizzate := ( for $sal in //Sala. $pre in $sal/Prenotazione[CodFisCliente = $cli/CodiceFiscale], $uso in $sal/UsoEffettivo[CodFisCliente = $cli/CodiceFiscale] where $pre/Data = $uso/Data and $pre/OraInizio >= $uso/OraInizio and $pre/OraFine <= $uso/OraFine return $pre, for $sal in //Sala $pre in $sal/Prenotazione[CodFisCliente = $cli/CodiceFiscale and ( Data < fn:current-date() or Data= fn:current-date() and OraFine < fn:current-time() ) ] where count( $sal/UsoEffettivo[ CodFisCliente = $cli/CodiceFiscale and Data = $pre/Data and OraInizio >= $pre/OraInizio and OraFine <= $pre/OraFine ] ) = 0 return $pre ) Si noti che occorre escludere le prenotazioni “future” dalla seconda parte della lista, perché non possono evidentemente essere ancora associate ad alcun uso effettivo.
Update Lock Dato lo schedule r 1 (x) r 2 (x) r 3 (y) w 3 (y) w 1 (x) w 2 (y) mostrare una sequenza di richieste di lock e di unlock prodotte dalle transazioni che sia compatibile con una esecuzione 2PL, in un sistema che offre i lock SL, UL e XL (rispettivamente SharedLock, UpdateLock, ed eXclusiveLock). Si suggerisce di usare la notazione SL i (x) per indicare che la transazione T i richiede un lock SL sulla risorsa x e rel(SL i (x)) per indicare che quel lock è stato rilasciato (per evitare ambiguità tra la U di Unlock e la U di Update).
In un sistema con update lock, per evitare deadlock dovuti a tentative incrociati di upgrade SL XL, tale tipo di upgrade è vietato. Una transazione che intenda prima leggere e poi modificare una risorsa deve richiedere da subito un UL e poi effettuare l’upgrade UL XL, che è l’unico permesso. Questa situazione riguarda le transazioni 1 e 3, entre la transazione 2 effettua operazioni su risorse diverse. Affnché il sistema sia 2PL, inoltre, è necessario che la transazione 2 anticipi leggerlente l’accquisizione del lock su Y (rispetto alla scrittura w 1 (X)) per poter rilasciare in tempo il suo lock su X. Ne deriva la seguente successione di richieste e operazioni:
X Y UL 1 (x) r 1 (x) SL 2 (x) r 2 (x) UL 3 (y) r 3 (y) XL 3 (y) [upgr. U X] w 3 (y) rel( XL 3 (y) ) XL 2 (y) rel( SL 2 (x) ) XL 1 (x) [upgr. U X] w 1 (x) rel( XL 1 (x) ) w 2 (y) rel( XL 2 (y) ) Le tre transazioni possono eseguire il commit subito dopo la loro ultima operazione, senza problemi.
Componentistica Un negozio vende componenti elettronici descritti nel documento catalogo.xml. Di ogni componente si memorizza il costo e, per i componenti complessi, la struttura interna: <!ATTLIST Component Code ID #REQUIRED, Cost CDATA #REQUIRED> Dove Description è un elemento con solo contenuto PCDATA. 1.Estrarre il componente che ha il maggior numero di sotto-componenti (diretti e indiretti). 2.Costruire in XQuery un documento XML che, per ogni componente foglia (cio è privo di sottocomponenti), mostri la lista dei codici dei componenti che lo contengono, ordinati dal pi ù “ basso ” al pi ù “ alto ” nella gerarchia di contenimento.
Componentistica 1.Estrarre il componente che ha il maggior numero di sotto-componenti (diretti e indiretti) Ovviamente possiamo restringere la ricerca ai soli componenti di più alto livello, contenuti in Catalog let $max := max( for $c in doc("comps.xml")/Catalog/Component return count( $c//Component ) ) return doc("comps.xml")/Catalog/Component[ count(.//Component ) = $max ]
Componentistica 2.Costruire in XQuery un documento XML che, per ogni componente foglia (cio è privo di sottocomponenti), mostri la lista dei codici dei componenti che lo contengono, ordinati dal pi ù “ basso ” al pi ù “ alto ” nella gerarchia di contenimento. Data la natura ricorsiva dello schema, la prima idea è quella di usare una funzione di visita ricorsiva del documento che raggiunge tutti i componenti “foglia” e poi costruisce la lista dei componenti “dal basso all’alto” aggiungendoli “a ritroso” durante la fase di risalita. declare function local:reversepath($lc as element(), $root as element()) as element()* { for $c in $root/Component where = return ( local:reversepath($lc, $c), ) }; { for $lc in doc("comps.xml")//Component where count($lc/Component) = 0 return { local:reversepath($lc, doc("comps.xml")/Catalog) } }
Componentistica In alternativa, si può ragionare del fatto che, dato un generico componente “foglia”, la lista dei suoi “ancestors”, che lo contengono, può essere ordinata “dal basso all’alto” in base al numero totale di sottoelementi, (strettamente crescente man mano che si sale in gerarchia): let $foglie := doc("comps.xml")//Component[ count(./Component) = 0 ] for $f in $foglie let $padri := ( for $p in = ] let $numsucomp := count( $p//Component ) where $numsucomp > 0 order by $numsucomp return ) return { $padri }
Componentistica Oppure, si può ragionare del fatto che si possono collezionare genericamente tutti gli elementi che hanno una specifica foglia tra i sottoelementi. In questo caso occorre far leva sulla proprietà di XPath di restituire gli elementi in “document order”, e per rispettare il vincolo, ricorriamo alla funzione reverse() (che inverte gli elementi della sequenza passatale). for $l in doc("comps.xml")//Component[ count(./Component) = 0 ] return { reverse( for $c in = and count(./Component) > 0 ] return ) } Si lascia come utile esercizio al lettore un’analisi sul costo e la complessità computazionale delle varie alternative.
Componentistica 3. Si proponga uno schema a oggetti che rappresenti le stesse informazioni. (2 punti) class Catalog { Components: list of( Component * ) } class Component { Cost : Integer, Description : String, Subcomponents: list of( Component * ) } 4. Si formuli in OQL l’interrogazione che estrae il componente che ha il maggior numero di sotto-componenti diretti. (3 punti) select C from C in Component where count( C.Subcomponents ) = max( select count( X.Subcomponents ) from X in Components )