Obiektowe języki zapytań

Slides:



Advertisements
Podobne prezentacje
C++ wykład 2 ( ) Klasy i obiekty.
Advertisements

Język C/C++ Funkcje.
© K.Subieta. Obiektowe języki zapytań 08, Folia 1 kwiecień 2004 Obiektowe języki zapytań Wykładowca: Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik.
Wzorce.
Grażyna Mirkowska PJWSTK 15 listopad 2000
© K.Subieta. Obiektowe języki zapytań 14, Folia 1 czerwiec 2004 Obiektowe języki zapytań Wykładowca: Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik.
Bazy danych i inżynieria oprogramowania
© K.Subieta. Konstrukcja systemów obiektowych i rozproszonych 3, Folia 1 październik 2004 Konstrukcja systemów obiektowych i rozproszonych Wykładowca:
Języki programowania C++
Bazy danych II Instrukcja SELECT Piotr Górczyński 25/08/2001.
PROGRAMOWANIE STRUKTURALNE
© K.Subieta. Obiektowe języki zapytań 09, Folia 1 maj 2004 Obiektowe języki zapytań Wykładowca: Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik.
© K.Subieta. Obiektowe języki zapytań 13, Folia 1 czerwiec 2004 Obiektowe języki zapytań Wykładowca: Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik.
© K.Subieta. Obiektowe języki zapytań 07, Folia 1 kwiecień 2004 Obiektowe języki zapytań Wykładowca: Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik.
© K.Subieta. Obiektowe języki zapytań 06, Folia 1 kwiecień 2004 Obiektowe języki zapytań Wykładowca: Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik.
Domknięcie przechodnie (również) w bazach danych
PySBQL Język zapytań dla obiektowych baz danych. Aplikacje bazodanowe Główny nurt budowania aplikacji opiera się na połączeniu: SQL JDBC Java Jak wyświetlić
ZŁOŻONOŚĆ OBLICZENIOWA
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 7: Procedury i funkcje © Jan Kaczmarek.
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 8: Wykorzystanie procedur i funkcji © Jan Kaczmarek.
Język SQL – zapytania zagnieżdżone (podzapytania)
Struktury.
Wstęp do programowania obiektowego
Dr Anna Kwiatkowska Instytut Informatyki
Modele baz danych - spojrzenie na poziom fizyczny
Teoria relacyjnych baz danych
Ogólnopolski Konkurs Wiedzy Biblijnej Analiza wyników IV i V edycji Michał M. Stępień
Podstawy programowania
O relacjach i algorytmach
Podstawy układów logicznych
Automatyczne dereferencje w języku SBQL
Języki i środowiska programowania systemów rozproszonych, Wykład 06, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Języki i środowiska programowania systemów rozproszonych, Wykład 04, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Języki i środowiska programowania systemów rozproszonych, Wykład 11, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Programowanie obiektowe Wykład 6 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/14 Dariusz Wardowski.
Języki i środowiska programowania systemów rozproszonych, Wykład 10, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Języki i środowiska programowania systemów rozproszonych, Wykład 01 SBA&SBQL, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Języki i środowiska programowania systemów rozproszonych, Wykład 03, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Języki i środowiska programowania systemów rozproszonych, Wykład 07, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Języki i środowiska programowania systemów rozproszonych, Wykład 12, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Języki i środowiska programowania systemów rozproszonych, Wykład 05, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca:
Rozwiązanie zadań do zaliczenia I0G1S4 // indeks
Wybrane zagadnienia relacyjnych baz danych
Programowanie obiektowe 2013/2014
ZWIĄZKI MIĘDZY KLASAMI KLASY ABSTRAKCYJNE OGRANICZENIA INTERFEJSY SZABLONY safa Michał Telus.
EcoCondens Kompakt BBK 7-22 E.
EcoCondens BBS 2,9-28 E.
PL/SQL – dalsza wędrówka
Podstawy języka Instrukcje - wprowadzenie
User experience studio Użyteczna biblioteka Teraźniejszość i przyszłość informacji naukowej.
WYNIKI EGZAMINU MATURALNEGO W ZESPOLE SZKÓŁ TECHNICZNYCH
ZAPIS BLOKOWY ALGORYTMÓW
Testogranie TESTOGRANIE Bogdana Berezy.
Jak Jaś parował skarpetki Andrzej Majkowski 1 informatyka +
Programowanie strukturalne i obiektowe C++
Model obiektowy bazy danych
Treści multimedialne - kodowanie, przetwarzanie, prezentacja Odtwarzanie treści multimedialnych Andrzej Majkowski informatyka +
Elementy geometryczne i relacje
Projektowanie obiektowe. Przykład: Punktem wyjścia w obiektowym tworzeniu systemu informacyjnego jest zawsze pewien model biznesowy. Przykład: Diagram.
Projektowanie bazy danych z użyciem diagramów UML Obiektowe projektowanie relacyjnej bazy danych Paweł Jarecki.
Programowanie Zaawansowane
Pętle – instrukcje powtórzeń
 Formuła to wyrażenie algebraiczne (wzór) określające jakie operacje ma wykonać program na danych. Może ona zawierać liczby, łańcuchy znaków, funkcje,
Programowanie Obiektowe – Wykład 2
Obiektowe języki zapytań
Obiektowe języki zapytań
Modele baz danych - spojrzenie na poziom fizyczny
Zapis prezentacji:

Obiektowe języki zapytań Wykładowca: Kazimierz Subieta Polsko-Japońska Wyższa Szkoła Technik Komputerowych, Warszawa subieta@pjwstk.edu.pl Instytut Podstaw Informatyki PAN, Warszawa subieta@ipipan.waw.pl Wykłady 6..10

Plan wykładów 6..10 SBQL - składnia SBQL - stos rezultatów SBQL - procedura eval, zapytania elementarne i operatory algebraiczne SBQL - operatory nie-algebraiczne Rozszerzenie SBQL dla modeli M1, M2 i M3 (klas i dziedziczenia, dynamicznych ról, list eksportowych) Konstrukcje imperatywne bazujące na SBQL Perspektywy bazujące na SBQL

Wykład 6

Język SBQL (Stack-Based Query Language)

Generalne własności SBQL Język SBQL jest sformalizowanym obiektowym językiem zapytań w stylu SQL lub OQL. Posiada semantyczne odpowiedniki podstawowych konstrukcji tych języków. Może być zdefiniowany (uściślony) dla wielu modeli składu, w szczególności dla modeli M0 - M3. W odróżnieniu od relacyjnych i obiektowych algebr, rachunków, logik i innych tego rodzaju koncepcji, definicja SBQL bazuje na pojęciu stanu, którego składnikami są skład obiektów oraz stos środowisk. SBQL będziemy uważać za wzorzec teoretyczny podobny do algebry relacji (stanowiącej podstawę wielu rozważań dotyczących modelu relacyjnego). SBQL jest jednak nieporównywalnie bardziej uniwersalny i konsekwentny niż dowolna tego rodzaju algebra, włączając tzw. algebry obiektowe. Będziemy starali się wykazać, że: Języki zapytań mogą zawierać operatory nie-algebraiczne, których odwzorowanie w dowolną algebrę jest niemożliwe bez wprowadzenia poważnych ograniczeń koncepcyjnych.

Składnia SBQL (1) Przyjmiemy, że niektóre elementy wprowadzonego poprzednio zbioru V mają reprezentację „zewnętrzną”, która pozwala zapisać ten element w zapytaniu w postaci ciągu bajtów. Wiele elementów zbioru V, takich jak grafika, skompilowane procedury, itd. nie posiada reprezentacji zewnętrznej. Zwykle zewnętrzny reprezentant elementu zbioru V jest zwany w językach programowania literalem; nazwa ta została użyta w niewłaściwym znaczeniu w standardzie ODMG. Niedobrym terminem jest stała, gdyż następuje tu kolizja znaczeniowa z niemodyfikowalnym elementem składu obiektów. Wracamy do właściwych znaczeń terminów literal i stała. Terminologię ODMG odrzucamy. Zbiór literali będziemy oznaczać L. Będziemy zakładać, że istnieje prosta i jednoznaczna funkcja odwzorowująca element zbioru L w element zbioru V. Identyfikatory należące do zbioru I nie mają odpowiadających im literali.

Składnia SBQL (2) Jedyną możliwością odwołania się do obiektów znajdujących się w składzie obiektów będzie użycie ich zewnętrznej nazwy należącej do zbioru N. Przyjmiemy, że każdy element zbioru N może być użyty w zapytaniu. Dowolny literal jest zapytaniem; np. 2, 3.14, ”Kowalski” Dowolny element zbioru N jest zapytaniem; np. Osoba, Student, zarobek, wiek. Zapytania można łączyć w większe zapytania przy pomocy operatorów. Wykorzystamy tu typowy zestaw operatorów występujących w znanych językach zapytań, np. w SQL. Musimy przy tym rozróżniać leksykalną reprezentację operatora i sam operator, podobnie jak w przypadku rozróżnienia pomiędzy zbiorami L i V. Np. element leksykalny sum jest ciągiem trzech znaków, który oznacza sumującą funkcję zagregowaną. Operatory będą podzielone na unarne i binarne, oraz algebraiczne i nie-algebraiczne.

Składnia SBQL (3) Jeżeli  jest oznaczeniem operatora algebraicznego unarnego, zaś q jest zapytaniem, wówczas ( q ) jest zapytaniem. Przykładami operatorów algebraicznych unarnych są: count, sum, avg, log, -, sin, sqrt, not, itd. W niektórych sytuacjach, np. dla operatorów - oraz not, będziemy pomijać nawiasy, o ile nie doprowadzi to do niejednoznaczności. Jeżeli  jest oznaczeniem operatora algebraicznego binarnego, zaś q1 i q2 są zapytaniami, wówczas q1  q2 jest zapytaniem. Przykładami operatorów algebraicznych binarnych są: =, +, -, *, /, <. >, and, or, union, itd. Przecinek jest operatorem algebraicznym - konstruktorem struktur i kolekcji. Jeżeli  jest oznaczeniem operatora nie-algebraicznego, zaś q1 i q2 są zapytaniami, wówczas q1  q2 jest zapytaniem. Przykładami operatorów nie-algebraicznych są: selekcja (where), projekcja lub nawigacja (.), zależne złączenie (), kwantyfikatory, itd. Dla kwantyfikatorów zastosujemy tradycyjną składnię q1( q2 ) oraz q1( q2 ); jest ona równoważna podanej wyżej uniwersalnej składni q1q2 i q1q2

Składnia SBQL (4) Jeżeli q jest zapytaniem, zaś n  N, wówczas q as n jest zapytaniem. Operator as jest unarnym operatorem algebraicznym parametryzowanym nazwą n. Operator ten będziemy wykorzystywać w większości sytuacji wymagających zdefiniowania pomocniczej nazwy. Jeżeli q jest zapytaniem, zaś n  N, wówczas q group as n jest zapytaniem. Operator group as jest podobny do as, o nieco innej semantyce. Jeżeli q jest zapytaniem, to (q) jest zapytaniem. Jeżeli n N jest nazwą procedury, funkcji, lub metody posiadającej k parametrów, zaś q1, q2, ... , qk są zapytaniami, wówczas n(q1; q2; ... , qk) jest zapytaniem. Jeżeli n N jest nazwą procedury, funkcji, lub metody nie posiadającej parametrów, wówczas n() oraz n są zapytaniami. Zapytania będą podlegać ograniczeniom typologicznym, które najczęściej będą tutaj zakładane implicite.

Przykładowe zapytania 2000 "Kowalski" zarobek Osoba 2+2 zarobek > 2000 Osoba where (zarobek > 2000) (Osoba where (wiek() > 30)) . (zarobek + x + 2000/y) ((Osoba as p)  (p.pracuje_w.Dział as d)) . (p.nazwisko, d.nazwa)  Osoba (wiek < 65) Dział where ((zatrudnia.Osoba) as p (p.wiek() < 17)) (((Osoba as p)  (p.pracuje_w.Dział as d)) where (p.Nazwisko = "Nowak" and d.Nazwa = "Kontrola")) . (p.nazwisko, d.nazwa)

Podsumowanie składni SBQL zapytanie ::= L zapytanie ::= N zapytanie ::= operUna zapytanie zapytanie ::= operUna ( zapytanie ) zapytanie ::= zapytanie operBin zapytanie zapytanie ::= zapytanie operNieAlg zapytanie operNieAlg ::= where | .|  |  |  zapytanie ::= zapytanie ( zapytanie ) zapytanie ::=  zapytanie ( zapytanie ) zapytanie ::= zapytanie as N zapytanie ::= zapytanie group as N zapytanie ::= ( zapytanie ) zapytanie ::= N ( ) zapytanie ::= N ( parametr {; parametr} ) parametr ::= zapytanie L  V jest zbiorem literali N jest zbiorem nazw operUna jest operatorem algebraicznym unarnym operBin jest operatorem algebraicznym binarnym operNieAlg jest operatorem nie-algebraicznym Możliwe są dalsze operatory nie-algebraiczne Meta-nawiasy {} oznaczają iterację od 0

Drzewa syntaktyczne zapytań Jest to struktura danych, która powstaje jako rezultat rozbioru gramatycznego zapytania. Jest podstawą działania procedury eval wyznaczania rezultatu zapytania. Może być przedtem poddane przekształceniom mającym na celu optymalizację zapytania. Np. drzewo syntaktyczne zapytania: Osoba where Nazwisko = ”Nowak” Zapytanie Operator nie-algebraiczny where Zapytanie Zapytanie Nazwa Osoba Operator algebraiczny = Zapytanie Zapytanie Nazwa Nazwisko Reprezentant ”Nowak”

Stos rezultatów zapytań (QRES) Wszelkie pośrednie i końcowe rezultaty zapytań (wyrażeń) będą odkładane na stosie rezultatów QRES (Query REsult Stack). Stos rezultatów jest uogólnieniem stosu arytmetycznego spotykanego w implementacji języków programowania. Dość często osoby mało wyrobione w semantyce języków programowania plączą stos środowisk ze stosem rezultatów. Są to różne stosy, o odmiennej budowie, operacjach i przeznaczeniu. W SBA zajmujemy się obydwoma stosami. Elementami stosu będą rezultaty zapytań (elementy zbioru Rezultat) zdefiniowane poprzednio; Stos będzie przechowywał także wszelkie pomocnicze elementy niezbędne do obliczania zapytań; w szczególności, liczniki pętli iteracyjnych implikowanych przez operatory działające na kolekcjach. Elementy te nie będą uwzględniane w opisie formalnej semantyki. Stos rezultatów jest strukturą danych przechowywaną w pamięci operacyjnej. Zmienia się w trakcie obliczeń.

Przykład działania stosu rezultatów Wyrażenie Odwrotna notacja polska (2 *((5 + 3 ) / 4)) - 1 2 5 3 + 4 / * 1 - 2 5 3 2 8 4 / 2 5 + 2 8 2 4 1 - pusty 2 * 4 3

Dlaczego wprowadzamy stos QRES? QRES nie będzie przez nas uważany za składową pojęcia stanu. Dla sformułowania semantyki stos QRES ma znaczenie drugorzędne. W niektórych sformułowaniach tej semantyki (np. w semantyce denotacyjnej) QRES jest w ogóle zbędny. Jest również zbędny, jeżeli semantykę zaimplementujemy poprzez zestaw rekurencyjnych procedur. Stos QRES jest wygodny przy podejściu operacyjnym do semantyki. Doświadczenie pokazuje, że inne podejścia (np. denotacyjne) są za trudne; Stos ten uzupełnia koncepcję abstrakcyjnej implementacji, która jest zaletą podejścia stosowego. Dzięki temu nasze rozważania teoretyczne można dość łatwo zaimplementować; Stos rezultatów jest skojarzony z jego statycznym odpowiednikiem funkcjonującym podczas statycznej analizy zapytania (kompilacji). Statyczny stos rezultatów jest niezbędnym elementem optymalizatora zapytań.

Operatory działające na stosie QRES Stos QRES jest abstrakcyjną strukturą danych obsługiwaną przez cztery operatory: push (włóż nowy element na wierzchołek stosu), pop (zdejmij jeden element z wierzchołka stosu), top (odczytaj wierzchołek stosu), empty (sprawdź czy stos jest pusty). Operacje na stosie odbywają się zawsze na jego wierzchołku (uwzględniają jeden lub dwa wierzchołkowe elementy). Pozostałe elementy stosu staną się widoczne dopiero wtedy, gdy zdejmiemy odpowiednią liczbę elementów z jego wierzchołka.

Przykład zawartości stosu QRES wierzchołek stosu - jedyny widoczny element 15 i17 struct{ x(i61), y(i93) } bag{ struct{ n("Nowak"), s(i9)}, struct{ n("Stec"), s(i14)}, struct{ n("Mikuła" ), s(i18)}} niewidoczne elementy stosu dół stosu

Ogólna architektura elementów semantyki Stos środowisk ENVS Operatory niealgebraiczne Stos rezultatów QRES Ewaluacja zapytań referencje do obiektów referencje do obiektów Skład obiektów Obiekty trwałe Obiekty ulotne

Procedura ewaluacji zapytań eval (evaluation) Semantyka SBQL będzie zdefiniowana poprzez procedurę eval. Argumentem procedury eval jest dowolne zapytanie, zaś wynikiem procedury jest rezultat tego zapytania włożony na wierzchołek QRES. Procedura eval jest realizacją omawianej wcześniej zasady modularności lub kompozycyjności języka. Jest rekurencyjna, wywołuje sama siebie dla podzapytań danego zapytania. Procedura eval będzie korzystać ze składu obiektów, ENVS oraz QRES. Dla zapytań bez efektów ubocznych przyjmiemy następujące założenia: Procedura eval nie zmienia stanu składu obiektów; Procedura eval w trakcie ewaluacji zapytania q może zmieniać stan ENVS, ale po zakończeniu ewaluacji q stan ten będzie taki sam, jak na początku. Procedura eval w trakcie ewaluacji zapytania q nie zmienia tej części QRES, którą zastała w momencie rozpoczęcia ewaluacji. Semantyka będzie sterowana abstrakcyjną składnią. Procedura eval jest wspomagana przez analizator gramatyczny (parser).

