Professional Documents
Culture Documents
Danych
gich (ang. long running transactions). Sama architektura po- Najwyższa warstwa obsługuje transakcje aplikacji. LhimkDB
winna być otwarta, czyli powinna dać się rozszerzać i wbudo- jest zbudowany korzystając z algorytmów nieblokujących, nie wy-
wywać np. nowe typy indeksów. Ważne jest też, żeby baza stępuje w niej zjawisko zakleszczenia (ang. deadlock). Jeżeli wy-
danych szybko odtwarzała swój stan po awarii i pozwalała ro- stąpi konflikt wśród transakcji, to jedna z nich jest unieważniana.
bić gorące kopie (ang. hot backups).Jest to krytyczne w serwi- Jeżeli jakaś transakcja może być wciąż unieważniana powsta-
sach internetowych, gdzie system musi działać cały czas i nie je tzw. livelock. Zapobiega się temu np. gwarantując, że transak-
możemy wykonywać czynności administracyjnych, wymagają- cja, która zaczęła wcześniej na pewno skończy z powodzeniem.
cych odłączenia bazy danych choćby na kilka minut. Przyjrzyjmy się teraz po kolei tym wszystkim warstwom.
�����
�� �� Atomowy odczyt i zapis
� ��
Jeżeli kilka wątków zapisuje i odczytuje tę samą pamięć, to trze-
������ ba zapewnić, żeby operacje takie były atomowe. Najprostszą i naj-
częściej stosowaną metodą jest ustawianie loków, które z kolei ko-
rzystają z muteksów (semaforów) i liczników. Ostatnio pojawiły się
������� w procesorach tzw. operacje CAS (Compare and Swap). Nie wcho-
dząc w szczegóły, pozwalają one na atomowy odczyt i zapis frag-
mentów pamięci. Coraz częściej pojawiają się artykuły, biblioteki
i eksperymentalne programy korzystające z tych operacji. Wszyst-
kie przykłady pokazują, że aplikacje budowane przy pomocy ope-
� �� racji atomowych działają szybciej niż aplikacje blokujące z lokami,
szczególnie w systemach wieloprocesorowych. LhimkDB jest przy-
Rysunek 2. Sposób zapisu danych w LhimkDB.
gotowany na pracę przy pomocy operacji CAS.
ga stronicowanie, że system decyduje co zrzucić na dysk, a co
trzymać w pamięci (istnieją polecenia systemowe mlock, mun-
lock, którymi możemy zabraniać i pozwalać systemowi zapisy- tym, że przed każdą operacją zapisujemy do osobnego pliku (lo-
wać strony na dysk, ale wtedy cała obsługa stronicowania spada gu), dane, które pozwolą nam tę operację cofnąć (tzw. undo log).
na nas). Musimy zatem tak zorganizować dane, żeby w każdej Jeżeli chcemy robić to na poziomie pliku, to możemy np. przed
chwili każda ich wersja była poprawna i dało się odtworzyć stan zmianą danych zapisać stare dane (wraz z adresem i długo-
do poprzedniej poprawnie zakończonej transakcji. ścią) do logu. W ten sposób stworzymy znany mechanizm pliku
Najczęściej bazy danych korzystają z mechanizmu Write z dziennikiem (ang. journaled file). Problem z tym rozwiązaniem
Ahead Log (WAL), czyli najpierw zapisz do logu. Polega on na jest taki, że po każdej operacji na pliku, musimy mieć gwarancję,
że plik z logiem jest fizycznie zapisany (czyli wykonać jakiś sync
na pliku z logiem). Oznacza to, że będzie tyle operacji synchroni-
�����������������������������������������������������
zacji ile zmian w bazie, a to może znacznie spowolnić działanie
�����
bazy. Drugi problem polega na umożliwieniu dostępu do takie-
�� ��
� ��
go pliku przez wiele wątków (lub procesów) jednocześnie. Zapi-
sy trzeba oczywiście wykonywać w sposób atomowy i nie mogą
������ interferować z odczytami. To można łatwo osiągnąć przy pomo-
cy sekcji krytycznych lub operacji CAS (więcej o operacjach CAS
w ramce obok). Ale nie chcemy, żeby transakcja mogła odczyty-
������� wać dane, które są zapisane przez inną transakcję, która jeszcze
nie skończyła (nie było commit). Bazy danych stosują w tym celu
kolejne mechanizmy loków (ang. locks). Takie loki są zakładane
najczęściej na całe strony. Jeżeli jeden wątek odczyta jakąś stro-
� �� nę, to np. żaden inny wątek nie będzie mógł tej strony zmieniać.
Jeżeli transakcja obejmuje wiele stron, to może z łatwością dojść
������������������������������������������������� do zakleszczenia (ang. deadlock), czyli sytuacji, kiedy np. wątek
������������������������������������������������������������������ A czeka aż wątek B zwolni stronę 1, a wątek B czeka aż wątek
����� A zwolni stronę 2. W takich bazach danych muszą być wprowa-
��� �� dzone specjalne mechanizmy wykrywające zakleszczenia, któ-
�� �� re reagują najczęściej zabijając przynajmniej jeden wątek (pro-
gramiści często o tym zapominają, a jest to źródło wielu weso-
������ łych nieporozumień z klientami). Takie czekanie na zasoby bar-
dzo spowalnia działanie aplikacji wielowątkowych (czyli np. ser-
werów WWW). Pojawiły się ostatnio rozwiązania oparte na algo-
������� rytmach nieużywających loków (ang. lockless lub lockfree). Naj-
częściej oparte są one na operacjach CAS i strukturach danych
z cieniem (ang. shadow data).
LhimkDB organizuje dane w pliku podobnie jak to się dzie-
�� �� je w strukturach danych z cieniem. Każda dana ma swój PID,
który jest adresem w pliku, który trzyma wszystkie PID (plik ten
Rysunek 3. Sposób obsługi wielodostępu do danych. Transakcja w LhimkDB nazywany jest indeksem, nie mylić z indeksem
A zaczęła się wcześniej i w momencie jej startu ostatnią w sensie bazy danych). Przy każdym PID jest pamiętany adres
zakończoną transakcją zapisu była transakcja
danych w pliku z danymi (w LhimkDB nazywany plikiem z rekor-
o numerze 100. Transakcja B zaczęła się później, poprosiła
o prawa do zapisu i dostała numer 101. Transakcja B zmienia dami, znowu, nie mylić z rekordami w relacyjnej bazie danych).
obiekt o PID 1, i wprowadza jego nową wartość pod aresem 30. PID jest zatem odpowiednikiem adresu dla obiektu. Obiekt mo-
Jednak wciąż jest pamiętana stara wartość, i kiedy transakcja A żemy dowolnie przemieszczać w pliku rekordów, wystarczy wte-
chce odczytać wartość obiektu PID(1), to dostaje stary adres 15. dy wprowadzić jego nowy adres przy PIDw pliku indeksu.
Listing 1. Fragment implementacji Skip List z artykułu Thomassa Niemanna “A compact guide to searching and sorting”
(http://epaperpress.com/sortsearch/index.html)
statusEnum findLeft(SkipList *This, keyType key, x->level = newLevel;
nodeType** node){ x->prev = This->update[0];
int i; if (x->prev == This->hdr){
nodeType *x = This->hdr; x->prev = NIL;
*node = NIL; }
for (i = This->listLevel; i >= 0; i--){ printf("update prev, x->prev %d:%d \n",
while (x->forward[i] != NIL x->prev->key, x->prev->rec.stuff);
&& compLT(x->forward[i]->key, key)){ for (i = 0; i <= newLevel; i++){
x = x->forward[i]; x->forward[i] = This->update[i]->forward[i];
} This->update[i]->forward[i] = x;
This->update[i] = x; }
} if (x->forward[0] != NIL){
*node = x; printf("update forward[0] prev, x->forward[0] %d:%d \n",
return STATUS_OK; x->forward[0]->key, x->forward[0]->rec.stuff);
} x->forward[0]->prev = x;
statusEnum insert(SkipList *This, keyType key, }
recType *rec){ return STATUS_OK;
int i, newLevel; }
nodeType *x; statusEnum delete(SkipList *This, keyType key){
findLeft(This,key, &x); int i;
x = x->forward[0]; nodeType *x;
if (x != NIL && x != This->hdr && compEQ(x->key, key)) findLeft(This,key, &x);
return STATUS_DUPLICATE_KEY; x = x->forward[0];
for ( if (x == NIL || !compEQ(x->key, key))
newLevel = 0; return STATUS_KEY_NOT_FOUND;
rand() < RAND_MAX/2 && newLevel < MAXLEVEL; for (i = 0; i <= This->listLevel; i++){
newLevel++); if (This->update[i]->forward[i] != x) break;
printf("new level: %d \n", newLevel); This->update[i]->forward[i] = x->forward[i];
if (newLevel > This->listLevel){ }
This->listLevel = newLevel; free (x);
} while ((This->listLevel > 0)
if ((x = malloc(sizeof(nodeType) && (This->hdr->forward[This->listLevel] == NIL))
+ newLevel*sizeof(nodeType *))) == 0) This->listLevel--;
return STATUS_MEM_EXHAUSTED; return STATUS_OK;
x->key = key; }
x->rec = *rec;
W LhimkDB pamiętamy jednak nie jeden adres, a dwa. ły commitować swoje zmiany po kolei. Trwa to zazwyczaj bar-
Dla każdego z dwóch adresów pamiętamy też numer transak- dzo krótko (na moim notebooku około 1/300 s) i nie stanowi
cji, która wprowadzała te dane (patrz Rysunek 2.). Wyobraź- zasadniczego ograniczenia.
my sobie, że mamy dwie transakcje A i B. Transakcja A zaczę- Drugi problem jest taki, że jeżeli od startu transakcji A by-
ła się wcześniej i w momencie jej startu ostatnią zakończoną ły dwie transakcje B1 i B2, które zmieniały obiekt PID(1), to już
transakcją zapisu była transakcja o numerze 100. Transakcja nie pamiętamy wersji o numerze 100, którą chciałaby odczytać
B zaczęła się później, poprosiła o prawa do zapisu i dosta- transakcja A. W takim wypadku występuje konflikt i transakcja
ła numer 101. Transakcja B zmienia obiekt o PID(1), i wpro- A jest unieważniana. Moje “badania” empiryczne pokazały, że nie
wadza jego nową wartość pod adresem 30 (adres w pliku re- jest to przypadek częsty. Przy dziesięciu równolegle zapisujących
kordów). Jednak wciąż jest pamiętana stara wartość, i kiedy wątkach, walczących o 3 te same obiekty, występuje to zjawisko
transakcja A chce odczytać wartość obiektu PID(1), to dostaje w około 1% transakcji. Gdyby był to jednak znaczący problem,
stary adres 15 (patrz Rysunek 3.). W ten sposób zapisy trans- zawsze można zapamiętywać więcej historycznych wartości.
akcji B nie będą w żaden sposób interferować z transakcją A. Wiemy już jak obiekty są zapisywane, i jak są im przypo-
Pojawia się ograniczenie, że w jednym czasie może być rządkowywane PID. Zastanówmy się nad zarządzaniem pamię-
tylko jedna transakcja z prawami zapisu. Nie jest to dla nas cią. Obiekty są tworzone, kasowane, powstają nowe wersje itp.
problem, ponieważ mówimy tu o transakcjach niskopozio- W związku z tym muszą być mechanizmy, które zarządzają alo-
mowych, aplikacja będzie się posługiwać zupełnie innymi kacją i zwalnianiem pamięci. Jest to dosyć ciekawa dziedzina,
transakcjami, które będą mogły dowolnie równolegle dzia- w przypadku alokacji pamięci, która będzie zapisywana na dysk
łać. Oznacza to tylko tyle, że transakcje aplikacji będą musia- i odtwarzana z dysku, wcale nie ma tak dużo gotowych wzorco-
sywać dane (baza docelowa) i własne SkipDB używane wy- Dlaczego takie środowisko jest ważne? Nie wystarczy
łącznie jako cache (baza podręczna). Transakcje współdzielą nam PHP+MySQL?
między sobą wyłącznie obiekt pliku (część UDB), więc wspól- Każdy, kto budował większe aplikacje przy użyciu serwera
ny dostęp musi być kontrolowany wyłącznie do danych pliku. SQL, wie, że w zasadzie serwer SQL architektonicznie się do te-
Transakcja LhimkDB działa tak: kiedy czyta rekord, naj- go nie nadaje. Można tego “naukowo” dowieźć. Weźmy dowolną
pierw czyta z bazy podręcznej. Jeżeli tam go nie ma, to spraw- operację na danych w takiej aplikacji (np. dekretację dokumen-
dza w bazie docelowej. Jeżeli chce zmienić rekord, to wprowa- tu). Jeżeli jest ona wykonywana raz, to może być zrealizowa-
dza te zmiany tylko do bazy podręcznej. Dopiero po sprawdze- na przy pomocy sekwencji: odczytaj z SQL, przetwórz w aplika-
niu wszelkich ewentualnych konfliktów, kiedy jesteśmy pewni, że cji, zapisz do SQL. Ale jeśli będziemy ją chcieli wywołać np. dla
transakcja zakończyła się sukcesem, następuje przepisywanie 1000 elementów, to okaże się, że przetwórz w aplikacji musimy
zmian z bazy podręcznej do docelowej. Czyli w jednym czasie, zapisać w postaci procedury SQL. W ten sposób cały nasz kod
tylko jedna transakcja może wykonywać commit. Transakcja jest z czasem wyemigruje do SQL, a w “aplikacji” zostanie tylko po-
zatem materializowana w postaci zapisów do bazy podręcznej. kazywanie danych. Jest to szczególnie drastycznie widoczne
Jeżeli będziemy tę bazę zapisywać (domyślnie jest tylko w pa- przy aplikacjach webowych, gdzie nic się klientowi nie pokazu-
mięci), to mamy transakcje długie (trwałe), które możemy nawet je, tylko wysyła HTML. Stąd pomysły, żeby np. do serwera SQL
nazywać. Jeżeli będziemy z kolei tworzyć hierarchię takich baz dobudować serwer HTTP. Te pomysły zostały zarzucone, po-
podręcznych, uzyskamy transakcje zagnieżdżone. nieważ architektury serwerów SQL zakładają stałe połączenie
Trzeba pamiętać, że transakcja może zostać unieważnio- z klientem, a HTTP jest protokołem bezstanowym. Rozwiąza-
na. Dlatego, jeżeli chcemy mieć gwarancję, że transakcja się niem może być zatem wbudowanie bazy danych do serwera
wykona, trzeba ją wykonywać w pętli (Listing 2). aplikacyjnego i serwera HTTP. I takie rozwiązanie jest w Lhimku.
Danych
M
iesiąc temu rozpocząłem cykl artykułów
opisujący jak zbudować od podstaw bazę ������� ������������������
�����
wać w specjalnych typach, które zajmują kilka stron). Ge-
�� �� neralnie, im większy jest rozmiar strony tym baza danych
� �� działa szybciej i można zapisywać większe dane, ale ma-
my mniejszą współbieżność.
������ Ale to jest prawda w tradycyjnych bazach danych, u nas
strony są podobne tylko z nazwy. W MFile strona nie stano-
wi jednostki blokowania dostępu, ani ograniczenia w wiel-
������� kości danych. Wielkość stron jest też dużo większa (wydaje
mi się, że optymalna wielkość strony to 4MB) i jest ich dużo
mniej. Zasada działania jest taka, że jeżeli chcemy coś zro-
bić pod adresem np. 12345, to prosimy klasę PageSystem,
� �� aby dała nam stronę dla tego adresu. Wszystkie strony
trzymane są w tablicy, a ich indeks to adres/rozmiar _ stro-
Rysunek 2. Sposób zapisu danych w UDB
ny. Jeżeli strona już jest utworzona, to jest po prostu zwra-
łość (ang. persistance). W LhimkDB pliki są obsługiwane cana. Jeżeli nie, to jest tworzona, mapowana na pamięć
przez klasę MFile – Memory Mapped File. i zwracana. Jeżeli przekroczyliśmy limit ilości stron, to
Idea jest taka: ostatnio najmniej używana strona (ang. Least Recently
Used) jest zwalniana ( munmap).
• Otwieramy plik (lub tworzymy nowy) Jeżeli rozmiar danych przekracza wielkość strony to za-
• Mapujemy plik na pamięć -- wywołanie mmap (wszystkie pis i odczyt są wykonywane w pętli aż cały bufor zostanie za-
funkcje systemowe podaję dla Linuksa) pisany lub odczytany (Listing 2). W rezultacie możemy skon-
• Odczyty i zapisy wykonujemy przy pomocy memcpy figurować MFile, żeby miał jedną małą stronę (np. 4096) i ob-
• Wszystkie operacje zapisu i odczytu są chronione przez
mutex
�����������������������������������������������������
• Tylko jeden wątek może mieć w jednej chwili prawa do zapisu
�����
sługiwać zapis i odczyt 100MB z 10GB plików. Tyle, że bę- UDBIndex zapamiętuje na początku swojego pliku na-
dzie to długo trwało. Rozmiary i ilość stron są limitowane tak główek, w którym zapisuje numer ostatnio zakomitowa-
naprawdę tym, ile możemy zająć z przestrzeni adresowej (nie nej transakcji (last _ commited) i numer aktualnie działają-
z pamięci!). Jeżeli np. jeden obiekt MFile zabierze 100MB cej transakcji (current). W momencie startu transakcji zapi-
z przestrzeni adresowej, to będziemy mogli takich plików su (begin()) jest zwiększany o jeden numer aktualnej trans-
otworzyć za jednym razem około 10-ciu. Jeżeli nie planuje- akcji i nagłówek jest zapisywany (jest wykonywana trans-
my otwierania dużej ilości plików, nasza aplikacja jest np. de- akcja pliku MFile). Jeżeli przy otwieraniu pliku okaże się,
dykowanym serwerem, to warto zająć nawet 1GB przestrze-
ni adresowej. Oczywiście problem znika (przynajmniej teo-
retycznie) na maszynach 64-bitowych. Na takich maszynach Lhimk
cały ten system stronicowania ma niewielki sens, lepiej pod-
Lhimk jest środowiskiem dynamicznej kompilacji dla języka
piąć cały plik i niech dalej się martwi system operacyjny.
o tej samej nazwie (Lhimk), który jest oparty na C. Lhimk jest ję-
Mamy zatem metodę trwałego zapisu dowolnie dużych
zykiem obiektowym, o bardzo prostej składni, w zasadzie, każ-
danych w dowolnie dużych plikach. W MFile nie ma poję- dy kto zna C/C++ może od razu zacząć programować w Lhim-
cia transakcji, commit oznacza tylko tyle, że będzie zsynchro- ku. Do składni C są dodane klasy z wielodziedziczeniem (ang.
nizowana zawartość pamięci i pliku, czyli wszystko zostanie multiinheritance) i możliwością przeciążania operatorów, szablo-
na pewno na dysk zapisane. Musimy zatem dodać funkcjonal- ny, sygnały, wyjątki i kilka innych drobiazgów. Lhimk jest środo-
ność, która obsłuży transakcje. wiskiem dynamicznej kompilacji, więc cały kod jest kompilowa-
ny w momencie uruchamiania programu. Kompilator jest bardzo
UDBIndex szybki, więc trwa to krócej niż ładowanie wielu bibliotek w skom-
Przechodzimy do właściwej klasy UDB. Tak naprawdę to skła- pilowanych programach.
da się ona z 4 klas: UDBIndex, UDBRecord, UDBRecords Lhimk jest dostępny częściowo na licencji LGPL i częścio-
wo na licencji BSD. Wynika to z tego, że korzysta z kodu TinyCC,
i UDB. Koncepcja jest taka, że będziemy wykorzystywać dwa
który jest na licencji LGPL, natomiast moją intencją jest dawać
pliki -- Index i Records. Zajmiemy się teraz klasą UDBIndex,
wszystko na licencji BSD.
która implementuje Index. Podstawową jednostką kodu w Lhimku jest moduł. Każdy mo-
Index jest czymś w rodzaju tablicy wskaźników. Jeże- duł ma unikalny adres, np.:
li chcemy odczytać dane (rekord) zapisane pod pid=10, to
sięgamy do 10-tej komórki w pliku Index (mówiąc ściśle, to \lhimk.org\Db\UDB
Listing 2. Pętla zapisująca dane dowolnej wielkości, Listing 4. Przykłady użycia UDB
niezależnie od wielkości strony.
@\ludb\UDB *udb = \ludb\UDB::new("TESTUDB", 1);
while(size > 0)
{ unsigned long long pid1, pid2, pid3;
Page* p = this->_mfile_p->
page_system->getPageForPos(pos); udb->beginTransaction();
pid1 = udb->putBuf("aaa",4);
unsigned int n_pos = pos - p->off; pid2 = udb->putBuf("bbb",4);
unsigned int wsize = ( (p->size()-n_pos)> pid3 = udb->putBuf("ccc",4);
size ? size : (p->size()-n_pos)); udb->commitTransaction();
Przykłady użycia
Na Listingu 4. pokazany jest prosty kod korzystający
z UDB. Nie ma tu nic, co mogłoby kogoś zaskoczyć (mo-
że poza charakterystycznymi dla Lhimka znakami '\' przed
printf). Obiekt UDB jest tworzony, transakcja się zaczyna,
zapisujemy dane przy pomocy putBuf, odczytujemy przy
pomocy at.
W przypadku aplikacji wielowątkowych, trzeba bazowy
obiekt UDB przekazać do funkcji wątku i zamiast tworzyć no-
wy obiekt UDB klonujemy obiekt bazowy:
Podsumowanie
Pokazałem jak od podstaw stworzyć warstwę dostępu do
danych – UDB. Udało się spełnić wszystkie zakładane za-
łożenia, włącznie z obsługą transakcji, wielowątkowości
oraz danych i plików dowolnej wielkości. Pokazane UDB
jest dość nietypowe, ale sprawdza się w praktyce. Jest bar-
dzo proste, przez co powinno mieć mało błędów. Z pobież-
nych testów wynika, że LhimkDB-UDB działa bardzo wydaj-
nie, nawet przy bardzo dużych plikach (rzędu 10GB). Moż-
na je też dowolnie konfigurować do potrzeb swoich aplika-
cji. Takie krytyczne parametry jak wielkość strony czy wiel-
kość, o którą zwiększamy plik (ang. grow step), mogą być
zmieniane dla istniejących plików, czego raczej nie spotyka
się w tradycyjnych bazach danych. Bardzo szybkie jest też
odtwarzanie po awarii, co jest szczególnie ważne w aplika-
cjach WWW.
Za miesiąc dodamy kolejną warstwę, która będzie odpo-
wiadać za dostęp do danych według klucza. n
danych
D
wa miesiące temu rozpocząłem cykl artyku-
łów opisujący jak zbudować od podstaw ba- ������� ������������������
�� �� �� �� �� �� �� �� � � � �
v[[10]] = 20 ;
�� �� �� �� �� �� �� ��
wywoła
���������������������������������������������������������������
operator_bba(10,20)
�������������������������
pListy, łącząc je bardzo prosto z kodem UDB. Zanim pokażę W ten sposób moduł \lhimk.org\LTL\List\ zostanie przekom-
pilowany z typem T zdefiniowanym jako int. Jest to podejście dużo
jak to zrobić, musimy mieć jeszcze jeden mechanizm: trwały
prostsze niż w C++, a czasami daje dużo lepsze możliwości (można
wskaźnik (ang. persistent pointer).
np. w zależności od typu T dodawać do klas poszczególne metody).
Sygnały są odpowiednikami tablicy wskaźników do funkcji. Sygnał
Persistent Pointer (PPointer) deklarujemy tak: signal changed(A*); Potem możemy dodać meto-
Zostawmy na chwilę Skip Listę i zastanówmy się jak zintegro- dę, która będzie wywoływana: this->changed << a->handle; //bez ()!
wać z naszą bazą danych UDB, najprostszą listę, taką jak L0 Jeżeli gdzieś w kodzie jest: this->changed->call(a); to zostanie wy-
z powyższego opisu (tyle, że nieposortowaną). wołanie: a->handle(); To chyba cały podręcznik Lhimka...
Klasa Lista może mieć takie pola:
Lista* next; Oznacza to, że next będzie trwałym wskaźnikiem, a nie zwy-
int val; kłym wskaźnikiem. Używamy go jak tablicy, żeby móc prze-
int key; ciążyć operator przypisania (patrz wyżej): l->next[[0]] = l2;
To w rezultacie wywoła: PPointer::operator_bba(0, l2);
Wiadomo o co chodzi, będziemy szukać elementów z odpo- Nałóżmy na obiekt klasy Lista jeszcze obowiązek pamię-
wiednim key i zwracać ich val. Używać chcemy tej struktury tania swojego PID (metody getPid i setPid).
w najprostszy sposób: Wewnątrz operator _ bba klasy PPointer, postępujemy tak
(pełny kod znajduje się na Listingu 1.):
Lista *l = Lista::new(key, val);
Lista *l2 = Lista::new(key2, val2); l Zapamiętaj wskaźnik do obiektu, jeżeli obiekt nie był jesz-
l->next = l2; cze zapisany do bazy danych (ma getPid()==0), to zapisz
go do bazy danych.
W momencie, kiedy robimy l->next=l2, chcielibyśmy, żeby l2
zostało “zapisane do bazy danych”. W tym celu zrobimy drob- Podobnie działa operator_bb (odczyt):
ną modyfikację. Wskaźnik next zastąpimy taką strukturą:
l Jeżeli masz wskaźnik do obiektu, to go zwróć (znaczy, że już
PPointer[Lista T] *next; go czytaliśmy), jeżeli nie, to weź pid i odczytaj obiekt z bazy.
danych
T
rzy miesiące temu rozpocząłem cykl artyku- tor desrialize można napisać “ręcznie”, można też
łów opisujący jak zbudować od podstaw ba- wykorzystywać informacje o klasach z RTTI, do auto-
zę danych. Zbudowaliśmy bazę danych – matycznego generowania takiego kodu.
LhimkDB – do wbudowywania do programów (ang. Klasa Datum spełnia w Lhimku jeszcze jedną
embedded database), o architekturze klucz-wartość funkcję. Otóż, umożliwia ona programowanie gene-
(ang. key-value). Teraz dodamy do niej możliwość ryczne (uogólnione). Zobaczmy konkretny przypa-
zapisu danych w strukturze drzewa (ang. tree struc- dek. W miejscu, gdzie chcemy zapisać dane do bazy
tured database). Wszystkie przedstawione do tej po- danych, musimy zrobić coś takiego:
ry warstwy LhimkDB mogą być używane niezależ-
nie. Tak będzie i tym razem, dodanie struktury drze- key->serialize(stream);
wa nie oznacza, że nie można z LhimkDB korzystać value->serialize(stream);
jak z bazy klucz-wartość.
LhimkDB jest pisany w języku Lhimk. Język ten Przy czym, nie znamy typów key i value. Gdybyśmy
jest na tyle podobny do C/C++, że nie ma sensu się założyli, że są to zawsze obiekty, to sprawa by była
go uczyć, czy wyjaśniać jak w nim pisać programy. rozwiązana, wystarczy zażądać implementacji tych
Te elementy, które występują w Lhimku, a nie ma ich dwóch metod. Ale tak nie jest, bo mogą to być rów-
w C/C++ są omówione w ramce “Lhimk” lub w samym nież typy proste. W C++ rozwiązuje się to poprzez
tekście artykułu. Jeżeli ktoś nie chce używać Lhimka specjalizację metod. W C++ napisalibyśmy to tak:
(co całkowicie rozumiem), to może z łatwością pokaza-
ny kod przepisać w C++. Można też z Lhimka korzystać stream->serialize(key);
z wewnątrz programów napisanych w C/C++. stream->serialize(value);
Lhimk jest środowiskiem dynamicznej kompilacji dla języka o tej sa- ��� �������� ��������
mej nazwie (Lhimk), który jest oparty na C. Lhimk jest językiem obiek-
��� ����� �����������������������
towym, o bardzo prostej składni, w zasadzie, każdy kto zna C/C++
może od razu zacząć programować w Lhimku. Do składni C są doda- ��� ������� �������
ne klasy z wielodziedziczeniem (ang. multiinheritance) i możliwością
przeciążania operatorów, szablony, sygnały, wyjątki i kilka innych dro- ��� �����������
biazgów. Lhimk jest środowiskiem dynamicznej kompilacji, więc cały
��� ����� ������
kod jest kompilowany w momencie uruchamiania programu. Kompi-
lator jest bardzo szybki, więc trwa to krócej niż ładowanie wielu biblio- ��� ������ ��������
tek w skompilowanych programach. Lhimk jest dostępny częściowo
na licencji LGPL i częściowo na licencji BSD. Wynika to z tego, że ��� ���������� ��������
korzysta z kodu TinyCC, który jest na licencji LGPL, natomiast mo-
��� ��� ������
ją intencją jest dawać wszystko na licencji BSD. Podstawową jed-
nostką kodu w Lhimku jest moduł. Każdy moduł ma unikalny adres,
Rysunek 1. Budowa drzewa przy pomocy typu Datum i list
np.: \lhimk.org\Db\UDB W Lhimku nie ma podziału na pliki nagłów-
kowe i implementację. Jak chcemy się odwołać do jakiegoś typu Wystarczy, że metoda serialize będzie zaimplementowana
lub obiektu z innego modułu, po prostu podajemy całą ścieżkę. To w klasie Datum. Możemy teraz używać bazy danych do prze-
jest np. odwołanie do klasy UDB (odwołania do typów poprzedzo- chowywania dowolnych wartości:
ne są znakiem '@'): @\lhimk.org\Db\UDB\UDB Na końcu jest dwa ra-
zy UDB, ponieważ klasa UDB jest w module UDB. Obiekty globalne
@\lds\SkipList[Datum* T][Datum* K]* list =
(czyli wbudowane funkcje) są po prostu poprzedzane przez '\', np.:
\lds\SkipList[Datum* T][Datum* K]::new("SKIPDB", 1);
\printf(“Hello!\n”); Klasa może mieć metody wirtualne, albo sta-
tyczne. W Lhimku raczej nie używa się referencji do obiektów, a wy- list->beginTransaction();