Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Języki i środowiska programowania systemów rozproszonych Wykładowca: Tomasz Kowalski Wykłady przygotowane na podstawie materiałów prof. Kazimierza Subiety Wykład 9 Przetwarzanie struktur nieregularnych
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Dane nieregularne (pół-strukturalne) Problem danych nieregularnych, zwanych również pół-strukturalnymi (semistructured) jest nowym sformułowaniem problemu wartości zerowych w bazach danych (NULL values). Problem obrósł ogromną literaturą (ponad 500 pozycji) i w swoim czasie był ulubionym tematem licznych prac naukowych w kontekście tzw. niekompletnej informacji. Powstały liczne prace dotyczące specjalnych algebr z wartościami zerowymi, specjalnych rachunków, trój-wartościowych i cztero- wartościowych logik, odpytywania dysjunktywnej informacji i zbiorów możliwych światów, i wiele innych. Skrajnie mizerne rezultaty przy ogromnym nacisku badawczym. Świat informatyki komercyjno-przemysłowej nie uznał więc tych rozwiązań za dostatecznie istotne; Nie pojawiły się nawet próby implementacji czegokolwiek z tej góry poronionych pomysłów.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Wartości zerowe w SQL Problem wartości zerowych znalazł swoje odbicie praktyczne w SQL. Sposób wprowadzenia wartości zerowych do SQL ignoruje zasadę korespondencji i wprowadza niespójności i rafy semantyczne. W SQL wartość zerowa nie ma żadnej zewnętrznej semantyki, np. do reprezentowania niekompletnej informacji. Jest to wyłącznie trik techniczny mający na celu dostarczenie projektantom i programistom możliwości uwzględnienia nieregularności w danych. Projektant lub programista może go stosować do dowolnie wybranych celów, i on jest też jedynym autorytetem który wie, jaka jest zewnętrzna (biznesowa) semantyka wprowadzonych poprzez ten trik wartości zerowych.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Trzy sposoby przetwarzania nieregularności 1.System typów danego uniwersalnego języka programowania (np. Java) przystosujemy w taki sposób, aby każdy wynik zapytania mógł być potraktowany jako poprawna wartość pewnego znanego w danym momencie typu. Sposób ten jest mało realny ze względu na znaczne trudności koncepcyjne i implementacyjne dla programisty aplikacyjnego. 2.Spłaszczenie hierarchicznej struktury zwracanej przez zapytanie. Przykładem takiego spłaszczenia jest potraktowanie całości wyniku wyszukiwania (łącznie z nazwami danych) jako ciągu bajtów a la XML. Podobnym sposobem jest reprezentacja danych półstrukturalnych w dwóch lub więcej tabelach relacyjnych, gdzie wszystkie nazwy danych są trzymane jako stringi obok wartości, oraz istnieje specjalna tabela reprezentująca hierarchię danych.. 3.Zbudowanie nowego kompletnego środowiska programistycznego przystosowanego do przetwarzania danych półstrukturalnych. Konieczność opracowania własnego języka programowania – trudne dla zastosowań komercyjnych, ale całkowicie możliwe w ramach prototypu lub własnego projektu.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Przykład – spłaszczenie struktury XML Jan Adam Kowalski Warszawa Sienna NrCzęści NazwaCzęści 1 pracownik 2 imie 3 imie 4 nazwisko 5 data_urodz 6 adres 7 miejscowosc 8 ulica 9 nr domu 10 pensja Części pliku XML NrCzęści Wartość 2 Jan 3 Adam 4 Kowalski Warszawa 8 Sienna Wartości Nadczęść Podczęść Hierarchia
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Nieregularne dane w modelach składu AS0-AS3 Wprowadzone przez nas modele składu AS0-AS3 z definicji są przygotowane do przechowywania dowolnych struktur nieregularnych. Podobnie jak w XML, nie wprowadzamy także wartości zerowych, uważając że jeżeli wartość pewnego (pod-) obiektu jest nieznana lub nierelewantna, to taki (pod-) obiekt może być w całości pominięty w danym składzie obiektów. Jak dotąd nie wiążemy modeli składu z jakimkolwiek systemem typów. Mechanizmy wiązania na stosie środowiskowym nie zależą od tego, czy dane mają regularny format, czy są nieregularne. W istocie jednak, ta swoboda jest pozorna i wynika wyłącznie z faktu, że przy konstrukcji semantyki języka zapytań pominęliśmy schemat danych, i to zarówno od strony znaczenia danych w dziedzinie przedmiotowej (dziedzinie biznesu) oraz od strony rozumienia wszelkich aspektów logicznej organizacji i reprezentacji danych. Dobrym wzorcem w tym zakresie jest DTD oraz XMLSchema należące do technologii XML. W systemie Loqis zrealizowaliśmy nieco inny model oparty o gramatyki bezkontekstowe i wyrażenia regularne.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Konstrukcje w jęz. zapytań dla danych nieregularnych Jeżeli dana o nazwie n jest nieobecna w pewnym obiekcie z identyfikatorem i, wówczas nested(i) nie będzie zawierać bindera o nazwie n. Jeżeli mamy zapytanie o postaci q 1 θ q 2 (n) gdzie q 1 zwraca identyfikator i, θ jest operatorem nie-algebraicznym, q 2 (n), jest zapytaniem zawierającym nazwę n (nie objętą innym operatorem nie-algebraicznym), to w tej sytuacji odpowiedniego bindera nie ma na stosie, wobec czego nazwa n nie może być związana. W tej sytuacji należy przyjąć, że wiązanie nazwy n zwróci pusty zbiór. Powstaje pytanie, co z takim pustym zbiorem programista może zrobić.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Przykład z pustym zbiorem Rozważmy przykład: Prac where Zar > 1000 przy czym atrybut Zar może nie wystąpić w niektórych obiektach Prac. W tej sytuacji można przyjąć, że jeżeli Zar nie wystąpi, to predykat Zar > 1000 przyjmuje wartość fałsz. Alternatywnie można przyjąć, że jest to sytuacja błędna powodująca podniesienie wyjątku. Pierwsze założenie prowadzi do licznych niespójności (identycznych z tymi, które były przyczyną krytyki wartości zerowych), zatem je bezwzględnie odrzucamy. Pozostaje tylko druga interpretacja, czyli przyjęcie, że zapytanie to jest błędne.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Jak powinien postąpić programista? Jeżeli programista oczekuje, że atrybut Zar może nie istnieć, to powinien tę sytuację przewidzieć w swoim zapytaniu. Np. przyjmując, że count działa również na indywidualnych elementach zwracając dla nich 1, może formułować to zapytanie w sposób następujący: (Prac where count(Zar) = 1 ) where Zar > 1000 (Prac where exists(Zar) ) where Zar > 1000 Najpierw sprawdza, czy Zar istnieje, następnie upewniwszy się, że istnieje, używa po where odpowiedniego predykatu. Zwrócimy uwagę, że w tej sytuacji nie byłoby poprawne pozornie identyczne zapytanie: Prac where exists(Zar) and Zar > 1000 gdyż wymagałoby to specjalnego trybu ewaluacji operatora and (drugi człon nie podlegałby ewaluacji jeżeli pierwszy człon zwróciłby false). W językach zapytań taka interpretacja mogłaby prowadzić do błędów ze względu na optymalizację zapytań, która w wyniku metod przepisywania może przestawić kolejność predykatów.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Sposób z kwantyfikatorem Analogicznie, przyjmując, że kwantyfikatory działające na indywidualnym elemencie traktują go jako zbiór jedno-elementowy, można to samo zapytanie sformułować z pomocą kwantyfikatora: Prac where Zar as z (z > 1000) Zapytanie zwraca referencje do obiektów pracowników, którzy mają zarobek i przekracza on Prac where Zar as z (z > 1000) Zapytanie, oprócz w/w referencji, zwraca także referencje do tych obiektów Prac, w których atrybut Zar jest nieobecny (kwantyfikator ogólny działający na pustym zbiorze zawsze zwraca prawda). Jak widać, dane nieregularne w SBQL nie wymagają wprowadzania wartości zerowych ani też nowych konstrukcji gramatycznych. Istotne w naszej metodzie jest również to, że nieobecna dana (czyli odpowiednik wartości zerowej) może być dowolnie złożona, np. Adres. Taka sytuacja jest niewyrażalna w modelu i systemach relacyjnych. Są one w stanie wyrazić sytuację, że każdy atrybut w ramach danej Adres ma wartość zerową, ale semantycznie nie jest to równoważne sytuacji, w której cała dana Adres ma wartość zerową.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Nieobecne dane i funkcje agregowane Przyjęta przez nas interpretacja nieobecnych danych jest także spójna z funkcjami agregowanymi. Rozpatrzmy podany przez Ch.Date przykład z SQL, w którym jeżeli relacja R(A, B) posiada atrybuty A i B z wartościami zerowymi, to w generalnym przypadku zapytania: select sum(A+B) from R select sum(A)+sum(B) from R mogą zwrócić różny wynik. W SBQL problem ten nie występuje, ponieważ syntaktyczny odpowiednik pierwszego zapytania SQL, mający postać sum(R.(A+B)), jest błędny. Poprawny semantyczny odpowiednik pierwszego zapytania ma w SBQL postać: sum(R.(A as a, B as b).(a+b)) Jeżeli pewien obiekt R nie zawiera atrybutu A lub nie zawiera atrybutu B, to pod-zapytanie (A as a, B as b) zwróci pusty wynik, wobec czego do liczenia a+b nie dojdzie Odpowiednik drugiego zapytania ma postać: sum(R.A) + sum( R.B )
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Wartości zastępcze W sytuacji, gdy zależy nam na dobrze sformatowanych strukturach, np. dla celów raportowania lub przetwarzania wyniku zapytania przez język programowania z mocną kontrolą typu, możemy zastosować metodę wartości zastępczych. Wartość zastępcza powinna być wybrana w taki sposób, aby zastąpić ewentualny brak wartości. Jeżeli pewnej danej nie ma, to zamiast niej programista generuje w zapytaniu wybraną wartość zastępczą, której typ jest zgodny z oczekiwanym typem nieobecnej danej. W tym celu może posłużyć się dowolnymi już wprowadzonymi opcjami SBQL, np. funkcjami count, exists, kwantyfikatorami i innymi.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Przykład z wartością zastępczą Np. wynik podanego wyżej przykładu można byłoby w ten sposób uczynić regularnym poprzez napisanie nieco dłuższego zapytania: Prac.( deref(Nazwisko) as n, (if exists(Zar) then Zar else 0 ) as z, (if exists(Zajęcie) then Zajęcie else brak zajęcia) as s)) W tym przypadku podany wcześniej wynik uzyska regularną postać, nadającą się do wydrukowania w postaci tablicowego raportu: bag{ struct{ n(Kowalski), z(i Zarobek1 ),s(i Zajęcie1 ) }, struct{ n( Pawlak), z(i Zarobek2 ),s(brak zajęcia) }, struct{ n( Nowak), z( 0 ),s(brak zajęcia) }, struct{ n( Bilski), z( 0 ),s(i Zajęcie4 ) }, struct{ n( Wolski), z(i Zarobek5 ),s(brak zajęcia) } } Przy pomocy podanych konstrukcji programista może osiągnąć nie tylko wszystkie efekty, które były osiągalne poprzez zewnętrze złączenie, ale także może opanować kwestię nieregularności w danych we wszystkich innych sytuacjach.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Problem z fałszywym wiązaniem Jeżeli język zapytań nie ma kontroli typów, nieobecne obiekty w połączeniu z pełną ortogonalnością języka (nieograniczoną możliwością zagnieżdżania zapytań) prowadzą do błędu semantycznego powstającego w wyniku fałszywego wiązania. Rozpatrzmy zapytanie: Podaj pracowników zarabiających tyle samo co Nowak: Prac where Nazwisko Nowak and Zar = ((Prac where Nazwisko = Nowak).Zar Interesuje nas wiązanie drugiej występującej w tym zapytaniu nazwy Zar, w sytuacji, gdy obiekt Nowaka nie posiada takiego atrybutu. Wiązanie powinno zwrócić pusty zbiór, co spowoduje błąd wykonania. Następny slajd pokazuje sytuację na stosie środowiskowym podczas wiązania tej nazwy, jeżeli pierwszy operator where działa na obiekcie Kowalskiego, w którym atrybut Zar występuje.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Sytuacja na stosie ENVS podczas wiązania Zar Jak widać, wbrew naszym założeniom druga nazwa Zar zostanie związana, ale do zarobku Kowalskiego, a nie Nowaka. W efekcie, zapytanie nie wykaże błędu wykonania i zwróci referencje do wszystkich obiektów pracowników posiadających atrybut Zar. Wynik ten jest oczywiście błędny. Nazwisko( i Nazw1 ),... Nazwisko( i Nazw2 ), Zar( i Zar1 ),... Sekcje bazowe Kierunek przeszukiwania stosu Sekcja obiektu Nowaka (brak Zar) Sekcja obiektu Kowalskiego
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Jak uniknąć fałszywego wiązania (1)? Przyczyną jest to, że w zapytaniu i w całym mechanizmie stosu nie ma informacji o tym, do którego bindera Zar ma być wiązana występująca w zapytaniu druga nazwa Zar. Uniknięcie tego błędu zmusza do jednego z następujących rozwiązań: 1.Zabronienie zagnieżdżania zapytań - sprzeczne z koncepcją. 2.Ustalenie bardziej rygorystycznej reguły wiązania, np. po operatorze kropki wiązanie następuje wyłącznie na wierzchołku stosu. Część zapytań stanie się niewyrażalna. Podobny błąd wystąpi zresztą dla innego operatora niealgebraicznego. 3.Zmuszanie programistów do świadomego formułowania tego zapytania tak, aby ten błąd nie mógł wystąpić. Np. inne (poprawne w każdym przypadku) sformułowanie tego zapytania jest następujące: (Prac where Nazwisko = Nowak).(Zar as x). (Prac where Nazw Nowak and Zar as y (x = y)) Powyższe założenie jest nieakceptowalne ze względów ergonomicznych, gdyż zmusza programistów do zbyt drobiazgowej analizy formalnej semantyki każdego zapytania.
Języki i środowiska programowania systemów rozproszonych, Wykład 09, Slajd Jak uniknąć fałszywego wiązania (2)? 4.Określenie typów dla wszystkich obiektów i następnie, połączenie mechanizmu wiązania z przechowywaną informacją o typie. Dzięki temu można będzie wnioskować, że wprawdzie w danym obiekcie Zar nie występuje, ale w typie tego obiektu jest to opcyjne, zatem będziemy jednak wiązać Zar w tym obiekcie, co oczywiście da poprawny wynik w postaci pustego zbioru. 5.Skorzystanie z rozwiązania opartego na wartościach domyślnych przechowywanych w ramach klas. Tylko dwa ostatnie rozwiązania są akceptowalne. Radykalnym rozwiązaniem jest zastosowanie typów obiektów. Np. można zmodyfikować funkcję nested w taki sposób, aby dla danej referencji i sprawdzała dodatkowo typ obiektu i. Jeżeli ten typ przewidywałby opcyjną daną o nazwie n, której w danym obiekcie nie ma, funkcja zwracała dodatkowo binder n( ). W ten sposób nieobecny pod-obiekt o nazwie n byłby reprezentowany na stosie środowiskowym przez binder n( ).