Działanie procedury eval rezultat zapytania q poprzedni stan QRES poprzedni stan QRES eval( q ) Rezultaty zapytań zapisane na QRES są „konsumowane” przez operatory języka, dla których zapytania były parametrami. Takim operatorem może być print lub operator SQL delete oznaczający usunięcie danych, np.: delete Osoba where Nazwisko = ”Nowak”; Wykonanie tego polecenia oznacza ewaluację przez procedurę eval zapytania Osoba where Nazwisko = ”Nowak” , w wyniku czego na wierzchołku QRES pojawi się referencja do obiektu Nowaka. Ten rezultat zostanie „skonsumowany” przez operator delete, który następnie usunie go ze stosu QRES. Po zakończeniu kompletu operacji QRES pozostanie taki sam, jak na początku. Na koniec wszystkich obliczeń QRES będzie pusty.

Wynik procedury eval dla elementarnych zapytań Jeżeli zapytanie jest literalem l  L, to procedura eval wkłada odpowiadającą mu wartość atomową l  V na wierzchołek QRES. Jeżeli zapytanie jest nazwą n  N, to procedura eval dokonuje wiązania tej nazwy na ENVS (funkcja bind), a następnie wynik tego wiązania wkłada na wierzchołek stosu QRES. procedure eval( q : zapytanie ) begin parse( q ); (* rozbiór gramatyczny *) case q jest rozpoznane jako l  L : push( QRES, l ); case q jest rozpoznane jako n  N : push( QRES, bind( n ) ); case ..... ..... end;

Operatory algebraiczne i nie-algebraiczne Operatory łączące zapytania będziemy dzielić na algebraiczne i nie-algebraiczne. Istotą podejścia stosowego są operatory nie-algebraiczne. Fundamentalna różnica pomiędzy operatorami algebraicznymi i nie-algebraicznymi polega na ich stosunku do stosu środowisk ENVS. Operatory algebraiczne nie używają ENVS: działanie takiego operatora dotyczy wyłącznie stosu QRES (z reguły jednego lub dwóch wierzchołkowych elementów). Operatory nie-algebraiczne, oprócz działań na QRES, bezpośrednio odwołują się do konstrukcji i operacji zachodzących na ENVS.

SBQL - operatory algebraiczne

Operatory algebraiczne Cechą dowolnej algebry jest m.in. to, że w wyrażeniu x1  x2 (gdzie  jest operatorem algebry) kolejność ewaluacji argumentów x1 oraz x2 tego operatora nie ma znaczenia. Jest to zasadnicza różnica w stosunku do operatorów nie-algebraicznych. W matematycznym (denotacyjnym) sformułowaniu wynik zapytań ( q1 ) oraz q1  q2 można zapisać jako: wynik( ( q1 ) ) =  ( wynik( q1 ) )  jest implementacją  wynik( q1  q2 ) = wynik( q1 )  wynik( q2 ) Funkcja wynik jest parametryzowana stanem, ale jej definicja nie odwołuje się do tego stanu bezpośrednio.

Funkcja eval dla operatorów algebraicznych procedure eval( q : zapytanie ) begin ..... case q jest rozpoznane jako ( q1 ) lub  q1: wynik_q1: Rezultat; (* lokalna zmienna typu Rezultat *) eval( q1 ); (* rezultat q1 na wierzchołku stosu QRES *) wynik_q1 := top( QRES ); pop( QRES ); push( QRES, D ( wynik_q1 ); end; case q jest rozpoznane jako q1  q2 : wynik_q1, wynik_q2: Rezultat; (* lokalne zmienne *) eval( q1 ); (* rezultat q1 na wierzchołku stosu QRES *) eval( q2 ); (* rezultat q2 na wierzchołku stosu QRES *) wynik_q2 := top( QRES ); pop( QRES ); push( QRES, wynik_q1 D wynik_q2 ); case .....

Rodzaje operatorów algebraicznych - ogólnie Języki zapytań wprowadzają wiele operatorów algebraicznych. Granica pomiędzy operatorami „wbudowanymi” w dany język, a operatorami „dobudowanymi” na wierzchołku języka jest rozmyta. Liczba operatorów „wbudowanych” powinna być minimalna. Prawie wszystkie powinny wchodzić w skład bibliotek, które nie należą do definicji języka, ale raczej uzupełniają pewien jego aspekt lub dziedzinę zastosowań. Może być duża liczba operatorów bibliotecznych przywiązanych do specyficznych dziedzin zastosowań, np. operatory obsługujące hurtownie danych, systemy geograficzne, systemy temporalne, systemy przestrzenne, systemy oparte o XML, systemy przetwarzania języka naturalnego, itd. Nas będą interesować głównie operatory generyczne, niezależne od dziedziny zastosowań. Nie będziemy jednak przesądzać, które operatory algebraiczne należą do SBQL, a które nie należą. Przyjmiemy, że do SBQL teoretycznie należy dowolny operator algebraiczny, który jest potrzebny i implementowalny. Jeżeli zaimplementowano 100 operatorów algebraicznych, to implementacja 101-szego jest zazwyczaj bardzo prosta.

Rodzaje operatorów algebraicznych (1) Generyczny operator porównania na równość, oznaczany zwykle =, i operator odwrotny oznaczany  . Np. Nazwisko = ”Kowalski” , x = y , Zarobek  2000 , itd. Operatory te są zwykle zdefiniowane dla wszystkich typów wprowadzanych w danym języku. Porównania i operatory dotyczące liczba całkowitych i rzeczywistych: <, , >, , +, - , *, /. Np. Zarobek < 3000 , 2+2 , -(głebokosc + x) , itd. Funkcje liczbowe: część całkowita liczby, zaokrąglenie liczby, wartość bezwzględna liczby, sinus, kosinus, tangens, cotangens, logarytm dziesiętny, logarytm naturalny, funkcja wykładnicza, pierwiastek kwadratowy, itd.; Np. sqrt( x2 + y2) , sin(x+45) , itd; Porównania, operatory i funkcje na stringach znaków: porównanie na porządek leksykograficzny stringów, zawieranie się stringów, obcięcie stringów, konkatenacja stringów, zamiana wszystkich liter na wersaliki i odwrotnie, określenie długości stringu, itd. Porównanie, operatory i funkcje na datach i czasie godzinowym: porównanie dat, porównanie czasu, zmiana czasu na liczbę sekund, itd.

Rodzaje operatorów algebraicznych (2) Funkcje arytmetyczne zagregowane: sum (suma liczb), avg (średnia), min (liczba minimalna), max (liczba maksymalna), itd. Argumentem takiej funkcji jest kolekcja liczb, zaś wynikiem - pojedyncza liczba. W SQL funkcje te nie są ortogonalne (niezależne), gdyż są związane z operatorem group by. Jest to konsekwencja wadliwej koncepcji. W naszym ujęcie takie związanie jest zbędne. Przykłady: sum( Pracownik.zarobek ) , avg( bag(25, 43, 47, 11) ) . Funkcja zliczająca liczbę elementów w kolekcji (w SQL - count), funkcja usuwająca duplikaty z kolekcji (w SQL - distinct), funkcja sprawdzająca, czy kolekcja nie jest pusta (w SQL - exists). W SQL funkcje te nie są składniowo ortogonalne. Będziemy tego unikać. Przykłady: count(Pracownik) , distinct(Pracownik.zawód) , exists(Pracownik where zawód = ”analityk”) . Funkcja dereferencji; zwykle występuje implicite. Jest wywoływana wtedy, gdy pewną referencję trzeba zamienić na wartość. Np. w zapytaniu Pracownik where Zarobek > 2000 nazwa Zarobek zwróci referencję do danej Zarobek. Funkcja dereferencji jest wymuszona przez operator >. Funkcję dereferencji będziemy oznaczać dereferencja i przyjmować, że dla wartości v nie będącej referencją zwróci v.

Rodzaje operatorów algebraicznych (3) Operatory działające na multizbiorach: suma, przecięcie, iloczyn kartezjański, różnica, równość, zawieranie się, itd. Przykłady: (Prac.Nazwisko)(Emeryt.Nazwisko) (Pracownik.Zawód) bag(”analityk”) Analogiczne do powyższych, operatory na sekwencjach: konkatenacja sekwencji, obcięcie sekwencji poprzez usunięcie elementów z przodu lub z tyłu, porównanie sekwencji na równość, zawieranie się sekwencji, pobranie i- tego elementu sekwencji itd. Operatory zmiany typu i/lub reprezentacji, czyli tzw. koercje: zmiana liczby rzeczywistej na string (i odwrotnie), zmiana liczby całkowitej na rzeczywistą (i odwrotnie), zmiana sekwencji na wielozbiór (i odwrotnie), itd. W zależności od liczby typów wprowadzonych do języka liczba tych operatorów może być znaczna. Konstruktory wartość złożonych: zmiana wartości na strukturę z etykietowanymi polami, zmiana wartości na wielozbiór lub sekwencję, itd. ...... itd.

Konstruktor struktur Składnia: ( q1, q2, q3,...) gdzie q1, q2, q3,... są zapytaniami zwracającymi pojedyncze elementy - wartości atomowe, referencje lub bindery. Jeżeli qi zwraca ri, to wyrażenie ( q1, q2, q3,...) zwraca struct{ r1, r2, r3,...} Np.: (2, 3, 5) , (Nazwisko, Zar) , (Nazwisko as n, (Zar+100) as z, "p" as r) Konstruktor ten uogólnimy do specyficznego "iloczynu Kartezjańskiego": Jeżeli dowolny argument konstruktora struktury jest wielozbiorem, to pozostałe też muszą być wielozbiorami. Jeżeli wśród nich są pojedyncze elementy to automatycznie są one traktowane jako jedno-elementowe wielozbiory. Np. jeżeli Prac zwraca bag{i1, i5, i9}, Dział zwraca bag{i17, i22}, to (Prac, Dział, 3) zwraca bag{ struct{i1, i17, 3}, struct{i5, i17, 3}, struct{i9, i17, 3}, struct{i1, i22, 3}, struct{i5, i22, 3}, struct{i9, i22, 3} } Możliwe jest uogólnienie typu "outer join": jeżeli w (.., qi-1, qi, qi+1,..) qi zwraca pusty wynik, to zapytanie jest równoważne (..., qi-1, qi+1,...) .

Konstruktor wielozbiorów Składnia: bag( q1, q2, q3,...) gdzie q1, q2, q3,... są zapytaniami zwracającymi pojedyncze elementy - wartości atomowe, referencje lub bindery. Jeżeli qi zwraca ri, to wyrażenie bag( q1, q2, q3,...) zwraca bag{ r1, r2, r3,...} Np.: bag(2, 3, 5) , bag(Nazwisko, Nazwa) , bag("x" as r, "y" as r, "z" as r) Konstruktor ten uogólnimy dla argumentów będących wielozbiorami: Jeżeli wśród argumentów konstruktora są pojedyncze elementy to automatycznie są one traktowane jako jedno-elementowe wielozbiory. Np. jeżeli zapytanie Lokacja zwraca bag{ "Kielce", "Krosno" }, to zapytanie bag(Lokacja, "Radom", "Płock" ) zwraca bag{"Kielce", "Krosno", "Radom", "Płock" } W innej terminologii konstruktor bag jest sumą wielozbiorów: bag( q1, q2, q3,...) jest równoważne q1 union q2 union q3 union ... przy czym jeżeli qi zwraca element ri , to jest on traktowany jak bag{ri}.

Operator definiowania pomocniczej nazwy Wiele zapytań wymaga zdefiniowania pomocniczych nazw. Są one istotnym środkiem konceptualizacji zapytań lub programów. Zwiększają moc języka: bez nich niektóre zapytania nie dadzą się wyrazić. Występują również jako "zmienne związane kwantyfikatorami", lub "jako zmienne iteracyjne" w konstrukcjach for each ... do ... W SBA występują również jako etykiety struktur. SBQL, podobnie jak SQL, i w odróżnieniu od OQL, nie zmusza do używania pomocniczych nazw. Generalnie, inne podejścia nie radzą sobie z tym pojęciem, w związku z tym rozwiązania są niespójne. W SBA i SBQL semantyka pomocniczych nazw jest potraktowana ogólnie i precyzyjnie, dzięki temu że: Każda nazwa występująca w zapytaniu lub programie podlega tym samym regułom zakresu i wiązania, które zostały zrealizowane w postaci mechanizmu stosu środowisk. Dotyczy to także wszelkich pomocniczych nazw definiowanych wewnątrz zapytań.

Definicja nazwy jako operator algebraiczny Operator definiowania pomocniczej nazwy as jest unarnym operatorem algebraicznym parametryzowanym nazwą. Niech q będzie zapytaniem, które zwraca kolekcję bag{x1, x2, x3, ...}. Wówczas zapytanie q as n zwróci kolekcję binderów bag{ n(x1), n(x2), n(x3), ...}. Operator as etykietuje określoną nazwą każdą wartość zwróconą przez zapytanie będące jego argumentem. Operator ten nie zmienia charakteru kolekcji: w szczególności, jeżeli q zwraca sequence{x1, x2,...}, to q as n zwraca sequence{ n(x1), n(x2),...}. Operator as może być zagnieżdżany: np. jeżeli q zwraca bag{x1, x2, x3, ...}, to (q as n1) as n2 zwraca bag{ n2( n1(x1) ), n2( n1(x2) ), n2( n1(x3) ), ...} Powyższa definicja wydaje się banalna, ale okazuje się uniwersalna i precyzyjna, zwiększająca znacznie potencjał dla optymalizacji zapytań.

Przykład działania operatora as Zapytanie Wynik Prac Prac as p i1 i6 i11 p( i1 ) p( i6 ) p( i11 )

Zastosowania operatora as „Zmienne” (krotkowe, dziedzinowe, itd.) definiowane w ramach klauzulami from w językach takich jak SQL i OQL; np. Prac as p. Pokażemy, że operator zależnego złączenia znany z OQL i SQL3 jest ortogonalny w stosunku do operatora as, wobec czego nie zachodzi potrzeba definiowania go w tym szczególnym kontekście; Zmienna związana kwantyfikatorem, np. Prac as p (p.Zar > 10000); Etykiety składników struktur zwracanych przez zapytanie, np. Prac.( Nazwisko as n, Zar as z ) ; Kursor w zdaniu for each; np. for each Prac as p do p.Zar := p.Zar +100; Definicja nowych nazw atrybutów dla wirtualnej perspektywy (view); np: create view BogatyProjektant { return (Prac where Stan = ”projektant” and Zar > 10000). (Nazwisko as N, PracujeW as NrD) };

Operator group as Podobny do operatora as. Jeżeli q zwraca pewną wartość r (w szczególności, kolekcję), to q group as n zwraca pojedynczy binder n( r ). W odróżnieniu od operatora as, operator group as przypisuje nazwę n do rezultatu całości zapytania, a nie do poszczególnych elementów kolekcji zwracanej przez to zapytanie. Operator group as powstał w wyniku prób sformalizowania mętnej semantycznie klauzuli group by języka OQL. Głównym motywem była "ortogonalizacja" tej klauzuli. I to się udało. bag{ i1, i6, i11 } Prac Zapytanie Wynik Prac group as p p( bag{ i1, i6, i11 } )

Wykład 7

SBQL - operatory nie-algebraiczne

Dlaczego "nie-algebraiczne"? (1) Do nich należą operator where, operator kropki, kwantyfikatory, zależne złączenie, sortowanie (order by), i inne. Wszystkie są binarne. Mimo podobieństwa do operatorów algebraicznych, semantyka operatorów nie-algebraicznych nie da się prosto sprowadzić do algebry. To zdanie może wydawać się niejasne. W modelu relacyjnym operatory selekcji (operator where), projekcji (operator kropki) oraz złączenia są przecież traktowane jako operatory algebraiczne algebry relacji. Tu właśnie jest nieporozumienie. Takie traktowanie jest możliwe wyłącznie przy ograniczonej funkcjonalności, oraz po przyjęciu triku formalnego. Trik polega na tym, że część semantyki jest przenoszona na poziom metajęzykowy. Operatory te są dodatkowo kwalifikowane wyrażeniem metajęzykowym. Np. w wyrażeniu algebry relacyjnej: Zar>1000( Prac ) operator selekcji  jest kwalifikowany wyrażeniem metajęzykowym Zar >1000. Operator selekcji nie jest pojedynczym operatorem, lecz nieskończoną rodziną zawierającą tyle operatorów, ile jest warunków selekcji.

Dlaczego "nie-algebraiczne"? (2) Powyższy trik można uważać za uzasadniony w przypadku, gdy wyrażenie metajęzykowe parametryzujące operator jest proste, a jego semantyka jest oczywista. Nie zawsze to jest prawda. Operator selekcji może mieć bardziej złożony warunek selekcji, np. ZarobekNetto( Zar + 100 ) >1000( Prac ) Warunek selekcji zawiera operator + oraz wywołuje pewną funkcję ZarobekNetto. Wyrażenie metajęzykowe posiada nietrywialną wewnętrzną semantykę, która jest nieformalna. Jeżeli jakikolwiek składnik języka nie ma formalnej semantyki, to cały język również nie ma formalnej semantyki. Mimo podobieństwa wizualnego, w powyższych wyrażeniach nazwy Prac oraz Zar są ulokowane w dwóch różnych światach. Pierwsza nazwa należy do języka algebry relacji, jest „pierwszej kategorii”, podlega formalnemu traktowaniu. Natomiast druga nazwa należy do metajęzyka algebry relacji, jest „drugiej kategorii”, nie podlega formalnemu traktowaniu.

Dlaczego "nie-algebraiczne"? (3) W ten sposób złamana została zasada relatywizmu, zgodnie z którą nazwy nie mogą posiadać fundamentalnie różnej semantyki w zależności od tego, jakiego rodzaju dane oznaczają. Ta zasada staje się szczególnie istotna dla języków obiektowych, gdyż obiekty mogą mieć strukturę hierarchiczną, zaś nazwy mogą oznaczać dane na dowolnym poziomie hierarchii obiektów. Podobny problem dotyczy operatorów. Operator selekcji  jest elementem języka tej algebry, należy do „pierwszej kategorii”, podczas gdy operator < występuje w metajęzyku, jest „drugiej kategorii”. Powyższa językowo-meta-językowa schizofrenia podważa poprawność definicji semantyki. Podane argumenty są wystarczające aby twierdzić, że tzw. algebry obiektowe są pseudo-matematyczną bzdurą (włączając algebry dla XML). Paradoksalnie, są one motywowane koniecznością zbudowania „solidnych podstaw matematycznych” obiektowych języków zapytań.

Dlaczego "nie-algebraiczne"? (4) W podejściu stosowym dowolne operatory nie są indeksowane wyrażeniami meta-językowymi. Nie występuje więc semantyczna schizofrenia nazw dzieląca je na „językowe” i „meta-językowe”. Nie ma podziału na nazwy „pierwszej kategorii” i „drugiej kategorii”. Każda nazwa ma dokładnie taką samą semantykę i podlega dokładnie tym samym regułom wiązania i zakresu Podobnie z operatorami: nie występuje semantyczne zróżnicowanie operatorów: operator < występuje na tym samym poziomie semantycznym jak operator selekcji where. Koncepcja operatorów nie-algebraicznych jest bardzo prosta oraz ma dobrze ugruntowane korzenie w semantyce języków programowania. Definicja operatorów nie-algebraicznych będzie się nieco różniła w zależności od tego, który modelu składu (M0 - M3) będzie rozpatrywany. Wszystkie te definicje będą bazowały na podanej niżej podstawowej definicji dla modelu M0.

Opis procedury eval dla operatora nie-algebr.  Aby dokonać ewaluacji zapytania q1  q2 wykonaj następujące czynności: Dokonaj ewaluacji zapytania q1. Zapytanie to zwraca wielozbiór elementów. Dla każdego elementu e należącego do wyniku q1 wykonaj następujące czynności: Oblicz wartość funkcji nested( e ). Wynik jest zbiorem binderów. Włóż obliczony zbiór binderów jako nową sekcje na wierzchołek stosu ENVS. Dokonaj ewaluacji zapytania q2 w tym nowym środowisku. Oblicz wynik cząstkowy dla danego elementu e poprzez połączenie e z wynikiem zwróconym przez q2. Funkcja łącząca zależy od operatora . Usuń nowo wstawioną górną sekcję ze stosu ENVS. Zsumuj wszystkie wyniki cząstkowe w wynik końcowy. Sposób sumowania sumuj ( U ) zależy od rodzaju operatora . Stan stosu środowisk ENVS po zakończeniu ewaluacji jest taki sam, jak przez rozpoczęciem ewaluacji.

Formalny zapis procedury eval dla oper. niealgebr. procedure eval( q : zapytanie ) begin ...... case q jest rozpoznane jako q1  q2 : (* q1 , q2 są zapytaniami,  jest operatorem nie-algebraicznym *) wyniki_pośr: bag of Rezultat; (* lokalna kolekcja wyników pośrednich *) wynik_pośredni: Rezultat; (* lokalna zmienna na wynik pośredni *) wynik_końcowy: Rezultat; (* lokalna zmienna na wynik końcowy *) e: Rezultat; (* lokalna zmienna na element kolekcji zwracanej przez q1 *) wyniki_pośr := ; (* zerowanie kolekcji wyników pośrednich *) eval( q1 ); (*q1 zwraca kolekcję elementów; wynik q1 na czubku stosu QRES *) for each e in top( QRES ) do (* iteracja po wszystkich elementach wyniku q1 *) push( ENVS, nested( e ) ); (* nowa sekcja na stosie środowisk *) eval( q2 ); (* wynik q2 na czubku stosu QRES *) wynik_pośredni := połącz ( e, top( QRES ) ); (* połączenie e z wynikiem q2; zależne od  *) wyniki_pośr := wyniki_pośr U { wynik_pośredni }; (* akumulacja wyniku pośredniego *) pop( QRES ); (* usuniecie z QRES wyniku q2 *) pop( ENVS ); (* usuniecie z ENVS nowej sekcji *) end; wynik_końcowy := sumuj ( wyniki_pośr ); (* zsumowanie wyników pośrednich; zależne od  *) pop( QRES ); (* usuniecie z QRES wyniku q1 *) push( QRES, wynik_końcowy ); (* włożenie na QRES końcowego wyniku *) .......

Poglądowy obraz małej bazy danych i1 Prac i5 Prac i9 Prac i2 Nazwisko ”Nowak” i6 Nazwisko ”Kowalski” i10 Nazwisko ”Barski” i3 Zar 2500 i7 Zar 2000 i11 Zar 900 i4 PracujeW i12 Adres i8 PracujeW i13 Miasto ”Radom” i14 Ulica ”Wolska” i15 NrDomu 12 i16 PracujeW i17 Dział i22 Dział i18 Nazwa ”Produkcja” i23 Nazwa ”Sprzedaż” i19 Lokacja ”Kielce” i24 Lokacja ”Radom” i20 Lokacja ”Kraków” i25 Zatrudnia i21 Zatrudnia i26 Zatrudnia

Operator where (selekcja) Składnia: q1 where q2 Ograniczenie: podzapytanie q2 zwraca wartość prawda lub fałsz. Semantyka Dla każdego elementu e zwróconego przez q1, ENVS jest podwyższany o nested(e) Następnie ewaluowane jest q2 Po ewaluacji q2 stos ENVS wraca do poprzedniego stanu e należy do ostatecznego wyniku wyłącznie wtedy, gdy q2 zwróciło prawda. Objaśnienie funkcji eval Funkcja połącz: dla danego e należącego do wyniku q1 zwraca jednoelementowy wielozbiór { e } w przypadku, gdy dla tego e podzapytanie q2 zwróciło prawda, lub pusty wielozbiór { }, jeżeli podzapytanie q2 zwróciło fałsz. Funkcja sumuj: sumuje (mnogościowo) wszystkie wyniki pośrednie. Przykład: Prac where ( Zar > 1000 )

Operator where - ilustracja działania Rezultat zwracany przez zapytanie Prac (wiązanie Prac) Iteracja po elementach e poprzedniego rezultatu: na ENVS wkłada się nested(e) Rezultat zwracany przez zapytanie Zar (wiązanie Zar) Rezultat dereferencji wymuszanej przez operator > Rezultat zwracany przez zapytanie 1000 Rezultat zwracany przez zapytanie Zar>1000 Końcowy rezultat zapytania Nazwisko(i2) Zar(i3) PracujeW(i4) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) i1 i5 i9 i3 i7 i11 2500 2000 900 1000 prawda fałsz i1 i5 Nazwisko(i6) Zar(i7) PracujeW(i8) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Stan stosu ENVS przed ewaluacją Nazwisko(i10) Zar(i11) Adres(i12) PracujeW(i16) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Prac where ( Zar > 1000 )

