Před pár měsíci jsem se tu
šířil
o generátoru parserů ANTLR
a jeho použití při tvorbě front-endu transformujícího SQL do formátu vyžadovaného různými databázovými backendy
(teoreticky je SQL standartizovaný jazyk, ovšem v praxi vidí prodejci databází příliš mnoho výhod
v produktové diferenciaci,
než aby standartizovali úplně všechno). Předchozí post se omezil na obecný přehled
a první dojmy, ale jelikož jsem od doby jeho napsání zmiňovanou komponentu nejen úspěšně
dokončil, ale dokonce ukecal zákazníka aby ji uvolnil jako Open Source, mám příležitost
vrátit se k tématu podrobněji.
Komponenta se jmenuje
MacroScope, a zvenčí se tváří
jako víceméně obyčejný
datový provider.
Interně ovšem nepracuje přímo s databází, nýbrž
deleguje na
"opravdového" providera, a při té příležitosti přepisuje SQL specifikované aplikací tak,
aby mu vnitřní provider rozumněl. Úpravy SQL jsou specifikovány nad jeho gramatikou
("Oracle nezná SELECT TOP, ale zato má funkci ROWNUM, pomocí které se dá zkonstruovat
tentýž výsledek"), takže pro jejich implementaci je prakticky nezbytné SQL parsovat
- a tam právě přišel ke slovu ANTLR.
Pro parsování SQL se ANTLR rozhodně osvědčil - už proto že je to úloha dost běžná,
aby ji už někdo
skoro udělal
(pravda, pro starou verzi ANTLR, pouze SELECT a pouze pro MS SQL Server - MacroScope
podporuje i Oracle a na zvláštní přání publika dokonce MS Access - ale pořád mnohem
lepší než vynalézat kolo). Další velká výhoda formální gramatiky je dokumentační:
podporovaná varianta SQL je přesně to co specifikuje soubor MacroScope.g.
Popsat tu variantu slovně je fakticky dost obtížné - je to podmnožina definic INSERT,
SELECT, UPDATE a DELETE v SQL 92, s přidanými nekompatibilními konstrukcemi
z podporovaných backendů, takže když člověka zajímají detaily, řekněme jak napsat
datum, je formální definice nezastupitelná:
MAccessDateTime :
'#' Digit Digit Digit Digit '-'
Digit Digit '-' Digit Digit ' '
Digit Digit ':' Digit Digit ':' Digit Digit
'#'
;
Iso8601DateTime :
Digit Digit Digit Digit '-'
Digit Digit '-' Digit Digit ( 't' | ' ' )
Digit Digit ':' Digit Digit ':' Digit Digit
;
A je to jasné... Nad lexikálními pravidly buduje MacroScope
objektový model,
tj. např. pro to datum
educationalSimplification returns [ INode value ] :
// subset of an MS Access-specific format
| MAccessDateTime {
$value = new LiteralDateTime($MAccessDateTime.text);
}
// subset of ISO 8601 recommended for SQL Server 2005
| Iso8601DateTime {
$value = new LiteralDateTime($Iso8601DateTime.text);
}
;
INode je základní interface definující protokol společný pro
všechny podstromy SQL, od konstant až po kompletní příkaz. Protokol je
to velmi prostý:
public interface INode
{
INode Clone();
void Traverse(IVisitor visitor);
}
}
Veškeré transformace, včetně transformace stromu na řetězec, dělají
Návštěvníci.
Technicky by visitor mohl dělat i to klonování, ale rozhodl jsem se že
než být purista, napíšu to radši srozumitelně. Testy na živých programátorech
bylo zjištěno, že výsledný model je v zásadě použitelný pro automatizované
úpravy SQL (typicky přidávání podmínek - je mnohem čistší, srozumitelnější
a bezpečnější přidat objekt než hledat v řetězci "WHERE"), akorát je dost
velký a uživatelé ne vždy vědí kterou třídu instancializovat - ale to už nemá
s ANTLR moc společného...
Samozřejmě
neexistuje jídlo zdarma a
generátor kódu přináší kromě výhod i komplikace. Například takový build
funguje pro jednoduché projekty úplně automaticky z IDE Visual Studia, ale
s generováním kódu moc nepočítá. Visual Studio má
jakési háky,
ale logiku dodatečných kroků bych stejně musel programovat ručně v nějakém
dalším jazyce, "podporované" .BAT soubory používat nebudu a
NAnt nové uživatele zarazí IMHO méně
než řekněme
Perl.
Kromě toho jsem stejně chtěl integrovat i unit testy používající
NUnit, takže build komponenty dělá NAnt.
Na druhé straně pro ruční práci s projektem (tj. hlavně debugování) má IDE Visual
Studia své výhody, takže jsem se ho pokusil do buildu zahrnout: na nejvyšší
úrovní je build kontrolován ručně psaným skriptem, ale všechny ostatní buildovací
skripty jsou generované pomocí XSLT (NAnt na to má
příkaz)
z projektových souborů Visual Studia. Transformace bohužel není dost obecná
aby se dala beze změn používat i v dalších projektech - vždycky se najde
nějaký speciální případ, který je třeba hardcodovat - nicméně v rámci jednoho
projektu je celkem stabilní. Tato organizace umožňuje používat IDE Visual Studia
pro veškerý vývoj (včetně přidávání souborů do projektu) kromě změn gramatiky
a spouštění unit testů. Celkem se osvědčila - největší nevýhodou je že po přidání
souboru musím před spuštěním NAntu uložit projekt - a rozhodně ji zkusím
i při dalších příležitostech.
Jak už jsem se zmiňoval v předchozím postu, ANTLR je živý projekt, bouřlivě se vyvíjí
a není vždycky možné izolovat MacroScope od jeho nekompatibilních změn, jakkoli
by to bylo žádoucí. Pro překlad existujících zdrojáků není ANTLR nezbytný
(generovaný kód a binární verze ANTLR runtimu jsou součástí distribuce), ale jakékoli
změny gramatiky vyžadují jeho instalaci - na Windows
není automatická, ale na
druhé straně na aplikaci v Javě to není tak hrozné. Co se cílové platformy týče,
MacroScope jsem zkoušel na XP a Vistě, které celkem žádné problémy nedělaly.
Pro frontend k mnoha databázím Windows CE asi není až tak zajímavou platformou, ale
viděl jsem projekt na PDA, ve kterém by se ANTLR uplatnil, konkrétně pro parsování
konfigurace obsahující jakési vzorečky. Takové projekty by ovšem vyžadovaly
otestování a možná i úpravy ANTLR runtimu pro .NET CF, a jeho rekompilace z nových
zdrojáků by skoro určitě nefungovala s ANTLR 3.01, což je poslední release ANTLR.
Zdrojáky pro C# bohužel nejsou jeho součástí - nějak se u nich zrovna měnil
maintainer, a identifikovat použitelnou verzi v repozitáři by byla pěkná otrava -
takže na drastické úpravy asi bude lepší počkat na ANTLR 3.1, jehož release
se očekává každým dnem. Podle toho co jsem z nové verze viděl, určitě bude obsahovat
mnoho
zajímavých
změn.