170 likes | 281 Views
Języki i środowiska programowania systemów rozproszonych. Wykład 9 Przetwarzanie struktur nieregularnych. Wykładowca : Tomasz Kowalski Wykłady przygotowane na podstawie materiałów prof. Kazimierza Subiety. Dane nieregularne (pół-strukturalne).
E N D
Języki i środowiska programowania systemów rozproszonych Wykład 9 Przetwarzanie struktur nieregularnych Wykładowca: Tomasz Kowalski Wykłady przygotowane na podstawie materiałów prof. Kazimierza Subiety
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.
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.
Trzy sposoby przetwarzania nieregularności • 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. • „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.. • 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.
Przykład – „spłaszczenie” struktury XML <pracownik> <imie>Jan</imie> <imie>Adam</imie> <nazwisko>Kowalski</nazwisko> <data_urodz>1973-12-1</data_urodz> <adres> <miejscowosc> Warszawa </miejscowosc> <ulica> Sienna </ulica> <nr domu> 67 </nr domu> </adres> <pensja>2500</pensja> </pracownik> Części pliku XML Wartości Hierarchia 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” NrCzęści Wartość 2 ”Jan” 3 ”Adam” 4 ”Kowalski” 5 ”1973-12-1” 7 ”Warszawa” 8 ”Sienna” 9 ”67” 10 ”2500” Nadczęść Podczęść 1 2 1 3 1 4 1 5 1 6 1 10 6 7 6 8 6 9
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.
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 q1 θ q2(n) gdzie q1zwraca identyfikator i, θ jest operatorem nie-algebraicznym, q2(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ć.
Przykład z pustym zbiorem • Rozważmy przykład: PracwhereZar > 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.
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: (Pracwherecount(Zar) = 1 ) whereZar > 1000 (Pracwhereexists(Zar) ) whereZar > 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: Pracwhereexists(Zar) andZar > 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.
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: PracwhereZarasz (z > 1000) • Zapytanie zwraca referencje do obiektów pracowników, którzy mają zarobek i przekracza on 1000. PracwhereZarasz (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ą.
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: selectsum(A+B) fromR selectsum(A)+sum(B) fromR 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.(Aasa, Basb).(a+b)) Jeżeli pewien obiekt R nie zawiera atrybutu A lub nie zawiera atrybutu B, to pod-zapytanie (Aasa, Basb) zwróci pusty wynik, wobec czego do liczenia a+b nie dojdzie • Odpowiednik drugiego zapytania ma postać: sum(R.A) + sum( R.B )
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.
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) asn, (ifexists(Zar) thenZarelse 0 ) asz, (ifexists(Zajęcie) thenZajęcieelse ”brak zajęcia”) ass)) • W tym przypadku podany wcześniej wynik uzyska regularną postać, nadającą się do wydrukowania w postaci tablicowego raportu: bag{ struct{ n(”Kowalski”), z(iZarobek1), s(iZajęcie1) }, struct{ n( „Pawlak”), z(iZarobek2), s(”brak zajęcia”) }, struct{ n( „Nowak”), z( 0 ), s(”brak zajęcia”) }, struct{ n( „Bilski”), z( 0 ), s(iZajęcie4) }, struct{ n( „Wolski”), z(iZarobek5), 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.
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: PracwhereNazwisko ”Nowak” andZar = ((PracwhereNazwisko = ”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.
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. Kierunek przeszukiwania stosu Sekcja obiektu Nowaka (brak Zar) Sekcja obiektu Kowalskiego Nazwisko( iNazw1),... Nazwisko( iNazw2), Zar( iZar1 ),... Sekcje bazowe
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ń: • Zabronienie zagnieżdżania zapytań - sprzeczne z koncepcją. • 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. • 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: (PracwhereNazwisko = ”Nowak”).(Zarasx). (PracwhereNazw ”Nowak” andZar asy (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.
Jak uniknąć fałszywego wiązania (2)? • 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. • 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().