Operator kropki (projekcja, nawigacja) Składnia: q1 . q2 Semantyka Dla każdego elementu e zwróconego przez q1, ENVS jest podwyższany o nested(e) Następnie ewaluowane jest q2 Po ewaluacji q2 stos ENVS wraca do poprzedniego stanu Ostateczny wynik jest sumą mnogościową wyników q2 Objaśnienie funkcji eval Funkcja połącz: ignoruje e; zwraca wynik podzapytania q2. Funkcja sumuj: sumuje (mnogościowo) wszystkie wyniki pośrednie. Przykład: Prac . Zar Operator kropki przykrywa tzw. wyrażenia ścieżkowe (path expressions) w najbardziej uniwersalnej postaci, pozwalając je jednocześnie dowolnie kombinować z innymi operatorami.

Operator kropki - ilustracja działania Rezultat zwracany przez zapytanie Prac (wiązanie Prac) Iteracja po elementach e poprzedniego rezultatu: na ENVS wkłada się nested(e) Rezultat zwracany przez zapytanie Zar (wiązanie Zar) Końcowy rezultat zapytania Nazwisko(i2) Zar(i3) PracujeW(i4) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) i1 i5 i9 i3 i7 i11 i3 i7 i11 Nazwisko(i6) Zar(i7) PracujeW(i8) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Stan stosu ENVS przed ewaluacją Nazwisko(i10) Zar(i11) Adres(i12) PracujeW(i16) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Prac . Zar

Operator zależnego złączenia Składnia: q1  q2 Semantyka Dla każdego e zwróconego przez q1, ENVS jest podwyższany o nested(e) Następnie ewaluowane jest q2 Po ewaluacji q2 stos ENVS wraca do poprzedniego stanu Ostateczny wynik jest sumą mnogościową wszystkich struktur, w których na początku jest e, zaś dalej jest element wyniku q2 zwrócony dla tego e. Objaśnienie funkcji eval Funkcja połącz: zarówno e jak i każdy element e2 zwracany przez q2 traktuje jako struktury (jednoelementowe lub wieloelementowe). Dla każdego e2 zwracanego przez q2 tworzy strukturę poprzez połączenie e oraz e2. Wynikiem pośrednim jest kolekcja wszystkich takich struktur. Funkcja sumuj: sumuje (mnogościowo) wszystkie wyniki pośrednie. Przykład: Prac  Zar Zależne złączenie jest zdefiniowane w ODMG OQL (klauzula from) w znacznie ograniczonej postaci w stosunku do powyższej definicji.

Operator zależnego złączenia - ilustracja działania Rezultat zwracany przez zapytanie Prac (wiązanie Prac) Iteracja po elementach e poprzedniego rezultatu: na ENVS wkłada się nested(e) Rezultat zwracany przez zapytanie Zar (wiązanie Zar) Końcowy rezultat zapytania Nazwisko(i2) Zar(i3) PracujeW(i4) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) i1 i5 i9 struct(i1, i3) struct(i5, i7) struct(i9, i11 ) i3 i7 i11 Nazwisko(i6) Zar(i7) PracujeW(i8) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Stan stosu ENVS przed ewaluacją Nazwisko(i10) Zar(i11) Adres(i12) PracujeW(i16) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Prac  Zar

Operator sortowania Składnia: q1 order by q2 Semantyka Wykonywane jest zapytanie: q1  dereferencja( q2 ) Wynik (bag) jest sortowany według części struktur zwróconej przez q2 . Po posortowaniu wynik jest sekwencją. Końcowy wynik uzyskuje się poprzez projekcję tej sekwencji (bez zmiany kolejności elementów na części struktur zwrócone przez q1 . Np. Prac order by Nazwisko Prac order by ((PracujeW.Dział.Nazwa), Zarobek) Operator ten można dodatkowo wyposażyć w kwalifikatory asc (wzrastająco) i desc (malejąco) przy każdej składowej q2. Np. Prac order by ((PracujeW.Dział.Nazwa) asc, Zarobek desc) Operator asc jest komentarzem, operator desc jest odwrotnością wartości: np. 5 desc oznacza -5, "abceg" desc oznacza "zyxvt", itd. Operator ten należy parametryzować (najlepiej konfiguracyjnie) funkcją porównania elementów (zależną od języka: angielski, polski, niemiecki,.. ).

Kwantyfikator egzystencjalny Składnia:  q1 ( q2 ) lub q1  q2 Ograniczenie: podzapytanie q2 zwraca wartość prawda lub fałsz. Semantyka Dla każdego e zwróconego przez q1, ENVS jest podwyższany o nested(e) Następnie ewaluowane jest q2 Po ewaluacji q2 stos ENVS wraca do poprzedniego stanu Ostateczny wynik jest prawda wtedy i tylko wtedy, jeżeli dla co najmniej jednego e podzapytanie q2 zwróciło prawda. Objaśnienie funkcji eval Funkcja połącz: ignoruje e; zwraca wynik podzapytania q2. Funkcja sumuj: Zwraca prawda jeżeli co najmniej jeden wynik pośredni zwrócony przez q2 jest prawda; w przeciwnym wypadku zwraca fałsz. Przykład:  Prac ( Zar > 1000 )

Kwantyfikator uniwersalny Składnia:  q1 ( q2 ) lub q1  q2 Ograniczenie: podzapytanie q2 zwraca wartość prawda lub fałsz. Semantyka Dla każdego e zwróconego przez q1, ENVS jest podwyższany o nested(e) Następnie ewaluowane jest q2 Po ewaluacji q2 stos ENVS wraca do poprzedniego stanu Ostateczny wynik jest prawda wtedy i tylko wtedy, jeżeli dla wszystkich e podzapytanie q2 zwróciło prawda. Jeżeli q1 zwróciło pusty wielozbiór, to wynik także jest prawda. Objaśnienie funkcji eval Funkcja połącz: ignoruje e; zwraca wynik podzapytania q2. Funkcja sumuj: Zwraca fałsz jeżeli co najmniej jeden wynik pośredni zwrócony przez q2 jest fałsz ; w przeciwnym wypadku zwraca prawda. Przykład: Prac ( Zar > 1000 )

Kwantyfikator uniwersalny - ilustracja działania Rezultat zwracany przez zapytanie Prac (wiązanie Prac) Iteracja po elementach e poprzedniego rezultatu: na ENVS wkłada się nested(e) Rezultat zwracany przez zapytanie Zar (wiązanie Zar) Rezultat dereferencji wymuszanej przez operator > Rezultat zwracany przez zapytanie 1000 Rezultat zwracany przez zapytanie Zar>1000 Końcowy rezultat zapytania Nazwisko(i2) Zar(i3) PracujeW(i4) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) i1 i5 i9 i3 i7 i11 2500 2000 900 1000 prawda Nazwisko(i6) Zar(i7) PracujeW(i8) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) prawda fałsz Stan stosu ENVS przed ewaluacją Nazwisko(i10) Zar(i11) Adres(i12) PracujeW(i16) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) fałsz Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Prac  ( Zar > 1000 )

Kroki ewaluacji zapytania z pomocniczą nazwą Rezultat zwracany przez zapytanie Prac Rezultat zwracany przez zapytanie Prac as x Iteracja po elementach e poprzedniego rezultatu: na ENVS wkłada się nested(e) Rezultat zwracany przez zapytanie x (wiązanie x) Iteracja po elementach e poprzedniego rezultatu: na ENVS wkłada się nested(e) Rezultat zwracany przez zapytanie Zar Rezultat dereferencji wymuszanej przez operator > Rezultat zwracany przez zapytanie 1000 Rezultat zwracany przez zapytanie Zar>1000 Końcowy rezultat zapytania Nazwisko(i2) Zar(i3) PracujeW(i4) x(i1) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) Nazwisko(i6) Zar(i7) PracujeW(i8) x(i5 ) Nazwisko(i10) Zar(i11) Adres(i12) PracujeW(i16) x(i9) i1 i5 i9 x(i1) x(i5 ) x(i9) i1 i5 i9 i3 i7 i11 2500 2000 900 1000 prawda fałsz x(i1) x(i5 ) x(i1) Prac(i1) Prac(i5) Prac(i9) Dział(i17) Dział(i22) x(i5 ) x(i9) (Prac as x) where ( ( x . Zar ) > 1000 )

Zamiana "zmiennej" na etykietę struktury Dla zapytania (Prac as x) where (( x . Zar ) > 1000 ) końcowy wynik jest różny od wyniku zapytania Prac where Zar > 1000 , mianowicie, elementy wyniku są opatrzone nazwą x. Elementy takie można uważać za proste struktury (w sensie języków C/C++), których jedynym polem jest pole o nazwie x. W standardzie ODMG są "tajemnicze" miejsca, w których zmienna dziedzinowa zmienia się w etykietę struktury. Standard tego nie wyjaśnia. Dopiero na gruncie SBA widać jasno, dlaczego tak się dzieje. Wymagało to jednak bardzo istotnych założeń odnośnie semantyki. Standard ODMG jest semantycznie nieprecyzyjny, więc nie jest w stanie tego wyjaśnić. Tego efektu nie można także wyjaśnić na gruncie „algebry obiektowej”, „dziedzinowego rachunku obiektowego”, lub innego tradycyjnego formalizmu. Można pokazać, że zapytanie Prac where Zar > 1000 jest równoważne zapytaniu ((Prac as x) where (( x . Zar ) > 1000 )) . x

SBQL - schematy BD dla przykładów zapytań Schemat relacyjny Adres NrP Miasto Ulica NrDomu Prac NrP Nazwisko Stan Zar PracujeW Dział NrD Nazwa Szef Lokacje NrD Lokacja Strzałki modelują asocjacje; prowadzą od klucza obcego do głównego Schemat obiektowy (diagram klas) Prac[0..*] NrP Nazwisko Stan Zar PracujeW Zatrudnia[1..*] Dział [0..*] NrD Nazwa Lokacja[1..*] Kieruje[0..1] Szef Adres [0..1] Miasto Ulica NrDomu Asocjacje są zrealizowane jako (bliźniacze) obiekty pointerowe

SBQL - przykłady zapytań (1) Podaj pełną informację o pracownikach: Prac Jest to odpowiednik zapytania SQL: select * from Prac. Wbrew popularnym opiniom, lukier select ... from ... będziemy uważać za szkodliwy. Różnice semantyczne: zapytanie SQL zwraca tabelę Prac, podczas gdy Prac zwraca referencje do obiektów Prac. Zapytania SBQL nigdy nie zwracają obiektów. Podaj nazwiska wszystkich pracowników: Prac . Nazwisko Zapytanie jest odpowiednikiem zapytania SQL: select Nazwisko from Prac. Zapytanie SQL zwraca jedno-kolumnową tablicę stringów będących nazwiskami, natomiast zapytanie SBQL zwraca tablicę referencji do pod-obiektów Nazwisko w obiektach Prac. Do tej tablicy można oczywiście zastosować operator dereferencji, który referencje na stringi, ale automatyczna dereferencja prowadzi do straty informacji. Referencje są bardziej uniwersalne niż stringi, gdyż. np. mogą być użyte po lewej stronie operacji podstawienia.

SBQL - przykłady zapytań (2) Podaj pełną informację o Kowalskim: Prac where (Nazwisko = ”Kowalski”) SQL: select * from Prac where Nazwisko = ‘Kowalski’. W odróżnieniu od SQL, zapytanie w SBQL zwróci referencję do obiektu Kowalskiego. Referencję tę można następnie „skonsumować” zdaniem imperatywnym; np. można usunąć obiekt Kowalskiego zdaniem delete Prac where (Nazwisko = ”Kowalski”); W dalszych przykładach będziemy często rezygnować z nawiasów. Podaj zarobek Kowalskiego: (Prac where Nazwisko = ”Kowalski”) . Zar SQL: select Zar from Prac where Nazwisko = ‘Kowalski’. W odróżnieniu od SQL, zapytanie w SBQL zwróci referencję do zarobku Kowalskiego. Referencję tę można następnie „skonsumować” zdaniem imperatywnym; np. można zmienić zarobek Kowalskiego zdaniem: ((Prac where Nazwisko = ”Kowalski”) . Zar) := 5000; Odpowiada to zdaniu aktualizacyjnemu SQL: update Prac set Zar = 5000 where Nazwisko = ‘Kowalski’;

