Objektová databáze pro C++/CX

19. 10. 2011 11:05 (aktualizováno) zboj

Při použití databáze v klientské aplikace máme zpravidla na výběr mezi relační databází (s přístupem k datům přes SQL) a objektovou databází. Objektové databáze jsou nerelační, což ovšem pro použití v běžné aplikaci naprosto stačí, často je dokonce použití takové databáze pro vývojáře jednodušší, protože se nemusí starat o „rozklad“ objektů v Javě, C# nebo nějakém jiném jazyce do tabulek v databázi. Objektové databáze, mají-li být snadno použitelné, vyžadují od jazyka značnou míru introspekce, aby se k datům dostaly a mohly je náležitě uložit.

Zajímavým počinem pro C++/CX (WinRT) je objektová databáze MetroBase. Protože RTTI je pro implementaci objektové databáze nedostatečné a WinRT sice obsahuje metadata pro třídy (v souboru .winmd generovaném překladačem), ale ta nejsou (alespoň v současné preview verzi) v C++ dostupná, spoléhá se celá knihovna preprocesor. Jednotlivé výhody použití MetroBase si postupně ukážeme.

Deklarace persistentních tříd

Třídy, které chceme ukládat do databáze, deklarujeme pomocí několika jednoduchých maker, například:

MCLASS(PAddress); MSTRING(PAddress, Street); MSTRING(PAddress, City); MSTRING(PAddress, State); MSTRING(PAddress, Country); MENDCLASS(PAddress);

Tento kód vytvoří třídu PAddress (prefix P je použit kvůli podivnosti v překladači, kdy názvy tříd kolidují s názvy vlastností) s vlastnostmi (properties) Street, City, State a Country. Při uložení instance této třídy do databáze si můžete představit tabulku PAddress se čtyřmi sloupci odpovídajícími vlastnostem třídy (ve skutečnosti se ukládá i primární klíč, ten je ovšem generován automaticky).

Výhodou objektových databází je možnost vytvářet a ukládat celé hierarchie tříd, můžeme mít tedy třídu PPerson, v ní vlastnost Address a pokud je p instance PPerson, mohli bychom přímo v kódu použít například výraz p->Address->City.

Vytváření instancí persistentních tříd

Vytvoření instance třídy je jednoduché. Použijeme ref new a naplníme vlastnosti:

auto address = ref new PAddress; address->Street = "Street 5678"; address->City = "New York"; address->State = "NY"; address->Country = "USA";

Ještě jednodušší je uložení této instance do databáze (někdy se takovýmto databázím pro jednoduchost použití říká „one line database“):

auto database = ref new Database("test.db"); bool success = database->Store(address);

Druhý řádek instanci zapíše atomicky do databáze, aniž by se musel programátor starat o její strukturu.

Získání instancí persistentních tříd z databáze

Získání tříd z databáze je také záležitost jedné řádky kódu. Všechny instance třídy PAddress můžeme z databáze vytáhnout takto:

database->Fetch(MQUERY(PAddress, p, true), ref new FetchedObjectCallback([](MetroClass^ _obj) { auto obj = (PAddress^) _obj; // ... }));

Tento kód postupně volá předaný lambda výraz s jednotlivými instancemi třídy PAddress. Skutečná síla objektových databází se ale ukazuje až při práci s podmínkami v dotazech. Zatímco při použití relační databáze bychom museli používat řetězce s SQL, objektové databáze umožňují specifikovat podmínky dotazů přímo v použitém jazyce, v našem případě tedy v C++. Takto deklarované dotazy se nazývají „native queries“, případě „safe queries“. Jejich největší výhoda je zřejmá – kontrola kódu překladačem (chyba v SQL by se projevila až za běhu programu).

Nativní dotazy

V MetroBase je pro dotazy důležité makro MQUERY. V něm specifikujeme třídu, jejíž instance chceme z databáze získat, a podmínku, kterou musí tyto instance splňovat. Pro získání všech lidí (instancí třídy PPerson) žíjících v Praze bychom tedy použili:

MQUERY(PPerson, p, p->Address->City == "Praha")

Zápis podmínky je čisté C++, překladač syntax kontroluje, funguje IntelliSense atd. Výraz se ale nepřeloží do lambda výrazu (typu std::function<bool(MetroClass^)>), i když se tak chová, ale do abstraktního syntaktického stromu (AST), který následně databáze použije pro vyhledávání v indexech. Lambda výraz bychom totiž mohli použít jen tak, že bychom z databáze získali všechny instance dané třídy a zavoláním lambdy testovali, zda splňují podmínku. Takové řešení bychom ale těžko mohli nazývat databází (pro některé datové struktury má samozřejmě smysl, např. spojový seznam). Místo vyhodnocování lambdy se překladačem vytvořený AST přeloží do něčeho jako WHERE p.Address.City = ‚Praha‘ v OQL a databáze zaloví v indexu a vytvoří jen instance, které podmínku splňují.

Překlad výrazů do AST je v C++ poněkud krkolomný (složitě využívá prepocesor a přetěžování operátorů), důležité ale je, že výsledkem překladu je AST a hledání v databázi tedy není sekvenční. Nevýhodou je, že tato metoda funguje pouze v C++, takže při tvorbě Metro aplikace v C#, VB nebo JS (při použití MVC) musí být model v C++. Vzhledem k WinMD a projekcím tříd ale není problém napsat model jako WinRT komponentu s rozhraním použitelným z ostatních podporovaných jazyků.

NB: Výše uvedené příklady jsou trochu zjednodušující, v reálné aplikaci je nutné používat BeginTransaction, Commit, Rollback. V rámci jedné transakce je samozřejmě možné uložit libovolný počet objektů.

Sdílet