SBQL - przykłady zapytań (3) Podaj numery i nazwiska pracowników zarabiających więcej niż 1000. (Prac where Zar > 1000) . (NrP, Nazwisko) Wynikiem zapytania jest wielozbiór struktur struct{iNrP, iNazwisko}, gdzie w każdej strukturze pierwsza referencja dotyczy atrybutu NrP, zaś druga - atrybutu Nazwisko. Przecinek oznacza operator algebraiczny konstruktora struktury. Zwróć referencję do danej pointerowej PracujeW dla pracownika Kowalskiego: (Prac where Nazwisko = ”Kowalski”) . PracujeW Zapytanie nie ma odpowiednika w SQL i OQL. Zapytanie takie ma jednak sens, gdyż może być podzapytaniem szerszego zapytania. Ma również sens z powodu operacji aktualizacyjnych. Przykładowo, jeżeli chcielibyśmy przenieść Kowalskiego do działu Sprzedaż, wówczas takie zdanie może mieć postać: (Prac where Nazwisko=”Kowalski”).PracujeW := &(Dział where Nazwa=”Sprzedaż”) Z lewej strony podstawienia obliczana jest l-wartość (l-value), czyli referencja do danej pointerowej PracujeW w obiekcie Kowalskiego. Z prawej strony podstawienia mamy r-wartość (r-value), która jest referencją do działu Sprzedaż.

SBQL - przykłady zapytań (4) Podaj pełne dane o dziale, w którym pracuje Kowalski: ((Prac where Nazwisko = ”Kowalski”) . PracujeW ) . Dział Zapytanie to zwraca referencję do obiektu działu, w którym pracuje Kowalski. OQL: select d from Prac as p, p.PracujeW as d where p.Nazwisko = ”Kowalski” OQL unika nazwy Dział. Jest to niewłaściwe z dwóch powodów. (1) Jak pokazuje poprzedni przykład, istnieje potrzeba rozróżnienia pomiędzy referencją do pointera prowadzącego do obiektu, a referencją do samego obiektu. (2) Zapytanie w SBQL jest bardziej czytelne, gdyż explicite używa nazwy Dział, oznaczającej końcowy efekt ewaluacji. Podaj nazwę działu, w którym pracuje Kowalski: (Prac where Nazwisko = ”Kowalski”) . PracujeW . Dział . Nazwa Zapytanie to zwraca referencję do nazwy działu, w którym pracuje Kowalski. OQL: select p.PracujeW.Nazwa from Prac as p where p.Nazwisko = ”Kowalski” Przykład ilustruje tzw. wyrażenia ścieżkowe (path expressions), czyli nawigację wzdłuż ścieżki wyznaczonej powiązaniami pointerowymi lub w dół hierarchii obiektów. W SBQL takie wyrażenia są efektem definicji kropki - wyrażenie czytamy jako (((Prac where Nazwisko = ”Kowalski”) . PracujeW) . Dział) . Nazwa

SBQL - przykłady zapytań (5) Wyrażenia ścieżkowe mogą być dowolnie długie. Np. nazwisko szefa Kowalskiego: (Prac where Nazwisko = ”Kowalski”) . PracujeW . Dział . Szef . Prac . Nazwisko Nie definiujemy specjalnych wyrażeń ścieżkowych, lecz wykorzystujemy operator kropki. Uzyskujemy przez to możliwość dowolnego kombinowania wyrażeń ścieżkowych z innymi operatorami. Przykładowo, poniższe wyrażenie SBQL (Prac where ”budynek D”  (PracujeW.Dział.Lokacja)).(Nazwisko, (Adres.Miasto)) specyfikuje nazwisko i miasto pracownika pracującego w budynku D. ODMG OQL ogranicza możliwość używania wygodnych wyrażeń ścieżkowych poprzez niezbyt mądry w tym kontekście lukier select...from...where... oraz poprzez przyjęcie (również niezbyt mądrego) założenia, że operator kropki może się pojawić tylko wtedy, jeżeli wyrażenie przed kropką zwraca dokładnie jedną wartość. Obydwa te założenia są implicite odrzucone przy definiowaniu operatorów niealgebraicznych.

SBQL - przykłady zapytań (6) Podaj wszystkie informacje o pracownikach zarabiających więcej od Kowalskiego: Prac where Zar > ((Prac where Nazwisko = ”Kowalski”).Zar) SQL: select * from Prac where Zar > select Zar from Prac where Nazwisko = ”Kowalski” W zapytaniu tym występuje dwa razy nazwa Zar, ale dzięki stosowej semantyce każde z tych wystąpień jest inaczej wiązane: pierwsze Zar jest wiązane na stosie posiadającym 2 sekcje, drugie Zar na stosie posiadającym 3 sekcje. Dla każdego pracownika zwróć pełną informację o pracowniku i jego dziale. Prac  (PracujeW . Dział) Skorzystaliśmy z operatora zależnego złączenia. Wynikiem jest wielozbiór struktur struct{ iPrac, iDział }, gdzie pierwszy składnik każdej struktury jest referencją do obiektu pracownika, zaś drugi jest referencją do obiektu jego działu. Zapytanie to ma odpowiednik w OQL: select struct(pr: p, dz: d) from Prac as p, p.PracujeW as d Nie jest to dokładny odpowiednik, ponieważ w OQL struktury muszą mieć etykiety (tutaj pr i dz), a ponadto OQL nie wprowadza referencji.

SBQL - przykłady zapytań (7) Dla każdego pracownika zwróć pełną informację o pracowniku i jego dziale. Analogiczne zapytanie odnoszące się do struktury relacyjnej ma postać: Prac  (Dział where PracujeW = NrD) lub (z użyciem produktu kartezjańskiego) (Prac  Dział ) where PracujeW = NrD To ostatnie zapytanie ma odpowiednik w SQL: select * from Prac, Dział where PracujeW = NrD Nie jest to dokładny odpowiednik, ponieważ wynikiem nie jest wielozbiór z parami referencji (jak w przypadku SBQL), lecz zbiorcza tabela zawierająca zarówno atrybuty tabeli Prac jak i tabeli Dział.

SBQL - przykłady zapytań (8) Podaj informację o działach i średnich zarobkach pracowników w działach: Dział  avg(Zatrudnia.Prac.Zar) Wynikiem zapytania jest wielozbiór struktur struct{ iDział, średni_zar }, gdzie pierwszy składnik każdej struktury jest referencją do obiektu Dział, zaś drugi jest liczbą będącą średnią zarobków w tym dziale. Następny slajd przedstawia stany stosu środowisk przy wiązaniu poszczególnych nazw występujących w tym zapytaniu. W OQL zapytanie to wymaga użycia opcji group by, która została wyspecyfikowana nieprecyzyjnie, toteż nie ma pewności jak jej użyć. Podobne zapytanie można sformułować przy pomocy następującego zdania SQL: select d.*, avg(p.Zar) from Dział d, Prac p where d.NrD = p.PracujeW group by d.NrD Wadą jest konieczność użycia operatora group by, który jest nieortogonalny, prowadzi do nowego jakościowo warunku (having), ma rafy semantyczne, oraz sprawia trudności z optymalizacją zapytań. W SBQL unikamy tego wątpliwego operatora, również dla struktur relacyjnych: Dział  avg((Prac where PracujeW = NrD ). Zar)

SBQL - przykłady zapytań (9) Stany stosu środowisk przy wiązaniu nazw występujących w zapytaniu Dział  avg(Zatrudnia.Prac.Zar) ( Dział  avg((Zatrudnia . Prac ) . Zar ) ) Prac(..) Prac(..), ... Dział(..) Dział(..) ... Nrd(..), Nazwa(..) Lokacja(..) Lokacja(..) ... Zatrudnia(..) Zatrudnia(..) ... Szef(..) Prac(..) Prac(..), ... Dział(..) Dział(..) ... Prac(..) Nrd(..), Nazwa(..) Lokacja(..) Lokacja(..) ... Zatrudnia(..) Zatrudnia(..) ... Szef(..) Prac(..) Prac(..), ... Dział(..) Dział(..) ... Nrd(..), Nazwa(..) Lokacja(..) Lokacja(..) ... Zatrudnia(..) Zatrudnia(..) ... Szef(..) Prac(..) Prac(..), ... Dział(..) Dział(..) ... NrP (..) Nazwisko(..) Stan(..) Zar(..) Adres(..) PracujeW(..) Kieruje(..) Nrd(..), Nazwa(..) Lokacja(..) Lokacja(..) ... Zatrudnia(..) Zatrudnia(..) ... Szef(..) Prac(..) Prac(..), ... Dział(..) Dział(..) ... Nrd(..), Nazwa(..) Lokacja(..) Lokacja(..) ... Zatrudnia(..) Zatrudnia(..) ... Szef(..) Prac(..) Prac(..), ... Dział(..) Dział(..) ... Prac(..) Prac(..), ... Dział(..) Dział(..) ...

SBQL - przykłady zapytań (10) Podaj średnią liczbę pracowników w działach. Dla schematu obiektowego: avg( Dział . count(Zatrudnia)) Dla schematu relacyjnego: avg( Dział . count(Prac where NrD = PracujeW)) Analogiczne zdanie w standardzie SQL-89 nie istnieje; zapytanie można wyrazić z pomocą dodatkowej perspektywy. W standardzie SQL-92 zdanie to można sformułować przy pomocy opcji group by. Opcja ta prowadzi do znanej rafy semantycznej, polegającej na tym, że jeżeli pewien dział nie będzie miał ani jednego pracownika, wówczas nie zostanie uwzględniony przy obliczaniu średniej. W SBQL ta rafa nie występuje.

SBQL - przykłady zapytań (11) Dla pracowników zarabiających więcej niż 2000 i pracujących w budynku A podaj nazwisko, stanowisko, nazwę działu i nazwisko szefa działu. ((Prac where Zar > 2000)  (PracujeW . (Dział where "budynek A"  Lokacja ))) . (Nazwisko, Stan, Nazwa, (Szef.Prac.Nazwisko)) Wynikiem będzie kolekcja struktur struct{iNazwisko1, iStan, iNazwa, iNazwisko2 }, gdzie każda struktura zawiera cztery referencje. Czy w każdym dziale jest pracownik zarabiający więcej od swojego szefa?  Dział (  Zatrudnia.Prac ( Zar > Szef.Prac.Zar)) Wynikiem zapytania jest wartość boolowska prawda lub fałsz. Kwantyfikatory są operatorami niealgebraicznymi, wobec czego (jak w całym SBQL), użycie nazw pomocniczych (czyli „zmiennych związanych kwantyfikatorami”) nie jest konieczne. Jeżeli zachodziłaby potrzeba, wówczas takie „zmienne” można byłoby powołać w postaci pomocniczych nazw:  Dział as x (  x.Zatrudnia.Prac as y ( y.Zar > x.Szef.Prac.Zar)) Zmuszanie użytkowników do obowiązku stosowania pomocniczych nazw, jak w OQL, jest konsekwencją pseudo-matematycznych koncepcji semantyki.

SBQL - przykłady zapytań (12) Podaj pracowników którzy na pewno mieszkają w Olsztynie. Zgodnie ze schematem, adres pracownika jest daną opcyjną i złożoną. W terminologii modelu relacyjnego, brak adresu dla pracownika oznacza, ze w tym miejscu jest zapisana wartość zerowa (null). Wartości te wymagają w SQL specjalnych opcji, które są niespójne oraz komplikują semantykę i pragmatykę języka. W naszym podejściu będziemy ich unikać. Zamiast operacji na wartościach zerowych można zastosować kwantyfikatory. Prac where  Adres (Miasto = ”Olsztyn”) Podaj pracowników którzy albo na pewno mieszkają w Olsztynie albo być może mieszkają w Olsztynie, ponieważ ich adres jest nieznany: Prac where  Adres (Miasto = ”Olsztyn”) Pamiętać: kwantyfikator  działający na pustym zbiorze zwraca true.

SBQL - przykłady zapytań (13) Podaj nazwiska i zarobki projektantów zarabiających więcej od swoich szefów: Zilustrujemy kilka stylów tworzenia zapytań, które są konsekwencją przyjętych przez nas definicji: Styl SQL (schemat relacyjny): (((Prac as x)  (Dział as y)  (Prac as z)) where x.Stan = ”projektant” and x.PracujeW = y.NrD and y.Szef = z.NrP and x.Zar > z.Zar) . (x.Nazwisko, x.Zar) Styl OQL (schemat obiektowy): ((Prac as x)  (x.PracujeW.Dział as y)  (y.Szef.Prac as z) where x.Stan = ”projektant” and x.Zar > z.Zar) . (x.Nazwisko, x.Zar) Wariant minimalny SBQL (schemat obiektowy ): (Prac where Stan = ”projektant” and Zar > (PracujeW.Dział.Szef.Prac.Zar)) . (Nazwisko,Zar) Styl „rachunku dziedzinowego” zapisanego w SBQL (schemat relacyjny): (((Prac.(Nazwisko as np, Zar as zp, Stan as sp, PracujeW as dp))  (Dział.( Nrd as nd, Szef as sd))  (Prac.(Zar as zs, NrP as ns))) where sp = ”projektant” and dp = nd and sd = ns and zp > zs) . (np, zp)

SBQL - podsumowanie przykładów Poprzez przykłady, szczególnie z poprzedniego slajdu, pokazujemy, że debaty o „wyższości formalizmu A nad formalizmem B” oraz obecne spory o języki zapytań można pogodzić na gruncie podejścia stosowego. Istotne jest, jakie struktury danych dopuszczamy w systemie zarządzania bazą danych. Z chwilą precyzyjnej definicji tych struktur dla dowolnych z nich można zbudować język zapytań na gruncie podejścia stosowego. Teza M.Stonebrakera o tym, że dla modeli obiektowych nie można zbudować języka zapytań, jest nonsensem; próbą zbudowania fałszywego stereotypu. Struktury relacyjne nie mają w tym względzie jakiejkolwiek przewagi nad strukturami obiektowymi, i odwrotnie. Gorąca debata ideologów świata komercyjnego odnośnie tego, który paradygmat struktur danych i języków zapytań jest lepszy, na gruncie SBA staje się jałową demagogiczną retoryką, pustosłowiem pozbawionym merytorycznych argumentów. Szczególnie wtedy, gdy powołuje się na "podstawy matematyczne".

Wykład 8

Rozszerzenie SBQL dla modeli M1, M2 i M3 (klas i dziedziczenia, dynamicznych ról, list eksportowych)

Rozszerzenie SBQL w modelu M1 W modelu M1 należy zmodyfikować regułę wiązania nazw. Chodzi o zachowanie zasady zamienialności (substitutability). Przykładowo, obiekt Prac jest jednocześnie obiektem Osoba. Informacja o tej zależności jest przechowywana w modelu M1 w dwóch miejscach: (1) klasy, które przechowują nazwy należących do nich obiektów; (2) relacje KK i OK. W terminach operacji na ENVS zmieniona reguła wiązania oznacza, że o ile w pewnej sekcji stosu znajduje się binder o nazwie Prac, zaś wiązana jest nazwa Osoba, to binder ten uczestniczy w procesie wiązania tak samo, jakby był to binder o nazwie Osoba. M1 wprowadza nową zasadę otwierania i usuwania sekcji na ENVS. Jeżeli operator niealgebraiczny przetwarza obiekt posiadający identyfikator i, to na wierzchołek stosu środowisk wkładane są nie tylko bindery nested(i), ale poniżej wierzchołka stosu są ulokowane sekcje z binderami do własności klas tego obiektu w odpowiedniej kolejności,

Graficzna reprezentacja przykładu modelu M1 Osoba Nazwisko RokUr Wiek i40 KlasaOsoba i41 Wiek (...kod...) ................ i1 Osoba i2 Nazwisko ”Wilski” PracujeW i3 RokUr 1950 Prac Zar ZmieńZar ZarNetto i50 KlasaPrac i51 ZmieńZar (...kod...) i52 ZarNetto (...kod...) ................ i4 Prac i9 Prac i5 Nazwisko ”Nowak” i10 Nazwisko ”Kowalski” i6 RokUr 1944 i11 RokUr 1940 i7 Zar 2500 i12 Zar 2000 i8 PracujeW i13 PracujeW i127 i128

Sytuacja na ENVS dla modelu M1 Zakładamy, że operator nie-algebraiczny przetwarza obiekt O z identyfikatorem iO. Kolejność przeszukiwania stosu Bindery do własności przechowywanych wewnątrz obiektu O, czyli nested(iO) Bindery do własności przechowywanych wewnątrz K1 Bindery do własności przechowywanych wewnątrz K2 Bindery do własności przechowywanych wewnątrz K3 ............................ ........................... Bindery z identyfikatorami startowymi składu obiektów Klasa K3 Sekcje wkładane na stos środowiskowy przy przetwarzaniu obiektu O przez operator niealgebraiczny Klasa K2 Klasa K1 Obiekt

Zmiany w modelu M1 Podana koncepcja automatycznie uwzględnia własność przesłaniania (overriding). Jeżeli np. klasa K1 zawiera pewną metodę m, która przesłania metodę m zawartą w klasie K2, to zgodnie z przedstawioną kolejnością przeszukiwania stosu przy wiązaniu nazwy m związana zostanie metoda m z klasy K1; metoda m z klasy K2 będzie niewidoczna. Poprzedni slajd przedstawia stan stosu od strony koncepcji semantyki języka zapytań. Przy przetwarzaniu obiektu O na stos są wkładane cztery sekcje, a po przetworzeniu obiektu - zdejmowane. Ogólnie wkładanych jest n+1 sekcji, gdzie n jest liczbą poziomów w hierarchii klas Wizja ta może być bezpośrednio zaimplementowana, ale jest ona dość rozrzutna jeżeli chodzi o zużycie zasobów, gdyż może przy okazji przetwarzania każdego obiektu wkładać na stos i zdejmować wiele sekcji. Możliwe są proste optymalizacje.

Przetwarzanie obiektu Prac w M1 Operator niealgebraiczny, np. where w zapytaniu Prac where Wiek > avg(Prac.Wiek) przetwarza obiekt Nowaka (z identyfikatorem i4 ) dla bazy danych przedstawionej poprzednio. Kolejność przeszukiwania stosu Nazwisko(i5) RokUr (i6) Zar (i7) PracujeW (i8) ZmieńZar(i51) ZarNetto(i52) ... Wiek(i51) ... ......... Prac(i4) Prac(i9), ... Osoba(i1) Osoba(i4) Osoba(i9), ... Sekcja zawierająca nested(i4) - własności aktualnie przetwarzanego obiektu i4 Bindery do własności klasy KlasaPrac (połączonej z i4 związkiem OK). Bindery do własności klasy KlasaOsoba (połączonej z KlasaPrac związkiem KK). ... inne sekcje ... Bindery do obiektów bazy danych, zdublowane dla różnych nazw.

Zdublowane bindery w M1 Sekcja binderów do obiektów bazy danych zawiera bindery do obiektów i4 oraz i9 opatrzone zarówno nazwą Prac, jak i nazwą Osoba. Jest to jeden z wariantów uwzględnienia zasady zamienialności. Dodatkowe bindery Osoba pojawiają się na podstawie nazw obiektów będących inwariantami klas oraz związku KK. Zakładamy tu, że jeżeli wiązana była nazwa Osoba, to kontrola typologiczna ograniczy możliwość użycia własności obiektu Prac nie będących atrybutami obiektów Osoba. Dla języków pozbawionych mocnej kontroli typów ta metoda może prowadzić do anomalii; np. wiążąc nazwę Osoba mamy dostęp do takich własności jak Zar i ZarNetto. Jest to sprzeczne z zasadami obiektowości. W klasycznych modelach obiektowych problem nie występuje, gdyż: typy lub klasy nie przesądzają nazw obiektów, zasada zamienialności jest konsekwencją hierarchii dziedziczenia klas lub typów.

Przetwarzanie metod Dotychczas przyjmowaliśmy, że wiązanie polega wyłącznie na tym, że nazwę występującą w zapytaniu zamienia się np. na referencję obiektu na podstawie bindera wyszukanego na stosie ENVS. Dla metod ta zasada jest rozszerzona. Jeżeli wiązana nazwa jest nazwą metody, to: Następuje wiązanie jej nazwy jak zwykle na stosie ENVS; Po związaniu, metoda jest wywoływana; Wywołanie metody powoduje utworzenie nowej sekcji na stosie ENVS - zapisu aktywacji, zawierającego bindery do lokalnego środowiska metody, tj. jej parametrów i lokalnych obiektów, następnie wejście sterowania w kod. Zapis aktywacji zawiera także ślad powrotu z metody. Aktualne parametry metody są pobierane ze stosu rezultatów QRES i następnie wstawiane do lokalnego środowiska. Niekiedy może nam zależeć na pobraniu identyfikatora metody, a nie na jej wywołaniu, np. celem przekazania metody jako parametru. W tym celu potrzebna jest specjalna składnia, np. ref Wiek.

Co się dzieje, gdy wołana jest metoda? Implementacja metody w ramach klasy K Użycie metody w zapytaniu method met( p1: T1, p2: T2 ): T (*sygnatura*) begin x1: T11, x2: T12; (* lokalne obiekty *) .... end met; q  ... met(q1, q2)... gdzie  jest operatorem niealgebraicznym Niech eval(q) zwróci bag{r1, r2, ....}, gdzie są referencjami do obiektów będących członkami klasy K. Sekcja lokalna metody met Przetwarzanie ciała metody met p1 (Rezultat q1) p2(Rezultat q2) x1(..) x2(..) Bindery do prywatnych własności K nested(r1) Bindery do publicznych własności K Poprzedni stan ENVS p1 (Rezultat q1) p2(Rezultat q2) x1(..) x2(..) Bindery do prywatnych własności K nested(r1) Bindery do publicznych własności K Poprzedni stan ENVS Wiązanie i wywołanie metody met Kroki przetwarzania dla r1 : Operator  Obliczenie parametrów q1, q2 Usunięcie rezultatów q1, q2 nested(r1) Bindery do publicznych własności K Poprzedni stan ENVS Rezultat q2 Rezultat q1 Rezultat q Poprzedni stan QRES Obliczenie q Rezultat q Poprzedni stan QRES Rezultat q Poprzedni stan QRES Poprzedni stan ENVS Poprzedni stan ENVS

Podsumowanie zmian semantycznych w M1 Zmiany w globalnych sekcjach binderów (m.in. do bazy danych): dowolny binder n(i) wynikający ze zbioru R identyfikatorów startowych jest uzupełniany poprzez bindery n1(i), n2(i), ... , gdzie n1, n2,... są nazwami ustalonymi jako inwarianty klas nadrzędnych w stosunku do klasy do której należy obiekt i. Jeżeli operator niealgebraiczny przetwarza obiekt z identyfikatorem i należący do klasy K1, która ma kolejne superklasy K2, K3, ..., wówczas na wierzchołek stosu środowiskowego wkłada się po kolei, poczynając od wierzchołka: nested(i), bindery do własności K1, bindery do własności K2, bindery do własności K3, itd. Dla metod wiązanie nazw jest skojarzone z wywołaniem. Wywołanie oznacza obliczenie parametrów metody, założenie nowej sekcji (zapisu aktywacji) na czubku ENVS, i następnie, przekazanie sterowania do kodu metody. Nowa sekcja zawiera bindery parametrów, bindery lokalnych obiektów, oraz ślad powrotu.

Wielodziedziczenie w M1 i fałszywe przesłanianie Klasa K4 metoda m Klasa K5 Bindery do własności obiektu O Bindery do własności K1 Bindery do własności K2 Bindery do własności K3: m (...) Bindery do własności K4: m (...) Bindery do własności K5 ..... Bindery danych globalnych Kierunek przeszukiwania stosu Klasa K3 metoda m Klasa K2 Klasa K1 Obiekt Stos środowisk przy przetwarzaniu obiektu O Skład obiektów Metoda m z klasy K4 jest przesłonięta przez metodę m z klasy K3, przez co metoda m z klasy K4 nie ma szansy na to, aby była użyta. Jest to złamanie zasady zamienialności (substitutability)

Wielokrotne dziedziczenie w M1 Jeżeli pewna klasa dziedziczy z większej liczby klas, to możliwe są dwa przypadki: Nie występuje konflikt nazw pomiędzy własnościami dziedziczonymi z poszczególnych klas. W tym przypadku kolejność klas na stosie powinna uwzględniać hierarchię klas, ale kolejność klas na tym samym poziomie hierarchii nie ma znaczenia. Jeżeli występuje konflikt pomiędzy własnościami dziedziczonymi z poszczególnych klas, to nie istnieje dobry porządek ustawienia klas na stosie. Każdy porządek łamie zasadę zamienialności. Jest to inherentna wada modelu M1, która jest wyeliminowana w modelu M2. Nie ma sposobu uniknięcia anomalii wielodziedziczenia w modelu M1. Jeżeli zabronimy użycia identycznych nazw metod w nadklasach, to łamiemy zasadę "klasa otwarta dla specjalizacji, zamknięta dla zmian". Jeżeli dopuścimy zmianę nazwy metody w podklasie, to łamiemy zarówno zasadę zamienialności, jak i zasadę koncepcyjnej kontynuacji.

Skąd problemy wielodziedziczenia w M1? Wynikają one z faktu zmieszania w jednym środowisku własności różnych klas, często niekompatybilnych. Jest to przyczyna tego, że w niektórych językach i systemach zrezygnowano z wielokrotnego dziedziczenia. Nie usuwa to problemu, ponieważ w ten sposób zwiększa się dystans pomiędzy modelowaniem pojęciowym a modelem implementacyjnym. Brak wielodziedziczenia łamie zasadę „otwarte-zamknięte” (open-close principle), podstawowej dla ponownego użycia (reuse). Model a la M1 jest stosowany w większości języków i systemów obiektowych (z różnymi nazwami, mutacjami i własnościami). Niezależnie od tego, czy zezwala on na wielodziedziczenie (C++, OMG CORBA, standard ODMG, standardy SQL3 i SQL1999), czy też nie (Smalltalk, Java), zawsze pojawią się w nim pewne wady bądź w zakresie modelowania pojęciowego, bądź też w postaci anomalii technicznych tworzących rafy dla programistów. Sposobem usunięcia tych wad jest przyjęcie modelu M2 (i modeli pochodnych), zakładających koncepcję dynamicznych ról obiektów.

Przykłady zapytań w modelu M1 Schemat obiektowy (diagram klas) Nazwa działu i średni wiek jego pracowników: Dział . ( Nazwa, avg(Zatrudnia.Prac.Wiek()) as Średnia) Dziedziczenie metody Wiek przez klasę Prac. Osoba[0..*] Nazwisko RokUr Wiek() Podaj nazwiska, zarobek netto i nazwisko szefa dla programistów pracujących w Radomiu. (Dział where Lokacja as x (x = "Radom" ))  (Zatrudnia.Prac where "programista"  Zawód ). (Nazwisko, ZarNetto() as netto, (Szef.Prac.Nazwisko) as boss) Dziedziczenie atrybutu Nazwisko przez klasę Prac. Prac[0..*] NrP Zawód[1..*] Zar ZmieńZar(nowy) ZarNetto( ) PracujeW Kieruje[0..1] Dla każdego pracownika podaj nazwisko oraz procent budżetu przeznaczony na jego uposażenie. Prac. (Nazwisko as NazwiskoPracownika, (Zar * 12 * 100 / (PracujeW.Dział.BudżetRoczny())) as ProcentBudżetu) Zatrudnia[1..*] Szef Dział [0..*] NrD Nazwa Lokacja[1..*] BudżetRoczny()

Rozszerzenie SBQL w modelu M2 Jak dotąd, tylko podejście stosowe jest przystosowane do koncepcji obiektowości zakładającej dynamiczne role obiektów, określanej przez nas jako model M2. Pozostałe podejścia, takie jak obiektowe algebry, nie tylko nie rozważają takiej koncepcji, ale są niespójne w stosunku do prostszego modelu M1. Zmiana w stosunku do modeli M0 i M1 polega na sposobie zapełniania bazowych sekcji stosu. W tym przypadku sekcja bazowa ENVS musi zawierać bindery do wszystkich ról obiektów, gdyż identyfikatory startowe (zbiór R) obejmują wszystkie aktualne role wszystkich obiektów. W modelu M2 obiekt występuje wyłącznie jako konglomerat ról, z jedną wyróżnioną rolą główną.

Graficzna reprezentacja przykładu modelu M2 i40 KlasaOsoba i41 Wiek (...kod...) ............. i1 Osoba i60 KlasaStudent i2 Nazwisko ”Wilski” i61 ŚredniaOcen (...kod...) i50 KlasaPrac i51 ZmieńZar (...kod...) ................ i52 ZarNetto (...kod...) i3 RokUr 1950 ................ i4 Osoba i5 Nazwisko ”Nowak” i7 Osoba i6 RokUr 1944 i8 Nazwisko ”Kowalski” i9 RokUr 1940 i13 Prac i19 Student i16 Prac i14 Zar 2500 i20 NrIndeksu 76943 i17 Zar 2000 i15 PracujeW i21 Wydział ”fizyka” i18 PracujeW i127 i128

Dolne sekcje ENVS w modelu M2 .................... Bindery do obiektów/zmiennych nietrwałych aktualnej sesji użytkownika Osoba( i1 ) Osoba( i4 ) Osoba( i8 ) Prac( i13 ) Prac( i16) Student( i19) ... Bindery do globalnych funkcji bibliotecznych Bindery do zmiennych i funkcji środowiska komputerowego Sekcja bazy danych Sekcje danych globalnych

Przykład stanu stosu ENVS dla M2 Zapytanie ma postać Prac where ... n ... Przetwarzana jest rola z identyfikatorem i16 , wiązana jest nazwa n Kolejność przeszukiwania stosu Zar( i17 ) PracujeW( i18 ) ZmieńZar( i51 ) ZarNetto( i52 ) ... Nazwisko( i8 ) RokUr( i9 ) Wiek( i41 ) ... ......... Osoba( i1 ) Osoba( i4 ) Osoba( i8 ) Prac( i13 ) Prac( i16) Student( i19) ... Bindery do własności aktualnie przetwarzanej roli Prac Bindery do własności klasy KlasaPrac Bindery do własności roli Osoba będącej super-rolą aktualnie przetwarzanej roli Prac Bindery do własności klasy KlasaOsoba Sekcja bazy danych

Uogólnienie: skład z rolami w M2 iK1R1 K1R1 iK2R1 K2R1 iK3R1 K3R1 iK1R2 K1R2 iK2R2 K2R2 iK3R2 K3R2 iK1R3 K1R3 iK2R3 K2R3 iK3R3 C3R3 Klasy iR3 R3 Obiekty i role iR2 R2 Obiekt z rolami iR1 R1 Klasy KjRi (j = 1,2,...) nie muszą być unikalne; mogą tworzyć graf dziedziczenia.

Organizacja i kolejność przeszukiwania ENVS Operator nie-algebraiczny przetwarza rolę R1 Kolejność przeszukiwania stosu nested(iR1) nested(iK1R1) nested(iK2R1) ... nested(iR2) nested(iK1R2) nested(iK2R2) nested(iR3) nested(iK1R3) sekcje roli R1 sekcje roli R2 sekcje roli R3

Uwagi do poprzedniego rysunku Może się zdarzyć, że pewne sekcje klas wkładanych na stos będą się powtarzać. Poza koncepcyjną redundancją (którą można łatwo wyeliminować w implementacji) w niczym to nie przeszkadza, ponieważ istotna będzie tylko ta sekcja, która jest najbliższa wierzchołka stosu. Duplikaty sekcji znajdujące się poniżej w stosie nie będą uczestniczyć w wiązaniu nazw. Po ewaluacji zapytania q2 wszystkie te sekcje będą zdjęte ze stosu. Reguły wiązania nazw są takie same jak w przypadku modelu M0. Nie występują tu anomalie przy wiązaniu nazw, które były omawiane przy okazji modelu M1.

Operatory rzutowania w modelu M2 Model M2 implikuje operator algebraicznego znany z innych języków pod nazwą „rzutowanie” (casting). Chodzi o możliwość przełączenia się z danej roli obiektu do innej roli tego samego obiektu. Syntaktycznie, operator ten będzie zapisywany w postaci: (nazwa) zapytanie gdzie nazwa jest nazwą roli, zapytanie zwraca wielozbiór identyfikatorów ról. Operator ten dla danego identyfikatora roli zwróconego przez zapytanie wyszukuje w ramach tego samego obiektu role nazwa. Operator zwraca identyfikatory ról. Końcowy wynik jest sumą mnogościową wyników dla wszystkich identyfikatorów zwróconych przez zapytanie. Przykład. Podaj pracowników, którzy są jednocześnie studentami. (Prac) Student Ewaluacja zapytania Student zwróci identyfikatory wszystkich ról Student. Operator rzutowania (Prac) zmieni niektóre z nich na identyfikatory ról Prac (jeżeli obiekt ma jednocześnie role Student i Prac), zaś inne na wartość pustą.

Przykład operatora rzutowania Załóżmy, że role Student mają atrybut Stypendium. Dla każdej osoby należy zwrócić Nazwisko oraz dochody, które wynoszą 0, jeżeli dana osoba nie jest ani pracownikiem ani studentem, Zar jeżeli osoba jest pracownikiem, Stypendium jeżeli osoba jest studentem, lub Zar+Stypendium, jeżeli osoba jest jednocześnie pracownikiem i studentem. (Osoba as p) . (p.Nazwisko, sum( bag( 0, ((Student)p).Stypendium, ((Prac)p).Zar ) ) sum jest funkcją agregowaną znaną z SQL. Pomocnicza nazwa p „obiega” wszystkie role Osoba. Po pierwszej kropce następuje wyliczenie wyniku dla pojedynczej wartości p. Na wynik ten składa się Nazwisko danej osoby oraz suma dochodów. Dla wyliczenie tej sumy tworzy się wielozbiór składając się z jednego, dwóch lub trzech elementów. Jeżeli p posiada rolę Student, to p jest rzutowane na tę rolę, z tej roli pobiera się Stypendium; podobnie dla roli Prac i atrybutu Zar. Podane zapytanie automatycznie uwzględni fakt, że dana osoba jest kilkakrotnie pracownikiem i/lub kilkakrotnie studentem.

Rozszerzenie SBQL w modelu M3 Model M3 rozszerza M1 i M2 o listę eksportową, która dzieli własności klasy oraz własności obiektów tej klasy na publiczne i prywatne. Własności prywatne zarówno klasy K, jak i obiektów klasy K, są dostępne wyłącznie z wnętrza ciała metod (publicznych i prywatnych) klasy K. Można to prosto zrealizować poprzez odpowiednie ustawienie sekcji stosu oraz poprawkę do funkcji nested. Niech iK będzie identyfikatorem obiektu klasy K posiadającej listę eksportową exportK nested_private( iK ) = {n(x) : n  nested( iK ) and n  exportK} nested_public( iK ) = {n(x) : n  nested( iK ) and n  exportK} Dla pozostałych argumentów funkcje te nie ulegają zmianie. Funkcje te w sposób naturalny (czyli podobnie jak poprzednio) rozszerzamy na dowolne struktury: nested_private( struct{ x1, x2, ...}) = nested_private( x1 )  nested_private( x2 )  ... nested_public( struct{ x1, x2, ...}) = nested_public( x1 )  nested_public( x2 )  ...

Przetwarzanie obiektów w M3 Zapytanie q1  q2, gdzie  jest operatorem niealgebraicznym. Niech eval(q) zwróci bag{r1, r2, ....}, gdzie r1, r2, .... są referencjami do obiektów będących członkami klasy K1, która jest podklasą K2, która jest podklasą K3. Niech wiązana będzie pewna nazwa m występująca w q2 ; m nie jest nazwą metody. Kroki przetwarzania dla ri : Koniec przetwarzania ri przez q2 Operator  nested_public(ri) nested_public(iK1) nested_public(iK2) nested_public(iK3) Poprzedni stan ENVS Sekcje wkładane przy przetwarzaniu ri Poprzedni stan ENVS Poprzedni stan ENVS

Przetwarzanie metod w M3 Założenia jak poprzednio. Niech wiązana będzie pewna nazwa n występująca w q2 ; m jest nazwą metody, która znajduje się w klasie K2. Kroki przetwarzania dla ri : Wywołanie metody m Prywatne własności klasy K2 Lokalne środowisko metody m nested_private(iK2) nested_ private(ri) nested_public(ri) nested_public(iK1) nested_public(iK2) nested_public(iK3) Poprzedni stan ENVS Prywatne własności obiektu z OID = ri Środowisko indukowane przez wywołanie m nested_public(ri) nested_public(iK1) nested_public(iK2) nested_public(iK3) Poprzedni stan ENVS nested_public(ri) nested_public(iK1) nested_public(iK2) nested_public(iK3) Poprzedni stan ENVS Środowisko indukowane przez  Poprzedni stan ENVS Poprzedni stan ENVS

Reguły zakresu dla M3 Załóżmy, że dla rozpatrywanego zapytania q1  q2 zachodzi: aktualnie wykonywana jest metoda m1 występująca w q2 i przetwarzająca ri zapytanie występuje wewnątrz ciała metody m2 m2 została wywołana z metody m3 Kolejność wiązania nazw występujących w ciele m1 Środowisko indukowane przez wywołanie m1 dla ri Środowisko indukowane przez q2 , w którym zanurzone jest wołanie metody m1 Środowisko indukowane przez  dla ri Środowisko indukowane przez wywołanie m2 Środowisko indukowane przez wywołanie m3 ... Sekcje bazowe ENVS Lexical scoping: programista piszący m1 nie znał tych środowisk. Wobec tego nazwy występujące w m1 nie mogą być w nich wiązane.

Przykłady poprawnych i błędnych zapytań w M3 Osoba where Nazwisko = "Nowak" Poprawne (Osoba where Nazwisko = "Nowak").RokUr Błędne Osoba + Nazwisko - RokUr + Wiek (Prac where Nazwisko = "Nowak").Wiek Poprawne method Wiek() { return BieżącyRok - RokUr } Poprawne Prac - Zar + ZmieńZar + ZarNetto (Prac where Nazwisko = "Nowak").Zar Błędne method ZmieńZar( nowy ) { Zar := nowy } Poprawne + PracujeW method ZarNetto( ) { if RokUr > 1955 then return 0.9 * Zar else return 0.8 * Zar } + Zatrudnia Błędne Dział + Nazwa + Budżet Prac where Dział (Budżet > 100 * ZarNetto) .... method ZarNetto( ) { if Nazwa = "Marketing" then return 0.9 * Zar else return 0.8 * Zar } Błędne

Wykład 9

Konstrukcje imperatywne oparte na SBQL

Zapytania jako wyrażenia języka programowania W SBA zapytania pełnią rolę wyrażeń języka programowania. Zapytania będą również stosowane do nietrwałych danych. Inaczej mówiąc przyjęliśmy, że w naszym (hipotetycznym) języku programowania nie będzie innych wyrażeń niż zapytania. To założenie jest rewolucją w odniesieniu do języków zapytań. W SQL zapytania zagnieżdżone w język programowania mogły odwoływać się do zmiennych języka programowania poprzez specjalną składnię. Podział na wyrażenia i zapytania przyjmują języki czwartej generacji (4GL). PL/SQL system Oracle, pierwszy komercyjny język programowania w pełni zintegrowany z językiem SQL, również wprowadza podział na wyrażenia i zapytania. Powodem są ograniczenia i narzuty składniowe języka SQL, w którym nie da się zapisać są tak prostych wyrażeń jak 2+2. Jest jednak oczywiste, że wyrażenia i zapytania muszą mieć niepuste przecięcie, co stawia pod znakiem zapytania ten podział.

Zastosowania zapytań w jęz. programowania Zapytania, jako wyrażenia języka programowania, będą używane we wszystkich kontekstach w których używane są wyrażenia, w szczególności w następujących: Jako składowa zdań imperatywnych, np. instrukcji podstawienia, usuwania, tworzenia i wstawiania. Jako parametry procedur, funkcji lub metod, przy zachowaniu klasycznych metod przekazywania parametrów znanych jako wołanie przez wartość (call- by-value) oraz wołanie przez referencję (call-by-reference). Jako składnik zdania return określającego wynik procedury lub metody funkcyjnej. Jako składnik iteratora znanego jako „for each ... do ...”, określający ilość pętli iteratora oraz element przetwarzany w każdym jego cyklu.

Czy taki język nie jest utopią? Jakiego rodzaju zagrożenia można wiązać z realizacją języka programowania o podanych własnościach? Są one następujące: Niska wydajność. Ale: Wiele metod optymalizacyjnych wiązanych z modelem relacyjnym ma bezpośrednie odpowiedniki dla modeli obiektowych. Np. indeksy. Dla SBQL zostały odkryte nieznane wcześniej bardzo mocne metody optymalizacyjne oparte na przepisywaniu. Trudności z wypromowaniem nowego języka programowania. Ale: Java stała się szybko popularna, mimo że nie występują w niej cechy nieznane w poprzednich językach. (Jest nową kombinacją popularnych cech.) PHP w ciągu dwóch lat zdobył miliony zwolenników. Wiele rynkowych systemów jest wyposażana w języki o bardzo specyficznych nazwach i rozwiązaniach. Użytkownikom to nie przeszkadza. Świat badawczo-rozwojowy nie może zastopować badań i rozwoju z powodu potencjalnych trudności z powszechnym wdrożeniem.

Konstrukcje deklaratywne i imperatywne Założeniem języków zapytań jest deklaratywność, czyli wyrażanie bezpośrednio celu wyszukiwania. Deklaratywność jest wiązana z programowaniem w logice, np. językami Prolog lub Datalog. Zwolennicy tego podejścia twierdzą niekiedy, że tylko wyrażenia logiki matematycznej (i pochodne) są deklaratywne. Są to próby zbudowania fałszywego stereotypu. Pseudo-naukowe bzdury. Deklaratywność wynika z psychologii, odczuć użytkownika języka, a nie z jakichkolwiek tworów formalnych. Zarówno algebra relacji jak i SQL są uważane za języki deklaracyjne, mimo że ich semantyka jest objaśniana w sposób operacyjny. Deklaratywność nie jest celem samym w sobie - ma o tyle znaczenie, o ile skraca czas tworzenia programu, czyni go bardziej zrozumiałym, oraz zapewnia łatwiejszą i mniej kosztowną jego pielęgnację. Kluczem do deklaratywności jest uzyskanie jak najprostszego odwzorowania pojęciowego pomiędzy rzeczywistym problemem w dziedzinie przedmiotowej, a zapisem bądź rozwiązaniem tego problemu w środowisku komputerowym.

Konstrukcje deklaratywne a zmiany stanu Konstrukcje deklaratywne nie mogą (nie powinny) zmieniać stanu. Stan jest pojęciem związanym z czasem i następstwem czynności. Zmiany stanu wymagają wprowadzenia konstrukcji imperatywnych. Twórcy koncepcji opartych na programowaniu w logice starają się retuszować ten oczywisty fakt poprzez różnorodne konstrukcje. Np. w systemie LDL zastosowano "mimikrę syntaktyczną" w postaci symbolu -, który przypomina negację, ale w istocie jest konstrukcją imperatywną, której semantyką jest usunięcie pewnej danej. Obecność tego rodzaju sztuczek stawia pytanie, czy w misji zbudowania fałszywego stereotypu „programowania deklaracyjnego” niektórzy naukowcy nie posuwają się zbyt daleko, poza granicę etyki naukowej, która zabrania oszukiwania czytelników, użytkowników (i samych siebie). Z reguły, zmiany stanu są przemycane w tego rodzaju koncepcjach i językach poprzez różnorodne efekty uboczne. Te "efekty uboczne" podkopują formalną podstawę programowania w logice; powodują, że tysiące twierdzeń i wniosków udowodnionych przez armię akademickich "teoretyków" można a priori skierować do składu śmieci.

SBQL a konstrukcje imperatywne Przy rozszerzeniach SBQL będziemy starali się trzymać czystości rozdzielenia tej części języka, która nie może zmienić stanu obiektów (czyli deklaratywnych zapytań), oraz części, która będzie zajmować się zmianami stanu (czyli części imperatywnej). Efekty uboczne w zapytaniach będziemy uważać za niewskazane. Poprzez efekty uboczne wyrażenia stają się mniej czytelne, błędogenne oraz bardziej kłopotliwe podczas zmian oprogramowania; Efekty uboczne mogą zakłócić lub uniemożliwić optymalizację zapytań. Nie możemy też zabronić użycia efektów ubocznych w zapytaniach. Programista może wewnątrz zapytania wywołać funkcję lub metodę. Nie będziemy dzielić funkcji i metod w zależności od posiadania efektów ubocznych. Wprowadziłoby to zbytnią komplikację do języka i jego użycia. Efekty uboczne w zapytaniach powinny być stosowane w sytuacji pewności, że nie zakłóci to optymalizacji i nie doprowadzi do złego wyniku.

Zasada korespondencji correspondence principle Podstawowy drogowskaz przy konstruowaniu języka programowania. Zasada korespondencji mówi, że wraz z wprowadzeniem do języka pewnej cechy X należy precyzyjnie określić inne cechy języka w taki sposób, aby cecha X współdziałała z już istniejącymi konstrukcjami, została wtopiona w istniejące lub zmodyfikowane mechanizmy nazywania, typowania, zakresu i wiązania, oraz miała zapewnioną uniwersalną obsługę. Przykładowo, jeżeli cecha X jest dynamiczną tablicą, to odpowiednimi pytaniami są: czy może ona być składową zapisu (struktury, obiektu), czy może być parametrem procedury, czy może być zwrócona przez procedurę funkcyjną, jakie środki będą przewidziane do typizacji, wyszukiwania, aktualizacji, usuwania, dostawiania elementów; itd. Oddolny rozwój niektórych języków (np. SQL) jest przyczyną wielu przypadków łamania zasady korespondencji, co objawia się m.in. tym, że nowo dodawane cechy nie są gładko połączone ze starymi cechami.

Przegląd operatorów imperatywnych

Operator tworzenia obiektu Może to być deklarowanie obiektu, czyli utworzeniem obiektu w momencie inicjacji pewnej abstrakcji programistycznej (np. metody). Może to być dynamiczne utworzenie obiektu na polecenie wydane explicite przez programistę - operator musi być dodatkowo parametryzowany miejscem składu i statusem trwałości. Operator powinien być makroskopowy. Operator powinien dawać możliwość utworzenia nowej danej elementarnej oraz nowej danej pointerowej wewnątrz określonego obiektu. Operator powinien dawać możliwość utworzenia nowego modułu, klasy, metody, perspektywy, procedury, ograniczenia, trygera, itd. Generalnie, dla dowolnego elementu składu obiektów powinna istnieć odmiana tego operatora pozwalająca na utworzenie tego elementu w określonym środowisku.

Operator podstawienia i wstawiania Operator podstawienia powinien umożliwiać podstawienie makroskopowe, w stylu klauzuli update języka SQL. Podstawienie może dotyczyć wartości atomowych oraz wartości złożonych. Musi istnieć odmiana operatora podstawienia pozwalająca na aktualizację danej pointerowej oraz dowolnego innego elementu składu obiektów, o którym zakłada się, że może podlegać aktualizacji. Operator wstawiania obiektu powinien umożliwiać wstawianie makroskopowe, w stylu klauzuli insert języka SQL. Ma on za zadanie wstawić pewien obiekt (obiekty) jako podobiekt (podobiekty) innego obiektu. Wybrany obiekt jest przesuwany z dotychczasowego środowiska do określonego środowiska. Powinien on działać na wszystkich typach obiektów, włączając obiekty złożone, klasy, metody, perspektywy, itd. Dla modelu M2 powinna istnieć odmiana tego operatora pozwalająca podłączyć do istniejącej roli nową rolę.

Operator usuwania Operator usuwania powinien umożliwiać usuwanie makroskopowe, w stylu klauzuli delete języka SQL. Dotyczy obiektów tworzonych (nie deklarowanych) przez programistę i oznacza usunięcie obiektu z określonego środowiska. Operator usuwania powinien dotyczyć wszystkich rodzajów obiektów, w tym obiektów atomowych, pointerowych, złożonych., klas, metod, perspektyw, itd. Niektórzy lansują pogląd, że operator usuwania nie powinien być dostępny programiście, gdyż może prowadzić do tzw. zwisających pointerów. Programista usuwa pointery do obiektu, natomiast usunięcie samego obiektu załatwia automatyczny odśmiecacz (garbage collector). Ten pogląd jest powierzchowny, wynikający z niezrozumienia problemu. Pozostawienie obiektu, który już nie powinien istnieć, jest tak samo błędogenne jak zwisający pointer. Należy usuwać obiekty z automatycznym usunięciem/wyzerowaniem prowadzących do nich pointerów.

Inne operatory elementarne Operator deklaracji stałej lub makrosa. Można go uważać za operator tworzenia obiektu niemodyfikowalnego. Istotą tego operatora jest to, że wszelkie operacje związane z tym operatorem są wykonywane podczas kompilacji. Deklaracja ma np. postać: constant <nazwa> = <tekst>; gdzie <tekst> jest w zasadzie dowolnym tekstem (tzw. makrosem) który zastąpi po kompilacji każde użycie <nazwa>. Operator zmiany nazwy obiektu lub utworzenia nowego aliasu dla obiektu. Dotyczy raczej szczególnych sytuacji i jest możliwy wyłącznie w przypadku gdy nazwy obiektów są pierwszej kategorii programistycznej. Operator zmiany nazwy powinien być makroskopowy. Operatory dotyczące zdarzeń: wygenerowanie zdarzenia, przechwycenie zaistniałego zdarzenia.

Operatory warunkowe i przełączania Operator warunkowy: if <warunek> then <program1> else <program2> if <warunek> then <program> Wariant dla wyrażeń: if <warunek> then <zapytanie1> else <zapytanie2> Operator przełączania: posiada wiele wariantów, np. case <wyrażenie> do <etykieta 1>: <program 1>, <etykieta 2>: <program 2>, ... <etykieta n>: <program n> else <program n+1> Semantycznie, obliczane jest <wyrażenie>; jeżeli wyrażenie zwróci wartość <etykieta i> wówczas wykonywany jest <program i>. Jeżeli <wyrażenie> nie zwróciło wartości równej jakiejkolwiek etykiecie, wówczas wykonywany jest <program n+1>. Fraza else jest zwykle opcyjna.

Operatory pętli while <warunek> do <program> Operator powtarza wyliczenie wyrażenia <warunek> i wykonanie bloku <program> aż do momentu, kiedy <warunek> będzie nieprawdziwy. Jeżeli <warunek> jest na samym początku nieprawdziwy, wówczas <program> nie jest wykonywany ani razu. repeat <program> until <warunek> Najpierw następuje wykonanie bloku <program>, następnie wyliczenie wyrażenia <warunek>; jeżeli jest prawdziwy, to sterowanie jest przekazywane dalej, w przeciwnym przypadku powtarzane jest wykonanie bloku <program>, z ponownym wyliczeniem wyrażenia <warunek>. Modyfikacja tych operatorów polega na wprowadzeniu specjalnej instrukcji break, której wykonanie wewnątrz <program> powoduje przekazanie sterowania do instrukcji znajdującego się za tą konstrukcją. loop <program> przerwanie pętli wymaga użycia instrukcji break.

Operator for Intencją tego operatora jest powtórzenie wykonania pewnego programu dla kolejnych wartości tzw. zmiennej iteracyjnej, przyjmującej wartości z zadanego przedziału numerycznego, np. ze składnią: for <zmienna> = <wartość początkowa> until <wartość końcowa> do <program> Początkowa i końcowa wartość zmiennej iteracyjnej jest określona z góry przez programistę, zaś przyrost tej zmiennej w poszczególnych cyklach iteracyjnych wynosi 1. Składnia ta została uogólniona na przypadek, kiedy początkowa i końcowa wartość zmiennej oraz przyrost są określone przez dowolne wyrażenie: np.C: for( <zmienna iteracyjna> = <wyrażenie początkowe>; <warunek zakończenia pętli>; <instrukcja przyrostu zmiennej iteracyjnej> ) <program> Dodatkowo jest możliwe zakończenie cyklu iteracyjnego poprzez wydanie w dowolnym miejscu <program> instrukcji break.

Operator for each Intencja tego operatora jest nieco różna od operatora for. Chodzi tu o iterację obiegającą po kolei wszystkie elementy pewnej kolekcji, zapamiętanej lub wyliczonej przez zapytanie. Składnia jest zwykle następująca: for each <zmienna iteracyjna> in <kolekcja> do <program> Operator ten jest bardzo użyteczny dla programowania baz danych, zajmiemy się więc nim dokładniej nieco dalej.

Iteratory niższego poziomu Występują w postaci spójnej rodziny operatorów lub metod, niekiedy hermetyzowanej w postaci klasy lub szablonu. Służą do sekwencyjnego przetwarzania elementów kolekcji. Typowym przykładem są kursory znane z języka SQL lub iteratory. Klasyczny zestaw takich operatorów jest następujący (z dokładnością do nazw operatorów): getFirst, getNext, getPrior , wyjątek NoMoreElements Zwykle takie metody zwracają referencję do bieżącego elementu, która dalej jest przetwarzana przy pomocy standardowych metod (nie dotyczy to SQL). Iteratory niższego poziomu można zastąpić konstrukcją for each. Istnieją zadania, których nie można zrealizować przy pomocy for each; klasycznym przykładem jest algorytm zlania (merging) dwóch posortowanych zbiorów w jeden posortowany zbiór. Problem z iteratorami niższego poziomu polega na tym, że wymagają one wprowadzenia pojęcia „elementu bieżącego”. To prowadzi do trudności z zagnieżdżonym wywoływaniem iteratorów, w szczególności z rekurencją.

Procedury procedures Imperatywne języki programowania, w tym języki obiektowe, są wyposażone w procedury. Są to najważniejsze jak dotąd mechanizmy abstrakcji i hermetyzacji. Istotą procedur to, że hermetyzują one dowolnie złożony i długi kod. Wewnątrz kodu mogą być zadeklarowane lokalne zmienne lub obiekty, które są całkowicie niedostępne z zewnątrz procedury. Procedury mogą być wywoływane z wielu miejsc. Mogą być rekurencyjne. Ich przystosowanie do konkretnego celu następuje poprzez parametry. Procedury mogą mieć tzw. efekty uboczne. Pasywne efekty uboczne: kod wewnątrz procedury odwołuje się do pewnych danych/obiektów spoza ciała danej procedury. Aktywne efekty uboczne: kod wewnątrz procedury może zmieniać dane, obiekty spoza ciała danej procedury. Może także zmieniać stan środowiska komputerowego, np. stan ekranu lub plików.

Klasyfikacje procedur W zależności od pewnych dodatkowych własności procedury mogą być dalej podzielone wg następującej klasyfikacji: Procedury właściwe i procedury funkcyjne (zwane też funkcjami). Pierwsze nie zwracają wyniku, nie mogą więc być użyte jako składniki wyrażeń, drugie zaś zwracają wynik i przez to ich wywołania są wyrażeniami. Procedury i metody. Różnica dotyczy miejsca logicznego ulokowania kodu procedur oraz sposobu ich wywołania. Procedury są zwykle ulokowane w określonych jednostkach programu, np. w modułach, natomiast metody są ulokowane w klasach. Dodatkowo, niejawnym (pośrednim) parametrem metody jest obiekt, na którym ta metoda działa. Procedury znajdujące się po stronie programu aplikacyjnego i procedury przechowywane w bazie danych. W SZBD pojawił się typ procedury, zwany zapamiętaną procedurą (stored procedure) lub procedurą bazy danych (database procedure). Są to byty pierwszej kategorii programistycznej wiązane dynamicznie. Można je dynamicznie wstawiać i usuwać. Występują różne kombinacje tych aspektów klasyfikacyjnych.

Parametry procedur Istnieje wiele semantycznie różnych sposobów komunikowania parametrów do procedur. Najbardziej popularne: Call-by-value. Do wnętrza procedury przekazywana jest wartość parametru obliczona przed przekazaniem sterowania do wnętrza procedury. Wewnątrz procedury tworzy się lokalną zmienną zawierającą tę wartość. Call-by-reference. Do wnętrza procedury przekazywana jest referencja do zmiennej/obiektu. Dzięki temu wewnątrz procedury można dokonać zmiany stanu (aktualizacji) obiektu zewnętrznego w stosunku do tej procedury. Call-by-name. Parametr przekazuje się w postaci kodu wyrażenia będącego parametrem aktualnym. Tekst ten zastępuje (na zasadzie makro) wszystkie wystąpienia parametru formalnego w ciele procedury. Razem z parametrem "przekazuje się" środowisko zewnętrzne procedury. Technika jest "brudna". Call-by-need. Technika przekazywania parametrów określana także jako „leniwa ewaluacja” (lazy evaluation). Oznacza opóźnienie wyliczania wartości parametru aktualnego do momentu, kiedy będzie on rzeczywiście potrzebny wewnątrz ciała procedury. Technika jest "brudna". Niektóre z tych metod będą uogólnione dla parametrów - zapytań.

Dalsze cechy języka programowania Przy konstrukcji języka programowania uwzględnia się dalsze cechy: System kontroli typologicznej: bardzo istotny dla podwyższenia niezawodności programowania. Polega na przypisywanie typów wszystkim bytom występującym w programie (obiektom, funkcjom, procedurom, metodom, itd.), oraz sprawdzanie zgodności użycia danego bytu w programie z jego zadeklarowanym typem. Hermetyzowane abstrakcje: moduły, klasy, interfejsy, bloki programu. Zdarzenia, wyjątki, aktywne reguły (tryggery), obsługa zdarzeń/wyjątków. Asercje, reguły bezpieczeństwa i inne środki podwyższenia niezawodności. Zarządzanie transakcjami, procesy, wątki i inne środki synchronizacji równolegle działających programów. Programowanie generyczne: metamodel i środki dostępu do metamodelu, refleksja lingwistyczna.

Wybrane operatory imperatywne bazujące na SBQL

Operator for each Składnia: for each <zapytanie> do <program> Semantyka: przyjmujemy zasadę operatorów nie-algebraicznych Dla każdego e zwróconego przez <zapytanie>, ENVS jest podwyższany o nested(e). W modelach M1, M2, M3, jeżeli e zawiera referencje do obiektów, to na ENVST są wkładane sekcje z binderami do wnętrza odpowiednich klas. Następnie wykonywany jest <program>. Po zakończeniu wykonania <program> ENVS wraca do poprzedniego stanu. Cykl ten jest powtarzany dla wszystkich elementów zwróconych przez <zapytanie>. Tak jak w operatorach nie-algebraicznych, "zmienna iteracyjna" jest definiowana przez operator as i nie musi wystąpić. Przykłady: for each Prac where Stan = "asystent" do Zar := Zar + 100; for each (Prac where Stan = "asystent") as p do { p.Zar := p.Zar + 100; p.Stan := "starszy asystent"};

Operator podstawienia W oryginale ten operator ma postać <wyrażenie1> := < wyrażenie2>. <wyrażenie1> zwraca referencję czyli id.obiektu (l-value), <wyrażenie2> zwraca wartość (r-value), którą należy zapisać do obiektu wskazanego przez referencję. Jeżeli <wyrażenie2> zwraca referencję, to przed podstawieniem należy zastosować operator dereferencji. Jak zmienić ten operator aby stał się makroskopowy? Jeżeli zastosowalibyśmy składnię <zapytanie1> := <zapytanie2>, gdzie <zapytanie1> zwraca referencje, zaś <zapytanie2> zwraca wartości, to powstaje problem: którą wartość dopasować do danej referencji? Kolekcja bag nie zawiera informacji o kolejności elementów. Są dwa sposoby: 1. Operator nie jest makroskopowy. Makroskopowe podstawienie można osiągnąć poprzez for each - przykłady na poprzednim slajdzie. 2. Wprowadzić specjalny operator podstawienia (nazwijmy go assign), który będzie działać na kolekcji par, gdzie pierwszym elementem będzie referencja zaś drugim - wartość.

Operator assign Składnia: assign <zapytanie> <zapytanie> musi zwrócić kolekcje par <referencja, wartość>. Przykład: Podwyższ wszystkim pracownikom zarobek o 100. assign Prac.(Zar, Zar+100); Przykład: Niech dla działów mających lokację w Radomiu nazwa będzie identyczna z nazwiskiem ich szefów. assign ((Dział where "Radom" in Lokacja) as d)  ((d.Szef.Prac) as s). (d.Nazwa, s.Nazwisko); Podstawienie nie-makroskopowe (z ewentualnym użyciem operatora for each) jest bardziej intuicyjne dla użytkowników niż operator assign. Główną zaletą koncepcji wprowadzającej operator assign jest podwyższenie mocy i generyczności języka poprzez umożliwienie zakomunikowania danych do makroskopowego podstawienia jako parametru procedury.

Podstawienie na obiekt pointerowy Z zasady korespondencji wynika, że jeżeli wprowadziliśmy obiekty pointerowe, to musimy zapewnić ich aktualizację. Specjalny operator podstawienia :=& różni się od operatora := tym, że po obliczeniu prawej strony (r-value) nie wykonywana jest dereferencja. Operator ten można wprowadzić w wersji nie-makroskopowej (z użyciem for each) oraz w wersji makroskopowej, z użyciem specjalnego operatora (nazwijmy go assign pointer). Przykład: Niech wszyscy programiści przejdą do działu Produkcja. for each Prac where Stan = "programista" do PracujeW :=& (Dział where Nazwa = "Produkcja"); assign pointer (Prac where Stan = "programista") . (PracujeW , (Dział where Nazwa = "Produkcja")); Jeżeli skład zawiera informację o bliźniaczych pointerach, to aktualizacja pointera (pointerów) powinna automatycznie wyzwalać spójną aktualizację jego bliźniaka (bliźniaków). Tak jak w wiązaniu do C++ standardu ODMG.

Procedury w SBQL Składnia: procedure <nazwa> (<parametry formalne>) {<program>}; Nie będziemy różnicować procedur i procedur funkcyjnych. Procedura funkcyjna musi kończyć swoje działanie instrukcją return <zapytanie>, gdzie <zapytanie> określa wynik działania procedury. <zapytanie> może być konstruowane na podstawie lokalnego środowiska procedury oraz środowiska globalnego (np. bazy danych), ale nie powinno zwracać referencji do obiektu znajdującego się w jego lokalnym środowisku. Po zakończeniu działania procedury obiekty te przestaną istnieć. Parametry aktualne procedur są zapytaniami. Parametry mogą być wejściowe (call-by-value) lub wyjściowe (call-by-reference). Rozróżnienie jest np. poprzez słowo kluczowe var. Semantyka: procedura podwyższa stos ENVS wkładając na jego wierzchołek nowa sekcję (zapis aktywacyjny). Zawiera ona: bindery do lokalnych obiektów procedury, bindery do wartości parametrów aktualnych procedury, ślad powrotu z procedury.

Przykład procedury Deklaracja: procedura zmienia dział dla pracowników. Referencje do pracowników i ich nowego działu są jej parametrami. Wywołanie procedury: Przenieść wszystkich analityków do działu kierowanego przez Nowaka: Wersja procedury z automatyczną aktualizacją bliźniaczych pointerów Zatrudnia i PracujeW: procedure ZmieńDział( var P; var D ) { delete Dział.Zatrudnia where Prac in P; for each P as p do { p.PracujeW :=&D create local pointer Zatrudnia( &p ); insert Zatrudnia into D;}; }; ZmieńDział( Prac where Stan = "analityk"; Dział where (Szef.Prac.Nazwisko) = "Nowak" ); procedure ZmieńDział( var P; var D ) { for each P do PracujeW :=&D };

Przykład procedury funkcyjnej Procedura MałoZarabiający zwraca nazwisko, zarobek i nazwę działu dla mało zarabiających pracowników określonych zawodów. Mało zarabiający zarabia mniej niż średnia zarobków. Wynik jest strukturą z nazwami N, Z, D. Przykład: Podaj nazwiska i zarobek dla mało zarabiających piekarzy i stolarzy z działu produkcji. Przykład: Podwyższ o 100 zarobek wszystkim mało zarabiającym programistom z działu konserwacji. procedure MałoZarabiający ( Stanowiska ) { create local Średnia( avg( Prac.Zar ) ); create local pointer Mało( & (Prac where Stan in Stanowiska and Zar < Średnia) ); return Mało.Prac.( Nazwisko as N, Zar as Z, (PracujeW.Dział.Nazwa) as D) }; (MałoZarabiający ( bag("piekarz", "stolarz") ) where D = "produkcja") . (N, Z) for each MałoZarabiający ( bag("programista") ) where D = "Konserwacja" do Z := Z+100;

Wykład 10

Metody w SBQL Są procedurami umieszczonymi w ramach klas w modelach M1, M2 i M3. Istotą ich jest to, ze działają na środowisku obiektu, w stosunku do których są wywoływane. Implikuje to określone reguły zakresu, czyli omijanie lub nie omijanie sekcji ENVS przy wiązaniu. Przykład: metoda ZarobekNetto umieszczona w klasie KlasaPrac: Predefiniowana nazwa self - czy jest potrzebna? Przy podejściu stosowym jest zbędna. Jest jednak istotna dla modelowania pojęciowego, ponieważ explicite odwołuje się do obiektu, na którym działa metoda. method ZarobekNetto( ) { if self.Zar <500 then return self.Zar else if self.Zar < 1000 then return 0.8 * (self.Zar - 500) + 500 else return 0.7 * (self.Zar - 1000) + 900 };

Makroskopowe parametry procedur i metod (1) Identyczne techniki transmisji parametrów dotyczą procedur i metod. Rodzaj techniki transmisji parametrów jest określony przez składnię. Wołanie przez wartość (call-by-value) - wariant Pascala: Składnia deklaracji: procedure NazwaProcedury( ..., NazwaParam, ...){...ciało...}; Składnia wywołania: NazwaProcedury( ..., zapytanie, ...) Niech zapytanie zwróci bag{ v1,v2, ... }, gdzie v1, v2, ...  Rezultat Po wywołaniu procedury zapis aktywacyjny procedury/metody (na wierzchołku ENVS) będzie zawierał bindery: ..., NazwaParam( dereferencja( v1 ) ), NazwaParam( dereferencja( v2 ) ), ... Dzięki temu wewnątrz ciała procedury do parametru można będzie odwołać się poprzez NazwaParam, zaś wynikiem tego odwołania będzie bag{dereferencja( v1 ), dereferencja( v2 ), ... }. Wewnątrz ciała nie można dokonać podstawienia na NazwaParam, gdyż wiązanie nazwy NazwaParam nie zwraca referencji.

Makroskopowe parametry procedur i metod (2) Wołanie przez wartość (call-by-value) - wariant C (różnica semantyczna): Składnia deklaracji - jak poprzednio: procedure NazwaProcedury( ..., NazwaParam, ...){...ciało...}; Składnia wywołania - jak poprzednio: NazwaProcedury( ..., zapytanie, ...) Niech zapytanie zwróci bag{ v1,v2, ... }, gdzie v1, v2, ...  Rezultat Po wywołaniu procedury następuje utworzenie obiektów lokalnych: <ip1, NazwaParam, dereferencja( v1 )>, <ip2, NazwaParam, dereferencja( v2 )>,... Zapis aktywacyjny procedury/metody (na wierzchołku ENVS) będzie zawierał bindery: ..., NazwaParam( ip1 ), NazwaParam( ip2 ), ... Dzięki temu wewnątrz ciała procedury do parametru można będzie odwołać się poprzez NazwaParam (jak poprzednio), ale wynikiem tego odwołania będzie bag{ip1, ip2 , ... }. Wewnątrz ciała można zatem dokonać podstawienia na NazwaParam - są to zwyczajne obiekty lokalne. Jeżeli parametry nie są makroskopowe, to (podobnie j.w.) tworzy się na ENVS jeden binder (+ewentualnie jeden obiekt).

Przykład parametru wołanego przez wartość Parametrem procedury jest kolekcja nazwisk, wynikiem jest kolekcja par, w której pierwszym elementem referencja do pracownika posiadającego nazwisko znajdujące się w kolekcji będącej parametrem, drugim zaś jest referencja do jego kierownika. procedure PracSzef( Naz ) { return (Prac where (Nazwisko in Naz))  (PracujeW.Dział.Szef.Prac); }; Wywołanie procedury: PracSzef( bag( "Kowal", "Nowak" ) ) W wariancie Pascala, po wywołaniu procedury na wierzchołku ENVS umieszczona będzie sekcja zawierająca bindery: Naz("Kowal"), Naz("Nowak") W wariancie C, po wywołaniu procedury utworzone będą lokalne obiekty <ip1,Naz,"Kowal">, <ip2,Naz,"Nowak">, zaś sekcja na wierzchołku ENVS będzie zawierać bindery Naz(ip1), Naz(ip2). Po zakończeniu procedury obiekty <ip1,Naz,"Kowal">, <ip2,Naz,"Nowak"> muszą być usunięte.

Makroskopowe wołanie przez referencję call-by-reference Składnia deklaracji: procedure NazwaProcedury( ..., var NazwaParam, ...){...ciało...}; Składnia wywołania: NazwaProcedury( ..., zapytanie, ...) zapytanie musi zwrócić kolekcję referencji bag{i1,i2, ... }. Nie jest wykonywana dereferencja. Po wywołaniu procedury zapis aktywacyjny procedury/metody (na wierzchołku ENVS) będzie zawierał bindery: ..., NazwaParam(i1), NazwaParam(i2 ), ... Wewnątrz ciała procedury do parametru można będzie odwołać się poprzez NazwaParam, zaś wynikiem tego odwołania będzie bag{i1 , i2, ... }. Wewnątrz ciała można dokonać podstawienia na NazwaParam, co oznacza, że w istocie podstawienie jest dokonywane na obiekcie, którego referencja została zakomunikowana jako parametr. Podobnie dla przypadku, kiedy zapytanie zwróci pojedynczą referencję.

Przykład parametru wołanego przez referencję Parametrem procedury jest kolekcja referencji do pracowników, wynikiem jest podwyższenie ich uposażenia o zadaną wielkość. procedure Podwyżka ( var dlaPrac; Ile ) { for each dlaPrac do Zar := Zar + Ile; }; Wywołanie procedury: Podwyżka( Prac where Stan = "pielęgniarka"; 202 ); Niech zapytanie Prac where Stan = "pielęgniarka" zwróci bag{i11, i13, i55}. Po wywołaniu procedury na wierzchołku ENVS będzie umieszczona sekcja z binderami: dlaPrac(i11), dlaPrac(i13), dlaPrac(i55), Ile(202).

Makroskopowe wołanie strict-call-by-value Wynik zapytania bez jakichkolwiek zmian przekazuje się jako parametr do wnętrza procedury. Nie jest wykonywana dereferencja. Dla parametru formalnego p, dla którego parametr aktualny - zapytanie - zwrócił wynik r na wierzchołku ENVS tworzy się pojedynczy binder p(r). Ta metoda łączy call-by-value z call-by-referencje. procedure WydatkiDziałów( któreDziały ) { return któreDziały.(Nazwa, sum(Zatrudnia.Prac.Zar)); }; Wywołanie procedury: WydatkiDziałów(Dział where Lokacja  bag("Radom", "Kalisz")) Jeżeli zapytanie Dział where Lokacja  bag("Radom", "Kalisz") zwróci bag{i15, i35, i59}, to na wierzchołek ENVS wkłada się pojedynczy binder któreDziały( bag{i15, i35, i59}).

Perspektywy w SBQL

Perspektywy w SBQL są procedurami funkcyjnymi Taka procedura jest składową bazy danych: może być do niej dynamicznie wstawiona i usunięta. Procedura taka na ogół zwraca kolekcję elementów, które są interpretowane jako "obiekty wirtualne". Może też zwrócić pojedynczy element ("obiekt wirtualny"). Nazwa procedury funkcyjnej staje się nazwą "obiektów wirtualnych". Pamiętamy jednak, że w SBQL zapytania, a więc i procedury, nigdy nie zwracają obiektów, lecz kombinację wartości, referencji i nazw. Procedury takie, podobnie do danych, podlegają ochronie dostępu. Np. pewien użytkownik nie ma dostępu do obiektów, ale może wywołać taką procedurę, co oznacza, że ma dostęp do "obiektów wirtualnych". W zależności od tego gdzie taka procedura jest umieszczona w strukturze obiektowej możemy mieć: wirtualne obiekty (procedura jest na czubku struktury obiektowej); wirtualne atrybuty (procedura jest umieszczona wewnątrz klasy). Procedura może mieć lokalne środowisko (obiekty), może mieć parametry, może być rekurencyjna. Jak poprzednio.

Przykład struktury danych Mama Zatrudnia Dział Dnr Nazwa Lokacja * * Prac Pnr Stan Zar ZarNetto Osoba Nazwisko Płeć RokUrodz Wiek Rodzina 0..1 1 PracujeW Dziecko * 1 Tata Szef 0..1 0..1 Kieruje Dziecko * Powiązania rodzinne - drzewo genealogiczne - dla ilustracji procedur rekurencyjnych. Perspektywy są koncepcyjnie równoważne tzw. "regułom dedukcyjnym" w tzw. "dedukcyjnych bazach wiedzy" (Datalog). Rekurencyjne perspektywy z pełną mocą obliczeniową dają znacznie więcej niż "reguły dedukcyjne". Przede wszystkim są manipulowalne jako hermetyzowane byty i są zintegrowane z całością środowiska programistycznego.

Przykład perspektywy Zwraca wirtualne obiekty BogatyPrac dla takich pracowników, który zarabiają brutto co najmniej 3000. W ramach tych obiektów zwraca nazwisko pracownika jako Nazwisko, nazwisko jego szefa jako Szef oraz zarobek netto jako Zarobek. procedure BogatyPrac { return (Prac where Zar >= 3000). ( Nazwisko as Nazwisko, (PracujeW.Dział.Szef.Prac.Nazwisko) as Szef, ZarNetto() as Zarobek); }; Wirtualne obiekty: Nazwiska i zarobki netto bogatych pracowników pracujących dla Wilickiego. (BogatyPrac where Szef = "Wilicki") . (Nazwisko, Zarobek) BogatyPrac Nazwisko Szef Zarobek

Przykład - perspektywa z parametrem Procedura MłodyPrac zwraca wirtualne obiekty o postaci MłodyPrac(Nazw, Wiek, Firma) dla osób o podanym zawodzie, których wiek jest mniejszy o co najmniej 10 lat od średniej wieku. procedure MłodyPrac( Zawód ) { create local GranicaWieku( avg( Prac.Wiek) - 10); (* lokalny obiekt *) return (Prac where Stan = Zawód and Wiek < GranicaWieku ) . ( Nazwisko as Nazw, Wiek as Wiek, (PracujeW.Dział.Nazwa) as Firma ); }; Zapytanie: Podaj wszystkie stanowiska oraz średnią wieku młodych pracowników pracujących na tych stanowiskach, jeżeli istnieją; w przeciwnym wypadku podaj informację "Nie ma młodych". (distinct(Prac.Stan) as z)  ((MłodyPrac(z) group as m) . (if count(m) > 0 then avg(m.Wiek) else "Nie ma młodych" ))

Wynik procedury będącej perspektywą Co zwraca wywołanie procedury MłodyPrac? Zgodnie z semantyką SBQL, zwracany jest bag zawierający 3-elementowe struktury binderów: bag{ struct{ Nazw(iNazwisko1), Wiek(w1), Firma(iNazwa1) }, struct{ Nazw(iNazwisko2), Wiek(w2), Firma(iNazwa2) }, struct{ Nazw(iNazwisko3), Wiek(w3), Firma(iNazwa3) }, ... } gdzie iAtrybutj jest referencją do odpowiedniego atrybutu. Jest to równoważne sytuacji, jak gdyby nasz diagram obiektowy został uzupełniony o klasę “wirtualnych obiektów” MłodyPrac. Możliwa jest aktualizacja zapamiętanych danych poprzez atrybuty Nazw i Firma, gdyż zwracają one referencje. Nie wprowadzamy specjalnej składni dla nazywania atrybutów “wirtualnych obiektów” (jak w SQL). Jest to zaleta dla optymalizacji zapytań. MłodyPrac Nazw Wiek Firma

Perspektywy rekurencyjne Podejście stosowe jest przygotowane do dowolnej rekurencji. Np. należy zdefiniować obiekty wirtualne Genealogia z atrybutami Człowiek, JegoPrzodkowie, która zwraca wielozbiór par, gdzie pierwszym elementem jest referencja do osoby, zaś drugim - kolekcja referencji do jej przodków. Najpierw definiujemy procedurę rekurencyjną z parametrem w postaci wielozbioru referencji do osób. procedure Przodkowie( os ) { return ((os.(Mama union Tata).Osoba) as rodzic) . (rodzic union Przodkowie(rodzic))}; procedure Genealogia { return (Osoba as Człowiek)  (Przodkowie(Człowiek) group as JegoPrzodkowie);}; Podaj przodków Malinowskiego urodzonych przed 20-tym wiekiem: (Genealogia where (Człowiek.Nazwisko) = "Malinowski") . (JegoPrzodkowie where RokUrodz < 1900)

Perspektywy rekurencyjne a dedukcyjne reguły W obecnym sformułowaniu perspektywy koncepcyjnie przykrywają tzw. "reguły dedukcyjne" (i tzw. "dedukcyjne bazy danych"). Są jednak w stanie zaoferować znacznie, znacznie więcej. Klasyczne trudne zadanie dedukcyjnych baz danych - reguła CousinsSameGeneration: dla danej osoby podaj wszystkich kuzynów tej samej generacji. Jedno z wielu rozwiązań: Procedura Ancestors dla referencji os (do osoby) z poziomem generacji gen zwraca referencje do jej przodków z poziomem generacji odpowiednio wyższym. procedure Ancestors( os, gen ) { return (os.(Mama union Tata).Osoba) as p, (gen+1) as g) . ((p, g) union Ancestors( p, g ))}; procedure CousinsSameGeneration( person ) { return ((Osoba as s) where  Ancestors(s, 0) as x ( x in Ancestors(person, 0) ) };

Aktualizacja perspektyw - przykłady procedure MałoZarabiający ( Stanowiska ) { return (Prac where Stan in Stanowiska and Zar < avg( Prac.Zar ) ) . ( Nazwisko as N, Zar as Z, (PracujeW.Dział.Nazwa) as D) }; Podnieś o 100 zarobki mało zarabiającym asystentom i analitykom z działu informatyki: for each MałoZarabiający( bag( “asystent”, “analityk” ) ) where D = “Informatyka” do Z := Z + 100; Po wykonaniu tego zlecenia niektórzy mało zarabiający mogą przestać być mało zarabiającymi, zaś inni staną się mało zarabiającymi. Niektóre wirtualne obiekty znikną z naszej perspektywy, zaś pojawią się nowe. Dla mało zarabiającego asystenta Nowaka zmień dział na Marketing: for each MałoZarabiający( bag{ “asystent”} ) where N = “Nowak” do D := “Marketing”; Zlecenie można wykonać, lecz jego intencja jest zakłócona. Zmieniona będzie nazwa działu, w którym dotychczas pracował Nowak, a nie o to chodziło. (Analogia do aktualizacji po stronie klucza głównego w Oracle.) Z przykładów widać, że nasze podejście daje duże możliwości w zakresie aktualizacji perspektyw. Nie rozwiązuje jednak wszystkich problemów.

Najprostszy zabieg - ograniczyć aktualizację Specjalna składnia dla perspektyw umożliwiające ograniczenie operacji wykonywanych na elementach zwracanych przez perspektywę: view MałoZarabiający ( Stanowiska ) { updateable N, Z; return (Prac where Stan in Stanowiska and Zar < avg( Prac.Zar ) ) . ( Nazwisko as N, Zar as Z, (PracujeW.Dział.Nazwa) as D) }; Semantyka nie jest problemem: po materializacji perspektywy każda nieaktualizowalna referencja (w tym przypadku D) zwracana przez perspektywę zostaje wyposażona we flagę "Nie podlega aktualizacji". Zastosować dereferencję dla tych referencji, poprzez które aktualizacja ma być zabroniona: procedure MałoZarabiający ( Stanowiska ) { ( Nazwisko as N, Zar as Z, deref(PracujeW.Dział.Nazwa) as D) }; Jest to równoważne ustawieniu flagi "Nie podlega aktualizacji".

Obiekty wirtualne vs. "klasa wirtualna" Nie wprowadzamy pojęcia "klasy wirtualnej". Nie wiadomo co miałoby ono znaczyć. Obiekty wirtualne są wartościami, które "symulują" obiekty. Nie ma sensu mówić o identyfikatorach (OID) takich obiektów wirtualnych. Jeżeli procedura zwraca referencje do obiektów, wówczas z definicji są one podłączone do swoich klas (co wynika ze stosowej semantyki). Np. wywołanie procedury procedure Kobieta { return Osoba where Płeć = “K” }; zwraca referencje (identyfikatory) obiektów Osoba, które dziedziczą z klasy Osoba. Poprawne jest więc zapytanie z użyciem metody Wiek znajdującej się w klasie Osoba: Kobieta where Wiek > 25 Niepoprawne jest zapytanie: MałoZarabiający( bag( “asystent”) ) where Wiek > 25; ponieważ wynik procedury MałoZarabiający nie jest podłączony do jakiejkolwiek klasy.

Klasa dla obiektów wirtualnych Powinna to być normalna klasa, w której będą znajdowały się inwarianty (np. nowe metody) obiektów wirtualnych. Definicji perspektywy powinna uwzględniać podłączenie obiektów wirtualnych do takiej klasy. Np. Klasa jest zdefiniowana jak zwykle: Można teraz używać metody LiczbaDzieci w stosunku do obiektów wirtualnych: Wartość zwrócona przez perspektywę Kobieta będzie wyposażona we flagę informującą, która perspektywa te wartość zwróciła. Dzięki niej operator niealgebraiczny przetwarzający tę wartość włoży odpowiednio sekcję z binderami do cech tej klasy na stos ENVS. view Kobieta() { connect to own class KlasaKobiet; return Osoba where Płeć = “K”; }; class KlasaKobiet inherits from Osoba { method LiczbaDzieci { return count( Dziecko ) }}; (Kobieta where LiczbaDzieci > 3). Nazwisko

Można uwzględnić w definicji perspektywy: Uniemożliwienie dostępu do pewnych atrybutów/metod obiektów rzeczywistych “widzianych” poprzez obiekty wirtualne. Dodatkowa klauzula visible i/lub invisible. Uniemożliwienie aktualizacji pewnych atrybutów wirtualnych. Dodatkowa klauzula updateable i/lub non updateable. Odłączenia obiektów wirtualnych od klas obiektów rzeczywistych “widzianych” poprzez obiekty wirtualne. Dodatkowa klauzula not a member of. Podłączenie obiektów wirtualnych do istniejących klas. Dodatkowa klauzula is a member of. Podłączenie obiektów wirtualnych do nowo powołanych klas. Dodatkowa klauzula connect to own class. Klasa ta należy do definicji perspektywy, jest usuwana razem z perspektywą. Własności te można osiągnąć poprzez flagę skojarzoną z wartością zwracaną przez perspektywę i modyfikację mechanizmu ENVS.

Wirtualne atrybuty Inaczej metody funkcyjne. Załóżmy, że wewnątrz klasy Dział umieszczamy następującą procedurę: Procedura będzie wykonywana w środowisku “wnętrza” obiektu Dział. Zawiera ono atrybuty Dnr, Nazwa, Lokacja oraz pointery Zatrudnia i Szef. Z tego powodu zapytanie wewnątrz ciała procedury zaczyna się od Zatrudnia. Można byłoby użyć self.Zatrudnia, co niekiedy zwiększyłoby czytelność. Podaj lokację działów, w których średni zarobek jest większy od 1500: W zależności od miejsca ulokowania i środowiska na którym działa procedura może ona być perspektywą lub wirtualnym atrybutem. Podejście stosowe zunifikowało te sytuacje. Dla większości obecnych systemów klasy są obywatelami drugiej kategorii, w związku z czym nie można do nich nic dynamicznie wstawiać ani usuwać. To ogranicza dynamizm wstawiania/usuwania wirtualnych atrybutów. procedure ŚredniaZarobków { return avg( Zatrudnia.Prac.Zar ); }; (Dział where ŚredniaZarobków > 1500). Lokacja

Wirtualne powiązania Podobnie jak poprzednio, są to metody funkcyjne. Różnicą jest jedynie to, że wirtualne powiązanie zwraca referencje do obiektów. Utwórz wirtualny związek Podwładny w klasie Prac prowadzący od pracownika będącego szefem do wszystkich jego podwładnych. Podaj nazwiska ludzi, których szefem jest Abacki. Podane rozwiązanie nie jest "przezroczyste": powinno być: Dla uzyskania całkowitej zgodności i przezroczystości wirtualnych powiązań należałoby zastosować dodatkowe zabiegi, np. jak poprzednio, flagę skojarzoną z referencją zwracaną przez metodę oraz prostą modyfikację mechanizmu ENVS związaną z tą flagą. Wirtualne związki nie są symetryczne, ale można (na ogół) uczynić je symetrycznymi. Np. umieszczamy w klasie Prac następującą metodę: procedure Podwładny { return Kieruje.Dział.Zatrudnia.Prac }; (Prac where Nazwisko = "Abacki"). Podwładny. Nazwisko (Prac where Nazwisko = "Abacki"). Podwładny. Prac. Nazwisko procedure Przełożony { return PracujeW.Dział.Szef.Prac };

Modyfikacja zapytań Modyfikacja zapytań jest podstawową metoda optymalizacji zapytań używających perspektyw. Jest stosowana we wszystkich systemach relacyjnych. Oryginalnie została sformułowana przez M.Stonebrakera w 1975 roku, ale wskutek braku ortogonalności ówczesnych języków zapytań (w szczególności QUEL i SQL) sformułowanie jest bardzo złożone (i mętne). Przy pełnej ortogonalności języka (cecha SBQL) metoda ta staje się banalna i sprowadza się do następującego stwierdzenia: Metoda modyfikacji zapytań polega na tym, że definicję perspektywy traktuje się jako makro-definicję. Wszędzie tam, gdzie w zapytaniach występuje nazwa perspektywy, zastępuje się tę nazwę poprzez tekst będący definicją tej nazwy (pomijając nieistotne elementy leksykalne). Po tym zabiegu uzyskuje się zapytanie bez odwołań do perspektyw. Poddaje się go następnie standardowym metodom optymalizacyjnych, np. przesuwaniem operatorów, usunięciu martwych podzapytań, wykorzystaniu indeksów, itd.

Przykład modyfikacji zapytań (1) Załóżmy, że mamy perspektywę: Załóżmy, że mamy zapytanie: Zmaterializowanie wyniku procedury będzie bardzo kosztowne. Atrybut Nazwisko ma indeks, który zapewnia szybki dostęp do obiektów wg nazwisk. W powyższym zapytaniu indeksu nie można wykorzystać. Wyliczenie nazwy działów dla wszystkich mało zarabiających jest niepotrzebne, bo tej danej zapytanie nie wykorzystuje. Modyfikacja zapytań usuwa te problemy. Dzięki niej w ogóle nie trzeba będzie liczyć tej perspektywy, w szczególności jej niepotrzebnych członów. Można będzie wykorzystać indeks. procedure MałoZarabiający { return (Prac where Zar < 0.5 * avg( Prac.Zar ) ) . ( Nazwisko as N, Zar as Z, (PracujeW.Dział.Nazwa) as D) }; (MałoZarabiający where N = "Bilski") . Z

Przykład modyfikacji zapytań (2) Dokonujemy makro-substytucji: zastępujemy nazwę perspektywy przez treść jej definicji: Dokonujemy optymalizacji tekstu zapytania przez przepisywanie: Człon (PracujeW.Dział.Nazwa) as D) nie jest używany (jest martwym podzapytaniem); może być usunięty. Człon 0.5 * avg( Prac.Zar ) może być "wyciągnięty przed nawias" i policzony tylko raz: ((Prac where Zar < 0.5 * avg( Prac.Zar )). (Nazwisko as N, Zar as Z, (PracujeW.Dział.Nazwa) as D)) where N = "Bilski") . Z ((Prac where Zar < 0.5 * avg( Prac.Zar )). (Nazwisko as N, Zar as Z)) where N = "Bilski") . Z ((0.5 * avg( Prac.Zar )) as x ) . (((Prac where Zar < x) . (Nazwisko as N, Zar as Z)) where N = "Bilski") . Z )

Przykład modyfikacji zapytań (3) Definicje pomocniczych nazw N i Z stają się zbędne; można je usunąć, zastępując oryginalnymi nazwami Nazwisko i Zar: Dwa następujące po sobie operatory where łączymy w jeden: Wykorzystujemy dostęp przez IndeksPracNazwisko( N ), który na podstawie stringu N będącego nazwiskiem zwraca referencje do obiektów Prac: Zapytanie jest ostatecznie zoptymalizowane. Optymalizacja odbywała się na podstawie reguł, które można dość łatwo sformalizować i zaimplementować. ((0.5 * avg( Prac.Zar )) as x ) . (((Prac where Zar < x) where Nazwisko = "Bilski"). Zar ) ((0.5 * avg( Prac.Zar )) as x ) . ((Prac where Zar < x and Nazwisko = "Bilski"). Zar ) ((0.5 * avg( Prac.Zar )) as x ) . ((IndeksPracNazwisko( "Bilski" ) where Zar < x ). Zar )

Warunki poprawności modyfikacji zapytań Modyfikacja zapytań oznacza potraktowania perspektywy jako makro-definicji. Pytanie: czy i kiedy to jest semantycznie równoważne? Warunki: Procedura definiująca perspektywę nie ma własnego środowiska. Nie występuje rekurencja w wywoływaniu perspektyw. Środowisko w którym wywoływana jest procedura jest takie samo jak środowisko, w którym ewaluowane jest zapytanie wewnątrz tej procedury. Dla większości przypadków wykorzystania perspektyw nie są to warunki mocne. Mogą być w niektórych sytuacjach osłabione. Jeżeli perspektywa ma parametry, wówczas makrosubstytucja wymaga potraktowania każdego parametru również jako makro. To sugeruje metodę transmisji parametrów znaną jako call-by-name. Powstaje pytanie, kiedy call-by-name jest semantycznie równoważne przyjętej w danym języku metodzie transmisji parametrów, np. call-by-value.

Na zakończenie ... Podstawowe tematy związane z obiektowymi językami zapytań zostały omówione, ale pozostały jeszcze inne. Wiele z nich stanowi pole działalności badawczej, m.in. następujące tematy: Języki schematu dla obiektowych baz danych (a la ODMG ODL), metamodele dla obiektowych baz danych. Mocna kontrola typów dla języków zapytań i języków programowania opartych na językach zapytań; polimorficzny system typów. Tranzytywne domknięcia i równania stało-punktowe w językach zapytań. Modele składu i konstrukcje języków uwzględniające sekwencje, sortowanie i operacje zależne od kolejności elementów. Języki zapytań uwzględniające dane nieregularne i półstrukturalne. Aktualizacja wirtualnych perspektyw z uwzględnieniem możliwości sterowania przez definiującego intencją tej aktualizacji. Programowanie generyczne z refleksją. Przetwarzanie zapytań w rozproszonych obiektowych bazach danych. Metody optymalizacji obiektowych zapytań.