Tartalmi kivonat
Budapesti Műszaki és Gazdaságtudományi Egyetem Villamosmérnöki és Informatikai Kar Távközlési és Médiainformatikai Tanszék Adatbázis migráció Microsoft SQL Server adatbázisról Oracle adatbázis-kezelőre Tánczos Zoltán Csaba F966Q8 Konzulens: Kardkovács Zsolt Tivadar Diplomaterv 2009 Nyilatkozat Alulírott Tánczos Zoltán Csaba, a Budapesti Műszaki és Gazdaságtudományi Egyetem hallgatója kijelentem, hogy ezt a diplomatervet meg nem engedett segítség nélkül, saját magam készítettem, és a diplomatervben csak a megadott forrásokat használtam fel. Minden olyan részt, melyet szó szerint, vagy azonos értelemben, de átfogalmazva más forrásból átvettem, egyértelműen, a forrás megadásával megjelöltem. Budapest, 2009. május 31 Tánczos Zoltán Csaba Kivonat Ebben a dolgozatban az adatbázisok közötti migráció kérdéseivel foglalkozok. Bemutatom, hogy általános esetben egy adatbáziskezelő rendszer cseréje során
milyen jellegű problémákkal szembesülhetünk, illetve ezek kezelésére milyen módszerek léteznek. Ezután két konkrét rendszerre, a Microsoft SQL Serverre és az Oracle adatbáziskezelőre helyezem a hangsúlyt. Ismertetek olyan szoftvereket, amik segítik az ezen rendszerek közötti átállást, kitérve ezek használatának korlátaira is. Bemutatok egy módszertant, ami egyfajta keretet nyújt a migrációs munka szervezéséhez, elvégzéséhez, jól definiálva az elvégzendő fázisokat, a fázisok során felmerülő feladatokat. A különbségek taglalása után arra a megállapításra jutok, hogy a két rendszer közötti elvi különbségek miatt nem létezhet olyan szoftver, ami képes a teljes migrációs folyamatot automatizáltan véghezvinni. Ezeket a „problémás területeket” külön kiemelem, részletezem. A dolgozat második fejezetében egy olyan fordító funkcionális tervét mutatom be, ami képes Transact-SQL kód PL/SQL-re fordítására, az
ismertetett korlátok között. A fordítót az ANTLR és a StringTemplate rendszerek segítségével implementálom Az elkészített fordító a céljaim szerint bizonyos részfeladatok automatizálásával hatékony segédeszköz lehet azok számára, akik SQL Server-ről Oracle adatbáziskezelő alá migrálják a tárolt programokat, eljárásokat. A függelékben bemutatott tesztesetek tanulsága szerint bizonyos szituációkban jobb fordítási eredményt sikerült elérnem, mint az Oracle által fejlesztett SQL Developer. Abstract In this thesis I discuss database migration issues. I present the general problems one would face during the exchange of a database management system, and what methods exist to deal with them. Then I place the emphasis on two specific systems: Microsoft SQL Server and Oracle Database. I review database migration tools which can help the transition between these systems, and I also present limitations of such softwares. I describe a methodology
which can provide a framework for the recommended process for the migration of an existing third-party database to Oracle. It provides well-defined phases, and step-by-step instructions to be carried out. After I show the main differences between those database systems, I’ve come to the conclusion that because the two system differ in principle a fully automatic migration software which covers the whole migration process could not exist. I describe these problematic areas in details. In the second chapter of the work I present the functional plan of a language translator which is capable of translating Transact-SQL source code into PL/SQL, in respect of the described limitations. I implement the translator using the ANTLR and StringTemplate frameworks. The purpose of this translator is to help the migration from SQL Server to Oracle Database with the automation of certain tasks. As shown in the Appendix, in some cases I achieved better translation results as the SQL Developer, which
is developed by Oracle. Tartalomjegyzék 1. Bevezető 1 1.1 Adatbázisok migrációinak általános problémái 1 1.11 Adatok migrációja 2 1.12 Programnyelvek közötti fordítás 3 1.2 SQL Server és Oracle közötti migrációs eljárások 6 1.21 Az Oracle Relational Migration Map módszertan bemutatása 6 1.22 Migrációt segítő szoftverek bemutatása 10 1.23 A migrációs eszközök korlátai 12 1.3 Összefoglalás 22 2. Fordító tervezése 24 2.1 Formális nyelvi elemzés 25 2.11 Az ANTLR elemző-generátor bemutatása 27 2.12 A StringTemplate sablon-motor bemutatása 30 2.2 Funkcionális terv 32 2.21 A feladat leírása 32 2.22 A rendszer célja
33 2.23 Komponensek leírása 33 2.3 Összefoglalás 40 3. Értékelés 43 3.1 Bővíthetőség 45 3.2 Limitációk 45 4. Összefoglalás 46 A. Oracle SQL Developer Migration Workbench 48 B. A funkcionális tervben specifikált transzformációk működés közben 52 1 Ábrák jegyzéke 1.4 Egy T-SQL tárolt eljárás lehetséges visszatérési értékei 21 2.2 A fordítás folyamata ANTLR+StringTemplate-el 32 2.3 Egy T-SQL tárolt eljáráshoz tartozó absztrakt szintaxis fa 35 2.4 A fordítóhoz tartozó grafikus kezelői felület 42 3.1 Szintaktikailag hibás bemenet fordítása a fordító segítségével 44 B.1 Kurzorváltozók használata kimeneti eredményhalmaz előállításához 52 B.2 SELECT utasítás FROM klauzula
nélkül 53 B.3 SELECT utasítás kétféle alias szintaxisa 53 B.4 SELECT utasítás INTO klauzulájának fordítása 53 B.5 UPDATE utasítás átalakítása MERGE utasításra 1 54 B.6 UPDATE utasítás átalakítása MERGE utasításra 2 54 B.7 IF EXISTS szerkezet szimulálása 54 B.8 Tárolt eljárás visszatérési értékkel 55 B.9 WHILE ciklus 55 B.10 Hibajelzés: RAISERROR fordítása 56 B.11 INSERT utasítás INTO kulcsszó nélkül 56 B.12 DELETE utasítás második FROM klauzulával 56 B.13 DELETE utasítás második FROM klauzulával – összetettebb példa 57 B.14 DELETE utasítás hibás fordítása az Oracle Migration Workbench-el 57 2 Táblázatok jegyzéke 1.1 Feltételes szerkezetek
leképezhetősége a T-SQL és a PL/SQL között 4 2.1 A SELECT utasításnál szükséges transzformációk 38 2.2 Az INSERT utasításnál szükséges transzformációk 38 2.3 A DELETE utasításnál szükséges transzformációk 39 2.4 Az UPDATE utasításnál szükséges transzformációk 39 2.5 Az IF EXISTS szerkezetnél lehetséges transzformációk 41 A.1 Az sqlserver utilities csomagban található függvények 49 3 1. fejezet Bevezető 1.1 Adatbázisok migrációinak általános problémái Adatbázisok ritkán léteznek önmagukban. Adatbázisokat azért készítünk, hogy a bennük tárolt adatokhoz hatékonyan, rendszerezetten, kontrolláltan férjünk hozzá. Ez az esetek kis hányadában jelenti azt, hogy a felhasználó közvetlenül az adatbázishoz csatlakozik, és az adatbáziskezelő nyelvén utasításokat ad ki A gyakoribb az, amikor valamilyen rendszer van
az adatbázis előtt, amin keresztül érjük el az adatokat, az adatbáziskezelő funkcióit. Ez a külső „burok” tetszőlegesen összetett lehet. Az egyszerű kliens-szerver architektúrától kezdve a három rétegű architektúrán át egészen a különböző adattárház-rendszerekig számos kombináció elképzelhető. Ezen kívül több adatbázis-szerver is elképzelhető, melyek egymással közvetlenül is kommunikálhatnak, adatokat cserélhetnek. Ezen sokszínűség miatt az adatbázis-kezelő lecserélése komplex probléma, mely rendszerszintű szemléletmódot igényel. Az adatbázis-kezelő egy kiszolgáló, melynek bármilyen módosítása a hozzá közvetve vagy közvetlenül kapcsolódó összes entitás működését befolyásolhatja Az 1.21 fejezetben bemutatott módszertan egy olyan keretet próbál nyújtani, ami segíti a migrációs folyamat rendszerszintű átfogó kezelését Egy probléma mérnöki megoldása absztrakciót,
részfeladatokra bontást igényel. Ebben a dokumentumban kifejezetten az adatbázis-migráció problémáival, lehetséges megoldásaival foglalkozok, azt a célt szem előtt tartva, hogy a folyamat a lehető legkisebb módosítást igényelje a kliens-alkalmazásokban. A migráció során kezelendő problémákat az alábbi kategóriákba oszthatjuk: • Adat-migrációval kapcsolatos problémák: a forrásrendszerből az adatokat valahogyan át kell emelni a célrendszerbe, megőrízve az adatok konzisztenciáját, integritását. Ez különösen különböző paradigmájú adatbáziskezelők közötti váltásnál kritikus (pl: nemrelációsról relációsra), de pusztán az alkalmazható adattípusok közötti különbségek is okozhatnak problémákat. 1 • Nyelvi különbségekből adódó problémák: a legtöbb adatbáziskezelő az SQL nyelv valamilyen dialektusát támogatja, de természetesen nem egységesen. Az egyes gyártók megpróbálják minél több
kiegészítéssel ellátni a saját verziójukat, és még a szabványokat is eltérően értelmezik. Ez különösen akkor kritikus, ha az üzleti logika jelentős része az adatbázisban van megvalósítva (tárolt programok, eljárások formájában). • Adminisztrációval, üzemeltetéssel kapcsolatos problémák: az adatbázisokat általában szakemberek üzemeltetik, akik értenek az adott szoftver adott verziójával kapcsolatos feladatok ellátásához. Ha lecseréljük ezt a szoftvert, elengedhetetlen, hogy biztosítsuk az üzemeltetőknek a feladatuk ellátásához szükséges kompetencia megszerzését (vagy cseréljük le őket is). Mivel az adminisztratív feladatok köre illetve elvégzésük módja nem csak a gyártók között különbözik, hanem akár verzióról verzióra is változhat, ezen problémakör általános kezelése nem megoldható. A fejezet további részében az első két problémakörrel foglalkozok: először általánosságban, majd két
konkrét rendszer, az SQL Server és az Oracle adatbáziskezelő közötti migrációs lehetőségek, korlátok bemutatásán keresztül. 1.11 Adatok migrációja Az adatbázisban tárolt két legnagyobb érték az adatok maguk, illetve az üzleti logika (tárolt programok). Ha cseréljük az adatbázist, akkor értelem szerűen mindkét értéket meg szeretnénk őrizni, mitöbb, növelni szeretnénk az együttes hasznosságukat, különben nem lenne racionális döntés a váltás. Ebben a részben az adatok migrációjának kérdéseivel foglalkozok Az adatok migrációja általában három fázisra bontható[4]: • A forrás adatok elemzése • Modell leképezése vagy elkészítése • Forrás adatok átmozgatása Az első lépés során a forrásrendszerben található adatok illetve a séma elemzésével elkészítjük a forrásrendszer adatmodelljét. Szerencsés esetben ez a modell rendelkezésre áll Ezután megalkotjuk a leképezéseket, illetve definiáljuk az
esetlegesen szükséges transzformációkat. Ez a lépés közel sem triviális, hiszen elképzelhető, hogy egynél több forrás-adatbázist szeretnénk egy modellre, egy adatbázisra leképezni. Az adatok átmozgatása is egy összetett lépés, ami gondos tervezést igényel. Például, maga a sorrend sem mindegy, mely adatokat töltjük át először, és melyeket később, ha biztosítani szeretnénk a referenciális integritást a célrendszerben: először az elsődleges kulcsokat tartalmazó táblákat, majd utána a hozzá kapcsolódóakat[2]. 2 Adatok migrációjáról szóló szakirodalom viszonylag szűk ahhoz képest, hogy mennyi publikáció, szakkönyv született az adattárházak betöltését szolgáló ETL folyamatról (Extract, Transform, Load). A két dolog pedig közel azonos: egy vagy több heterogén forrásrendszerből az adatokat át akarjuk emelni egy vagy több célrendszerbe. Mindösszesen a cél és az időzítés különbözik: nem egy új
igényeket kielégítő, új rendszert akarunk rendszeresen (hetente, naponta, real-time) adatokkal táplálni, hanem a meglévő rendszer funkcionalitását megőrízve (esetleg kiegészítve) „csak” adatbázist akarunk váltani1 . Ezért az ETL folyamat során felmerülő problémák – leszámítva talán a performanciát érintőeket – itt is előjönnek Ezeket alapvetően két csoportra oszthatjuk[9]: • Egy forrásból eredő problémák • Több forrásból eredő problémák Mindkét csoportot tovább bonthatjuk séma-szintű illetve adat-szintű problémákra. A sémaszintű problémákra példák lehetnek a hibás tervezésből adódó hibák (egyediség hiánya, referenciális integritás megsértése), vagy több forrás esetén az elnevezésekből adódó félreértések (pl.: az a mező, hogy cím valójában mit is jelent?) Ha egy „tökéletesen” megtervezett forrás adatbázisunk van, amit nem akarunk más forrásokkal integrálni, csupán
egy közel megegyező architektúrájú másik adatbáziskezelő alá szeretnénk migrálni, akkor is lehetnek az adattípusok különbözőségéből adódó problémák. Az SQL Server és az Oracle adatbáziskezelő például egymástól különböző adattípusokkal rendelkezik. Ezen különbségekkel részletesen az 1.23 fejezetben foglalkozok Az igazi fejfájást okozó problémák a látszólagos egyezőségekből erednek: mindkét előbb említett adatbáziskezelő rendelkezik dátumok tárolására alkalmas típussal. Ez SQL Server alatt a DATETIME míg Oracle alatt a DATE. Viszont ez a típus SQL Server alatt 333 milliszekundum, Oracle alatt pedig másodperc pontosságú, és a tárolható intervallum is különbözik: 1753-től 9999-ig SQL Server alatt, és I.e 4712-től 9999-ig Oracle alatt Érdemes még megemlíteni, hogy ugyanazt az adatot is lehet többféleképpen értelmezni: az Oracle adatbáziskezelő a “ értékeket (üres sztring) NULL-ként kezeli.
1.12 Programnyelvek közötti fordítás A nyelvek közötti fordítást több tényező is motiválhatja. Az üzleti szempontokon kívül az egyik leggyakoribb cél a karbantarthatóság növelése. Számos szakirodalom foglalkozik régről megörökölt, legacy rendszerekről történő átállással. A probléma ezekkel egyrészt az, hogy nehéz az integrációjuk az újabb rendszerekkel szoftver vagy hardver inkompatibilitási okokból[4], 1 Ezzel együtt esetleg architektúrát, paradigmát is, pl.: nem relációsról relációsra, objektum-orientáltra, stb 3 másrészt, hogy – habár működnek – felmerülnek módosítási igények, viszont az oktatásból kikerülő szakemberek jó eséllyel már nem tanultak 10-15 évvel ezelőtti technológiákat. Ezt a rést hivatottak áthidalni a különböző nyelvek közötti automatizált fordító programok.[10] Egy forráskód átfordítása egy másik nyelvre a meglévő nyelvi konstrukciók leképezését jelenti
a célnyelvre. Az 11 ábrán a lehetséges leképezések láthatóak A natív szerkezetek azok a nyelvi elemek, amelyek egy adott feladatot kényelmesen oldanak meg. Például egy olyan programnyelvben, ami támogatja az if.thenelse szerkezetet, egy feltételes kifejezés leírása kényelmes, viszont olyan nyelvben, amiből ez hiányzik, szimulálnunk kell a működését Ezeket a szerkezeteket szimulált szerkezeteknek nevezzük. 1.1 ábra Nyelvi elemek leképezése a fordítás során [10] Az összes nyílnak megfelelő átmenet elképzelhető. Ennek illusztrálására nézzük meg a Transact-SQL és a PL/SQL feltételes szerkezetét: IF 1=2 SET @x = 3 IF 1 = 2 THEN v x := 3; END IF; IF EXISTS(SELECT * FROM t1) DECLARE SET @x = -1 v temp NUMBER(1, 0) := 0; BEGIN SELECT 1 INTO v temp FROM DUAL WHERE EXISTS ( SELECT * FROM t1 ); IF v temp = 1 THEN v x := -1; END IF; END; 1.1 táblázat Feltételes szerkezetek leképezhetősége a T-SQL és a PL/SQL között 4 Az 1.1
táblázatban az első esetben egy natív szerkezetet egy natív szerkezetre tudunk leképezni A második esetben viszont – mivel a PL/SQL nem támogatja az IF EXISTS szerkezetet – szimulálnunk kell a működését. A nem létező konstrukcióknak megfelelő átmenetre példa lehet a T-SQL tranzakció kezdetét jelző begin transaction utasítás: mivel Oracle alatt minden tranzakció implicit, azaz automatikusan, az első végrehajtható SQL utasítással kezdődik, nincs megfelelő utasítás. Terekhov és Verhoef munkájukban[10] 9 követelményt támasztanak az automatikus fordításokkal szemben: • Szükséges összegyűjteni azokat a natív és szimulált nyelvi konstrukciókat, amelyeket át kell fordítani. • Minden egyes konstrukcióhoz meg kell alkotni a fordítási stratégiát. • Ki kell jelenteni, hogy az átfordított rendszer funkcionálisan megegyező lesz-e a forrásrendszerrel. Erre válaszul kapásból igent mondhatnánk, pedig előfordulhat,
hogy fordítás közben felszínre kerülhetnek hibás, nem biztonságos kódrészletek, melyeket célszerű javítani. • Ki kell jelenteni, hogy a meglévő tesztesetek is fordításra kerülnek-e vagy sem. • A lehető legnagyobb automatizáltságra kell törekedni, azaz minimalizálni kell az emberi beavatkozási igényt. • Az elkészített kódnak karbantarthatónak kell lennie. Ha ugyanaz a csapat fog dolgozni az új kóddal is, mint a régivel, akkor fontos, hogy a fordítás során a minél hasonlóbb szerkezeteket használjunk a forrásrendszerhez képest. Viszont ha új fejlesztőket vonunk be, akkor pedig az általánosan elfogadott, ismert konvencióknak megfelelő kódot célszerű generálni. • Az átfordított kódnak elfogadható hatékonyságúnak kell lennie. • Ha többször (pl.: rendszeresen) kell használni a fordítót, akkor annak is megfelelő hatékonyságúnak kell lennie • Nem szabad túlzott méretnövekedést megengedni (a
karbantarthatóság miatt). Sajnos egy teljes értékű fordító készítése általában nem megvalósítható. Először is, ha sikerül egy natív konstrukciót natív konstrukcióra fordítani, nem biztos, ekvivalens kódot kapunk (habár szintaktikailag úgy tűnhet). Ilyen esetek a típusok, típuskonverziók, hibák, mellékhatások eltérő kezeléséből adódhatnak. Például, az alábbi utasítások szintaktikailag ekvivalensek: 5 -- Transact-SQL: SELECT @x = col FROM t1 -- PL/SQL: SELECT col INTO v x FROM t1 Viszont eltérő működést tapasztalunk, ha a t1 táblában nem pontosan 1 sor van: TransactSQL-ben ha kevesebb, mint 1 sor van, akkor a változó megtartja eredeti értékét, ha több, akkor pedig az utolsónak kiolvasott értéket kapja, míg PL/SQL-ben mindkét esetben kivétel dobódik2 . Másodszor, mikor egy magas szintű programnyelvről egy másik magas szintű programnyelvre fordítunk, akkor nem csak a nyelvi szerkezetek egymásnak
történő megfeleltetése szükséges, hanem a függvénykönyvtár-hívások megfeleltetése is[11]. Ezért egy teljes értékű fordító elkészítése csupán egy célként lebeghet a szemünk előtt, amit – a folyamatosan megjelenő új, illetve a változó függvénykönyvtárak miatt – valószínűleg sosem fogunk tudni elérni. 1.2 SQL Server és Oracle közötti migrációs eljárások A Microsoft SQL Server és az Oracle adatbáziskezelő közötti váltást elsősorban üzleti szempontok motíválhatják. Mindkét gyártó támogatja a migrációs folyamatot automatizált fordítókkal, természetesen csak az egyik irányba Oracle adatbázisról SQL Serverre (pontosabban a 2005-ös verzióra) való váltást az SSMA for Oracle (SQL Server Migration Assistant for Oracle) segíti. Ebben a dokumentumban a másik irányra koncentrálok, azaz hogyan, milyen módszertanok, eszközök segítségével lehet SQL Server adatbázisról Oracle adatbázisra átállni. 1.21
Az Oracle Relational Migration Map módszertan bemutatása Az Oracle Relational Migration Map[14] (röviden: Migration Map) az Oracle által használt és javasolt módszertan a meglévő adatbázisok Oracle platformra migrálásához. A módszertant elsősorban az Oracle Technology Migration Factory mérnökei használják az ügyfelek adatbázisainak és alkalmazásainak migrációjához. Szerepkörök A módszertan az alábbi szerepköröket definiálja: • Ügyfél 2 ORA-01403: nincs adat és ORA-01422: a pontos lehívás a kívántnál több sorral tért vissza 6 • Migrációs projekt menedzser • Adatbázis migrációs mérnök • Alkalmazás migrációs mérnök Ügyfél Az ügyfél elsődleges feladata, hogy technikai segítséget nyújtson a migráció során a rendszerrel kapcsolatban felmerülő kérdésekben. Ezen kívül a projekt elején ki kell tölteni egy úgynevezett Assessment Questionnaire3 -t, aminek célja az alapvető követelmények
meghatározása, a szükséges erőforrások felbecsülése, a jelenlegi alkalmazás bemutatása. Ezen kívül még el kell juttatnia az alkalmazás forráskódját a migrációs projekt menedzsernek. Az ügyfélnek a projekt során véleményeznie kell a migrációs csapat által leszállított dokumentumokat, programokat, script-eket, illetve meg kell vitatnia a migrációs projekt menedzserrel a felmerülő problémákat. Migrációs projekt menedzser A migrációs projekt menedzser áll a középpontban, ő felügyeli az egész projektet. Az ő feladata a minőségi munka biztosítása a migrációs mérnökök részéről, illetve ő kezeli az ügyfél igényeit. Rajta keresztül zajlik a kommunikáció, ő tervezi meg a feladatokat, illetve azok időzítését, a felmerülő problémák esetén ő tájékoztatja az ügyfelet, illetve ő szerzi be az engedélyeket a javasolt megoldások végrehajtásához. Adatbázis migrációs mérnök Az adatbázis migrációs
mérnök az Oracle SQL Developer Migration Workbench (továbbiakban: OMWB) segítségével elvégzi a forrás adatbázis Oracle adatbáziskezelőre történő migrációját, és teszteli, hogy ugyanúgy működik-e, mint a forrás adatbázis. Az ő feladata még, hogy megbecsülje a projekt elvégzéséhez szükséges erőforrásokat, illetve az időtartamot Alkalmazás migrációs mérnök Ez a szerepkör igen hasonló az adatbázis migrációs mérnök szerepköréhez, azzal a különbséggel, hogy az elsődleges feladat nem az adatbázis, hanem a hozzá kapcsolódó alkalmazás forráskódjában szükséges változtatások megtervezése és megvalósítása annak érdekében, hogy az alkalmazás képes legyen Oracle adatbáziskezelőt használni. Projekt fázisok A migrációs projekt az 1.2 ábrán látható módon 6 fázisra van bontva A fázisok során az ábra bal oldalán felsorolt kategóriákba tartozó feladatokat kell elvégezni. 3
http://www.oraclecom/technology/tech/migration/maps/assessment questionnairedoc 7 1.2 ábra Fázisok a Migration Maps módszertanban [14] Definíciós fázis A definíciós fázis arról szól, hogy elegendő információt gyűjtsünk a meglévő rendszerről, hogy aztán megfelelő becsléseket (erőforrás, idő) tudjunk készíteni a migrációról. A meglévő rendszer vizsgálata magában foglalja a különböző adatbázis objektumok típusainak, méretének, darabszámának kigyűjtését, az alkalmazás forráskódjából egy úgynevezett inventory készítését, amiben szintén rögzítjük a forrásfile-ok típusait illetve méretüket. Az ügyfél ebben a fázisban tölti ki a már említett Assessment Questionnaire-t annak érdekében, hogy a migrációt végző mérnökök feltudják becsülni a feladat végrehajtásához szükséges erőforrásokat. A migráció megkezdéséhez meg kell határozni az alapvető architektúrális technikai
követelményeket, ami az alábbi információk begyűjtéséből áll: • Interface követelmények • Adatbázis partícionálási követelmények • Adatbázis biztonsági követelmények • Adatbázis adminisztrációs követelmények • Alkalmazás architektúra • Biztonsági mentési és visszaállítási stratégia Az alapvető követelményeken kívül a módszertan definiálja a teljes architektúrális technikai követelmények fogalmát is, ami az alábbi részekből áll: • Operációs rendszer stratégia és architektúra • Hardver és tárolási stratégia • Adatbázis verzió stratégia 8 • Középső réteg stratégia és architektúra • Hálózati stratégia és architektúra • Rendszer integrációs stratégia és architektúra • Skálázhatósági és teljesítmény stratégia Ebben a fázisban történik a forrás adatbázis metaadatainak kigyűjtése az Oracle Migration Workbench segítségével: az ügyfél, vagy a migrációt
végző mérnökök a program segítségével elkészítik az úgynevezett Captured Model-t (lásd később). Analízis Az analízis fázisban kezdődik az igazi munka. Egyeztetni kell az ügyféllel a definíciós fázisban felderített kérdéseket, problémákat, javaslatokat Meg kell vizsgálni az alap és a részletes architektúrális technikai követelményeket, hogy a következő, tervezési fázisban el lehessen készíteni a célrendszer architektúrális terveit. Egy migráció során nem triviális, hogy az ügyfél mennyire ismeri a célrendszert, annak sajátosságait, a forrásrendszerrel szembeni különbségeit, ezért fontos feladat az oktatás. Az analízis fázisban kell meghatározni, hogy az ügyfélnek milyen témákban van szüksége oktatásra. Az ügyfélnek el kell készítenie a tesztelési és átállási követelményeket, amikben specifikálja a tesztelési ciklusokat, tesztelő scripteket, illetve az átállással kapcsolatos ütemezést, üzleti
és hardver követelményeket. Tervezés A tervezési fázisban a migrációt végző mérnökök a definíciós és analízis fázisok során szerzett ismeretek alapján megtervezik a felmerült feladatok, problémák megoldását, azaz elkészítik az oktatási tervet, a célrendszer hardver, hálózati és szoftver architektúráját, a felmerült architektúrális problémákra a megoldási terveket, az adatok migrációjának a stratégiáját, illetve az átállási stratégiát. Migráció A migrációs fázis célja, hogy tömör, megbízható script-ek és riportok álljanak az ügyfél rendelkezésére, amikből felépítheti a célrendszer alatt az adatbázis sémát. Ebben a fázisban mind a migrációs mérnököknek, mind az ügyfélnek részt kell venniük oktatásokon, hogy rendelkezzenek az adatbázis és az alkalmazás migrációjához szükséges ismeretekkel. A fázis legfontosabb része a séma illetve a tárolt programok migrációjának elkészítése, azaz
DDL (Data Definition Language) utasításokat tartalmazó scripteket kell készíteni, amik létrehozzák a táblatereket, felhasználókat, táblákat, kulcsokat, indexeket, tárolt eljárásokat, függvényeket, stb. Miután megtörtént a script-ek leszállítása, az ügyfél megvizsgálja a migrált sémát, és aláírásával igazolja annak helyességét. 9 Ezzel párhuzamosan történhet az alkalmazás migrációja, ami az analízis fázis során felderített esetlegesen problémás kódrészletek egyesével történő vizsgálatát, és a szükséges módosítások elvégzését jelenti. A megállapodástól függően a tesztelést vagy a migrációt végző mérnökök, vagy az ügyfél végzi. Átállás Az átállás fázis – mint ahogy a nevéből is következik – az átállást hivatott megvalósítani, azaz a migrált rendszer feltelepítését, ügyfél általi tesztelését, adatkonverziót illetve élesbe állítást. Az átálláshoz célszerű három
különálló rendszert felépíteni, egy fejlesztéshez használt DEV környezetet, teszteléshez használt TEST környezetet, illetve az éles működéshez használt PROD környzetet. A tesztelésnél a DEV környezetről egy pillanatkép készül, majd ezt követően telepíteni kell a TEST környezetre az aktuális állapotot. Ezen történnek majd többek között a UAT (user acceptance test) tesztek. Amennyiben minden teszt sikeresen lezárult, következhet a PROD környezet feltelepítése a TEST környezetről. Üzemeltetés Az utolsó, üzemeltetési fázisban a megállapodástól függően szükség lehet a már feltelepített, éles rendszerben támogatás nyújtására. 1.22 Migrációt segítő szoftverek bemutatása A migráció során felmerülő részfeladatok automatizálására, megkönnyítésére több eszköz is rendelkezésre áll. Ezek sikeres használhatósága több szemponton is múlik: • Először is pontosan tudnunk kell, hogy mit csinál az
adott eszköz. A fekete doboz megközelítés itt nem megengedhető Rendelkezésre kell állnia egy részletes dokumentációnak, amiben a gyártó specifikálja, hogy mit mire képez le az eszköz, ez milyen esetekben jó, és mikor igényel további kézi beavatkozást. Az 123 fejezetben részletesen megindoklom, hogy miért nincs értelme a teljes migrációs folyamat automatizálásának. • Másodszor pedig tudnunk kell, hogy mit nem csinál: a dokumentációban szerepelnie kell, hogy milyen konstrukciók fordításával nem foglalkozik az eszköz, akár az aktuális verzió limitációjából, akár a nyelvek különbségéből adódó korlátok miatt. Erre kiváló példa [12], amiben például a szerzők leírják, hogy habár SQL Server és Oracle alatt a SELECT utasítással egy változónak történő értékadás szemantikailag nem ekvivalens4 , a fordító 4 A hibakezelés miatt, lásd: @@error és ORA-01403: no data found és ORA-01422: too many rows 10 ezzel
nem foglalkozik. A továbbiakban vázlatosan bemutatok három ilyen eszközt a gyártók dokumentációja illetve saját tapasztalatok alapján. Az Oracle SQL Developer Migration Workbench lehetőségei Az Oracle SQL Developer Migration Workbench (röviden OMWB) az eredetileg Oracle Migration Workbench néven futó migrációt segítő különálló szoftver újraírt, kiegészített, és az SQL Developerbe integrált változata. Az OMWB segítségével Microsoft Access, SQL Server, MySQL és Sybase adatbázisokat lehet Oracle adatbázis alá migrálni. Kiemelendő, hogy az OMWB részletes, átlátható dokumentációval rendelkezik ([12]), mely szisztematikusan bemutatja a támogatott fordítási lehetőségeket, és külön jelzi, hogy mely területek igényelnek manuális beavatkozást. Sajnos hibás állításokat is tartalmaz az SQL Server-rel kapcsolatban. Például, a NULL értékek kezelését illetően azt állítja, hogy a NULL = NULL kifejezés true, ami nyilvánvalóan
hibás: a kifejezés értéke az ANSI NULLS5 beállítás értékétől függ[16]. Ez is azt az álláspontot igazolja, hogy amíg vélt, vagy valós különbségek vannak az adatbáziskezelő rendszerek között, addig a köztük történő migráció egy szakértelmet igénylő mérnöki feladat lesz, melyet az automatizáló szoftverek maximum segíthetnek, helyettesíteni nem tudják. Az OMWB migrációs beállításainak bemutatásához, illetve a migrációs folyamat részletezéséhez lásd az A. fejezetet A SwisSQL Migration Tool lehetőségei Az Oracle SQL Developer Migration Workbench-en kívül találhatunk még más szoftvereket is a migráció bizonyos részeinek automatizálására. Az egyik konkurens termék a SwisSQL SQL Server to Oracle Migration Tool (a továbbiakban: SwisSQL). A SwisSQL-t az AdventNet cég készítette. Ezzel SQL Server 2000 adatbázisokat (pontosabban csak adatbázis objektumokat) lehet Oracle 8, 9i, 10g adatbázis alá migrálni Ha adatokat is
szeretnénk migrálni, akkor külön meg kell vásárolni a SwisSQL Data Migration Tool-t. A SwisSQL bizonyos szempontból az OMWB ellentéte: míg az OMWB részletes dokumentációkkal segíti a felhasználót, addig a SwisSQL-ben mindössze egy vázlatos Súgó áll a rendelkezésre. A cég a terméket kvázi szolgáltatásként pozícionálja: ha adatbázist szeretnél migrálni, egyrészt vedd meg a szoftvert, másrészt vedd igénybe a támogatásunkat. Emiatt a megközelítés 5 Megjegyzendő, hogy ez a beállítási lehetőség az SQL Server következő verzióiban meg fog szűnni, és csak az ANSI szabványnak megfelelő működést lehet majd elérni. 11 miatt a SwisSQL értékelésére nincs mód, következtetéseket csak tapasztalat útján lehetne levonni, amit tovább nehezít az ingyenes változatban levő korlátozás: maximum 2000 sor kódot hajlandó lefordítani. Az SQLWays lehetőségei Az SQLWays az Ispirer cég által fejlesztett migrációs szoftver.
A SwisSQL-hez hasonlóan ez is egy kereskedelmi termék, ingyenesen csak egy próbaverzió áll rendelkezésre. Ami kiemeli az előző két szoftver közül az az, hogy – a honlapja6 szerint – 22 forrás- és 8 cél-adatbáziskezelőt támogat. Rendelkezik dokumentációval ([13]), ami viszont nem felel meg az 1.22 fejezetben ismertetett követelménynek: számos esetben nem lehet tudni, hogy mit mire fordít. Kiragad problémákat, amikre bizonyos forrás-cél konfigurációk esetén megadja a leírást, más esetekben nem. Például sehol sem említi, hogy SQL Server Oracle irány esetén hogyan kezeli a tárolt eljárások által visszaadott eredményhalmazok kérdését. A SwisSQL-hez hasonlóan az SQLWays részletes tárgyalása sem megvalósítható, mert sem kielégítő dokumentáció, sem teljes értékű ingyenes változat nem áll rendelkezésre. 1.23 A migrációs eszközök korlátai A bemutatott eszközök vegyesen próbálják megközelíteni a tárolt
programok átfordítását: van, ahol szimulálnak egy nyelvi konstrukciót, függvényt, van, ahol szintaktikailag ekvivalens, natív konstrukciót alkalmaznak (és a dokumentációban kijelentik, hogy szemantikailag nem ekvivalens), és van, ahol egész egyszerűen hibásan fordítanak. Ebben a részben sorra veszem azokat a területeket, amik miatt az automatikus fordítók csupán korlátozott sikert érhetnek el: a natív konstrukciók más működésre vezetnének, a szimuláció pedig túlburjánzó, karbantarthatatlan és fölösleges kódot eredményezne. Azonosító nevek, fenntartott kulcsszavak Az SQL Server az azonosító nevek tekintetében igen laza: engedi, hogy oszlopnévnek fenntartott, vagy kulcsszót adjunk (pl.: date), az objektumok nevei igen hosszúak is lehetnek, és természetesen az SQL Server által fenntartott szavak listája közel sem egyezik meg az Oracle adatbáziskezelő által fenntartott szavakkal. Ökölszabályként kijelenthető, hogy ne
használjunk se fenntartott, se kulcsszavakat azonosító névként7 . Ha vannak ilyenek, akkor azokat a forrás oldalon is nevezzük át Egy fontos szabály, amit nem lehet áthágni: azonosító név maximum 30 karakter (30 byte) hosszú lehet (kivéve adatbázis név - 8 byte, adatbázis-link név - 128 byte). Ha van olyan táblánk, 6 http://www.ispirercom Oracle alatt fenntartott és kulcsszavak listája: http://68.14211668/docs/cd/B28359 01/appdev111/b31231/appbhtm 7 12 oszlopnevünk, T-SQL változónk, függvényünk, stb., ami 30 karakternél hosszabb, akkor azt át kell nevezni (a Migration Workbench egyszerűen levágja a végét, ha névütközés van, akkor sorszámmal látja el az azonos neveket, pl: myverylongtable 1, myverylongtable 2, stb.) A ""-k között szereplő fenntartott szavak lehetnek azonosító nevek, például: "DATE", de ebből lehetnek problémák: Ha egy date típusú oszlopnak a "DATE" nevet adjuk, akkor – habár
a tábla létrejön – majdnem minden hivatkozás az adott táblára hibát fog eredményezni: ORA-06550: line 5, column 61: PL/SQL: ORA-06552: PL/SQL: Compilation unit analysis terminated ORA-06553: PLS-320: the declaration of the type of this expression is incomplete or malformed ORA-06550: line 5, column 2: Az igazán nagy problémát az jelenti, hogy ha átnevezünk egy azonosítót, akkor minden rá történő hivatkozást is módosítani kell. Ez kihathat az adatbázishoz csatlakozó kliensekre is Ezért ezt a problémát még a fordítás megkezdése előtt, attól függetlenül kell kezelni: olyan elnevezési konvenciókat kell bevezetni, amikkel ez a probléma megszűnik. Tranzakciókezelés Az SQL Server és az Oracle adatbáziskezelő tranzakció kezelése alapvetően különbözik egymástól. Microsoft SQL Server 2000 alatt három féle módon lehet tranzakciót kezdeni: • explicit módon: egy BEGIN TRANSACTION utasítás kiadásával • implicit módon:
amennyiben a SET IMPLICIT TRANSACTIONS ON utasítással bekapcsoltuk ezt a módot, akkor az ezt követő utasítás automatikusan egy tranzakciót fog nyitni. Amikor az befejeződik, akkor a következő utasítás egy új tranzakciót kezd, és így tovább. • autocommit: ez az alapértelmezett mód az SQL Server-ben: minden egyes T-SQL utasítás vagy véglegesítésre kerül (commit), vagy visszagörgetődik (rollback). Explicit tranzakciók esetén a programozó határozza meg a tranzakció elejét és végét is, Transact-SQL scriptek esetén BEGIN TRANSACTION / COMMIT / ROLLBACK utasításokkal, ADO.NET esetén pedig az SqlConnection objektum BeginTransaction() illetve Commit() / Rollback() metódusaival. A BEGIN TRANSACTION utasítás 1-el növeli a @@TRANCOUNT változó értékét (alapesetben a változó 0), a COMMIT 1-el csökkenti, míg a ROLLBACK 0-ra állítja. Ez azért van 13 így, mert SQL Server alatt úgy lehet tranzakciókat egymásba ágyazni, hogy a
beágyazott tranzakciók esetében az adatbázis a COMMIT utasításokat figyelmen kívül hagyja, csak a legkülső COMMIT után lesznek a módosítások véglegesítve. A ROLLBACK utasítás viszont minden aktív tranzakciót visszagörget. Ezzel szemben Oracle adatbáziskezelő alatt egy tranzakció az első végrehajtható SQL utasítással kezdődik, és vagy akkor ér véget, amikor explicit módon kiadunk egy COMMIT illetve ROLLBACK utasítást, vagy implicit módon, pl.: egy DDL utasítás végrehajtásával, vagy az adatbázisból történő kilépéskor (disconnect) Léteznek még úgynevezett autonóm tranzakciók (autonomous transactions), amik teljesen függetlenek a "hívó" tranzakciótól. Például, ha egy tárolt eljárás az AUTONOMOUS TRANSACTION direktívával lett lefordítva, akkor, amikor meghívódik egy külső tranzakcióból, nem látja annak a még nem véglegesített adatait, illetve tőle függetlenül lesz véglegesítve vagy
visszagörgetve. Ez például naplózást megvalósító eljárások esetén lehet hasznos Kérdéses lehet, hogy ezek után egy begin tran. commit blokkot hogyan lehet PL/SQL-re fordítani. Sajnos a – diplomatikus – válasz az, hogy attól függ Amennyiben ez a legkülső blokk egy végrehajtás során, akkor a begin utasítást elhagyjuk, a commit-ot meg meghagyjuk. Ekkor közel azonos működést érhetünk el. Viszont, ha ez egy beágyazott tranzakció, azaz már a hívó eljárás megnyitott egy tranzakciót, akkor ezen két utasítás hatása mindösszesen annyi, hogy a @@TRANCOUNT változó értékét módosítják. Tehát a tranzakciókezelő utasítások működése a futási lánctól függ. Ezen kívül van még egy fontos különbség a két rendszer között: máshogy kezelik a zárolásokat. Az alapértelmezett izolációs szinten (READ COMMITTED) az SQL Server a SELECT utasítás hatására egy shared zárat helyez a táblára, míg az Oracle semmilyet se.
Gondoljuk végig, hogy ez mit jelent: egy SELECT tehát csak akkor tud lefutni SQL Server alatt, ha rá tud tenni egy shared zárat, azaz a táblán maximum shared zár van jelenleg (nincs exclusive zár). Ennek következtében a SELECT és az UPDATE utasítások fizikailag szerializáltan hajtódnak végre [16]. Ezzel szemben Oracle alatt egy SELECT utasítást nem blokkol semmi, és egy SELECT utasítás nem blokkol senkit (FOR UPDATE klauzula nélkül) [17]. Képzeljünk el egy olyan T-SQL tárolt eljárást, ami kiolvassa egy számla egyenlegét egy változóba, elszöszmötöl egy darabig, majd – ha van elég pénz az egyenlegen – csökkenti azt. Ennek az eljárásnak a kódja valahogy így nézhet ki: declare @balance money begin transaction select @balance = balance from tran balance where userid=20 14 -- some very serious calculation. if @balance > 1500 update tran balance set balance=balance-1500 where userid=20 commit transaction Mi itt a hiba? Ez a kód bizonyos
időzítés mellett helyesen működik SQL Server alatt, Oracle alatt viszont (alapértelmezett izolációs szinten) szinte sehogy se. Tegyük fel, hogy az 1.3 ábrán látható módon indul el két tranzakció 1.3 ábra Két tranzakció konkurrens végrehajtása SQL Server alatt az alábbi lépések fognak történni: • A SELECT kirak egy shared lock-ot a táblára • Az UPDATE kirak egy exclusive lock-ot a táblára • Indulna a 2. tranzakció, de a SELECT nem tud shared lock-ot kirakni, mert a másik tranzakció exclusive lock-ja még érvényben van • Az első tranzakció lefut, végrehajtódik a COMMIT utasítás • Elindul a 2. tranzakcióban levő SELECT utasítás is, és az első tranzakció által véglegesített értéket olvassa ki Ha az időzítésen variálunk egy picit, pl: feljebb „toljuk” a 2. tranzakció SELECT-jét az első tranzakció SELECT és UPDATE utasítása közé, akkor SQL Server-en is „nem várt” működést tapasztalhatunk: •
lefut az első SELECT • lefut a második tranzakció SELECT-je is, a shared lock miatt (a shared lock-ok egymással kompatibilisek) lefut az első UPDATE • a második UPDATE blokkolódik, egészen addig, amíg az első tranzakció nem fejeződik be. 15 Mi a helyzet Oracle alatt? A SELECT utasítás mindkét (bármilyen) esetben le fog futni, kiolvasva az utolsó elcommitált értéket. A fenti példakód esetében tehát a konkurens tranzakciók lehet, hogy úgy terhelik be az egyenleget, hogy már nincs is rajta fedezet. Azaz, ha automatikusan fordítjuk át T-SQL kódjainkat PL/SQL-re, akkor a fenti „kicsit” rossz kódból nagyon rossz kódot kapunk. Ebben a példában két lehetőség van a javításra, Oracle oldalon. Az egyik a SERIALIZABLE izolációs szint használata: ezen a szinten levő tranzakciók ha egy tábla olyan sorát szeretnék módosítani, amit egy másik – még el nem commitált – tranzakció már módosított, akkor hibát kapunk akkor,
amikor a másik tranzakció elengedi a zárait: Hiba a(z) 1. sorban: ORA-08177: ehhez a tranzakcióhoz nem lehet sorbarendezni a hozzáférést ORA-06512: a(z) helyen a(z) 6. sornál A másik megoldás a LOCK TABLE használata: még mielőtt bármit is olvasnánk a táblából, rárakunk egy zárat. Ezzel elérhetjük az SQL Server-éhez hasonló működést, azzal a különbséggel, hogy nem a SELECT utasítás fog blokkolódni, hanem az előtte lévő LOCK TABLE Adattípusok A támogatott adattípusokból eredő probléma súlyát a két gyártó egymástól eltérően ítéli meg. [15] szerint habár az SQL Server és az Oracle adattípusai különböznek egy kicsit, ennek igen kis hatása van a migrációra nézve. Ezzel szemben [3] azt írja, hogy a legtöbb Oracle adattípusnak nincs pontos SQL Server oldali megfelelője A továbbiakban ezeket a különbségeket veszem sorra. Karakteres adattípusok A karakteres adattípusok összehasonlítását az 1.2 táblázat mutatja
A táblázatban felsorolt típusoknak mindkét rendszer alatt megvan a Unicode-os megfelelője is (NCHAR, NVARCHAR.) A táblázat nem tartalmazza a már nem ajánlott típusokat (TEXT, NTEXT, LONG). A méretbeli különbségek az alkalmazástól függően okozhatnak gondot. Fontos még tudni, hogy Oracle alatt az, hogy egy karakteres adattípus n byte vagy n karakter méretű, az az NLS LENGTH SEMANTICS paramétertől függ. Dátum típusok A dátum adattípusok összehasonlítását az 1.3 táblázat mutatja Mindkét rendszer rendelkezik dátum és idő tárolására alkalmas adattípusokkal, viszont ezek pontossága, és a bennük tárolható adatok értéktartománya is különbözik. Az alkalmazási területtől függ, hogy 16 SQL Server CHAR fix hosszúságú, n byte-os típus, ahol 0 < n <= 8000 VARCHAR változó hosszúságú, n byte-os típus, ahol 0 < n <= 8000 VARCHAR(max) változó hosszúságú, maximum 231 − 1 byte méretű Oracle CHAR fix
hosszúságú, n byte-os/karakteres típus, ahol 0 < n <= 2000 VARCHAR2 változó hosszúságú, n byte-os/karakteres típus, ahol 0 < n <= 4000 CLOB változó hosszúságú, maximum 128 terabyte méretű 1.2 táblázat Karakteres adattípusok összehasonlítása Oracle és SQL Server alatt Forrás: [16], [17] ezeket a különbségeket figyelmen kívül hagyhatjuk-e, vagy kezelnünk kell őket (pl.: DATE helyett TIMESTAMP ha szükséges a pontosság, illetve CHECK CONSTRAINT-ek alkalmazása, ha szűkebb értéktartományra van szükség). Fontos látni azt, hogy a helyes döntést egy algoritmus itt nem tudja meghozni, ehhez a teljes rendszert átfogó elemzésre van szükség. SQL Server DATETIME Dátum és idő tárolására alkalmas típus, értéktartomány: 1753-01-01 – 9999-1231, pontosság: 3.33 ms SMALLDATETIME Dátum és idő tárolására alkalmas típus, értéktartomány: 1900-01-01 – 2079-0606, pontosság: perc Oracle DATE Dátum és idő
tárolására alkalmas típus, értéktartomány: I.e 4712-01-01 – 999912-31, pontosság: másodperc TIMESTAMP Dátum és idő tárolására alkalmas típus, értéktartomány: I.e 4712-01-01 – 999912-31, pontosság: másodperc 10−9 -ed része DATE 1.3 táblázat Dátum adattípusok összehasonlítása Oracle és SQL Server alatt Forrás: [16], [17] Numerikus típusok A numerikus adattípusok összehasonlítását az 1.4 táblázat mutatja Az eltérések itt is az értéktartományok különbségéből adódnak. Ha szükséges az azonos tartományok kikényszerítése, alkalmazhatunk CHECK CONSTRAINT-eket 17 SQL Server BIGINT −263 – 263 INT −231 – 231 SMALLINT −215 – 215 TINYINT 0 – 255 DECIMAL(p,s), NUMERIC(p,s) Fix pontosságú számok tárolására alkalmas típus, maximum −1038 + 1 – 1038 −1 MONEY −922, 337, 203, 685, 477.5808 – 922, 337, 203, 685, 477.5807 SMALLMONEY −214, 748.3648 – 214, 7483647 Oracle NUMBER(19) Maximum 19
számjegyből álló egész. NUMBER(10) Maximum 10 számjegyből álló egész. NUMBER(5) Maximum 5 számjegyből álló egész. NUMBER(3) Maximum 3 számjegyből álló egész. NUMBER(p,s) NUMBER(19,4) Maximum 19 egész és 4 tizedes jegyből álló szám NUMBER(6,4) Maximum 6 egész és 4 tizedes jegyből álló szám FLOAT(n) FLOAT(n) Lebegőpontos számok 4 vagy 8 byte-on A NUMBER altípusa, n pontossággal, 1-22 tárolva (n függvénye) byte-on tárolva REAL FLOAT(24) Ekvivalens a FLOAT(24)-el BIT NUMBER(1) 0 vagy 1 1 számjegyből álló egész. 1.4 táblázat Numerikus adattípusok összehasonlítása Oracle és SQL Server alatt Forrás: [16], [17] 18 LOB típusok A LOB típusok szolgálnak nagy méretű adatok tárolására. Ezek összehasonlítását mutatja az 15 táblázat SQL Server VARCHAR(max) Maximum 231 − 1 byte méretű NVARCHAR(max) Maximum 231 − 1 byte méretű, Unicode szöveg VARBINARY(max) Maximum 231 −1 byte méretű bináris adat Oracle
CLOB Maximum 128 terabyte méretű NCLOB Maximum 128 terabyte méretű Unicode szöveg BLOB Maximum 128 terabyte méretű bináris adat 1.5 táblázat LOB adattípusok összehasonlítása Oracle és SQL Server alatt Forrás: [16], [17] XML típus Az XML adattípus az SQL Server 2005-ös verziójában jelent meg. Az Oracle oldali megfelelője az XMLType. Típusok közti konverziók A típuskonverziókat felbonthatjuk explicit és implicit konverziókra. Explicit típuskonverzió az, amikor a programozó explicit kijelenti, hogy mit és mire szeretne konvertálni, implicit konverzió pedig az, amikor ezt a döntést ráhagyjuk az adatbáziskezelőre. Explicit konverziók SQL Server alatt az explicit típuskonverziót a CAST és CONVERT függvények valósítják meg[16]: CAST ( expression AS data type [ (length ) ]) CONVERT (data type [ ( length ) ] ,expression [ , style ]) A CONVERT függvény SQL Server specifikus, és nagyobb rugalmasságot biztosít dátumok és tört
számok konvertálásakor. A CAST pedig inkább ANSI-szerű, ezért többé-kevésbé ugyanolyan szintaxissal lehet használni a különböző adatbázis szerverek alatt Implicit konverziók Implicit konverzió például akkor történik, amikor két különböző típusú adatot hasonlítunk össze, vagy ha egy tábla adott oszlopába szúrunk be egy attól különböző típusú értéket. Az implicit konverzió a felhasználó elől rejtve marad A lehetséges implicit konverziókat mindkét gyártó mátrix formájában adja meg a dokumentációban: melyik típusról milyen típusra hajlandó automatikusan konvertálni. 19 Ami különbözik, az a konverzió módja, például: milyen formában megadott dátumot tartalmazó karaktersorozatból hajlandó dátum típusú értéket konvertálni. Az SQL Server elfogadja az ÉÉÉÉ-HH-NN, ÉÉÉÉ.HHNN, NN-HH-ÉÉ, NNHHÉÉ formátumok közül bármelyiket Oracle alatt viszont ez a formátum a v$NLS PARAMETERS táblában van
tárolva: select value from v$NLS PARAMETERS where parameter = ’NLS DATE FORMAT’ --------RR-MON-DD Habár implicit típuskonverzió alkalmazása több okból is helytelen (a kód nehezebben értelmezhető, nem feltétlen optimális az utasítások végrehajtása performancia szempontból, a különböző verziók során változhatnak a konverziós szabályok, stb.), mégis sokan alkalmazzák Ezért a migráció során figyelnünk kell arra is, hogy az esetleges implicit konverziókat vagy explicitté, vagy „megfelelő” implicitté tegyük. Visszatérési értékek SQL Server alatt egy tárolt eljárás háromféle módon képes adatokat visszaadni a hívója felé: • kimenő paraméterekkel: CREATE PROCEDURE sp demo (@something int OUT) • visszatérési értékkel: return @someValue • úgynevezett eredményhalmazokkal (result set-ekkel): egy T-SQL tárolt eljárás képes visszaadni egy tetszőleges SELECT utasítás által visszaadott eredményhalmazt Kimenő
paraméterekből és visszaadott eredményhalmazokból tetszőleges számú lehet. Egy példa ezen módok bemutatására: CREATE PROCEDURE sp demo (@something int out) AS begin set @something = 5 select 7 return 9 end 20 1.4 ábra Egy T-SQL tárolt eljárás lehetséges visszatérési értékei Az eljárást futtatva az 1.4 ábrán látható eredményt kapjuk Az első eredményhalmazt a tárolt eljárás adta vissza (SELECT 7), a második pedig az @i, @j változók tartalmát mutatja. Oracle adatbáziskezelő alatt viszont egy tárolt eljárás csak kimenő paraméterekkel képes adatokat visszaadni. Nincs sem visszatérési érték, sem eredményhalmaz Eredményhalmazokat kurzor referenciákkal (REF CURSOR vagy SYS REFCURSOR) lehet emulálni. Az OMWB és a SwisSQL is ezt a módszert követi: ha egy T-SQL tárolt eljárás eredményhalmazokat adna vissza, azt úgy fordítja át PL/SQL-re, hogy felvesz annyi darab kurzor referencia kimenő paramétert, ahány
eredményhalmazt az eljárás visszaad. Például, a fenti sp demo eljárás PL/SQLes változata így nézne ki: CREATE OR REPLACE FUNCTION sp demo ( v something OUT NUMBER, cv 1 IN OUT SYS REFCURSOR ) RETURN NUMBER AS BEGIN v something := 5; OPEN cv 1 FOR SELECT 7 FROM DUAL ; RETURN 9; END; 21 Az OMWB ilyen esetekben függvénnyé alakítja a tárolt eljárásunkat. Ez azért van, mert visszatérési értéke csak és kizárólag függvényeknek lehet PL/SQL-ben. Viszont egy függvény és egy tárolt eljárás között számos lényegi különbség van, amik miatt nem szerencsés csak úgy egyikből a másikat csinálni. Egy függvény nem végezhet adatmódosító (DML) utasításokat SQL utasításokban, más a hívás módja is (pl.: NET-ből) Csupán azért alakította a migráló eszköz függvénnyé, mert csak azoknak lehet visszatérési értéke. A SwisSQL-ben egy konfigurációs paraméter segítségével adhatjuk meg, hogy az ilyen eljárásokat alakítsa-e
függvénnyé, vagy vegyen fel egy RETURNPARAMETER nevű kimenő paramétert, ami a visszatérési értéket tartalmazza. Ökölszabályként kijelenthető, hogy ha vannak olyan T-SQL tárolt eljárásaink, amiknek van visszatérési értékük, akkor célszerű részletesen megvizsgálni, hogy hogyan alakítsuk át őket. Például felvehetünk kimenő paramétereket, vagy, ha eddig valójában függvényként használtuk az eljárást, akkor alakítsuk is át függvénnyé. Az eredményhalmazok kérdése viszont egyéb problémákat vet fel: a kimenő paramétereket az eljárás hívásakor legtöbbször explicit fel kell venni. Ha egy eljárás hív egy másikat, akkor a hívó eljárásban is deklarálni kell egy kurzor változót, amit paraméterül adunk a másik eljárásnak. Az olyan tárolt eljárások esetében, melyek több eredményhalmazt adnak vissza, több kimenő kurzor változót kell deklarálni. Felmerülhet a kérdés, hogy miért nem valamilyen kurzor
változókból álló kollekciót (tömböt) használunk. A válasz egyszerű: jelenleg nem támogatott: PLS-00990: Index Tables of Cursor Variables are disallowed PLS-00989: Cursor Variable in record, object, or collection is not supported by this release A második hibaüzenet kis bizakodásra adhat okot. Talán az Oracle adatbáziskezelő későbbi változataiban támogatva lesz ez a módszer is. 1.3 Összefoglalás A fejezetben az adatbázisok közötti migráció főbb problémáit mutattam be, a hangsúlyt az SQL Server és Oracle adatbáziskezelő közötti különbségek bemutatására helyezve. Az alábbi felsorolásban összegzem a problémás területeket: • Adattípusok közötti különbségek: habár alkothatunk olyan leképezéseket a két rendszerben elérhető adattípusok között, melyek az esetek jelentős részét lefedik, ezek helyessége mindig csak egy konkrét rendszer esetében igazolható a típusok pontosságának, értékkészletének
különbségei miatt. A migrációs eszközök ezt a leképezések testreszabásával segítik. 22 • Azonosító nevek, fenntartott kulcsszavak: mind SQL Server, mind Oracle alatt rendelkezésre áll a kulcs és fenntartott szavak listája. Viszont azokat az adatbázis-objektumokat, amiknek a neve a cél-rendszerben ezen kategóriákba tartozik, nem célszerű automatizáltan átnevezni, ezeket a módosításokat az egész rendszeren végig kell vezetni, beleértve az adatbázishoz kapcsolódó kliens-alkalmazásokat is. Emiatt a pusztán adatbázis-migrációt segítő eszközök erre sem képesek megoldást nyújtani. • Tranzakciókezelés: a két rendszerben a tranzakciókezelés alapvetően különbözik egymástól. SQL Server alatt a tranzakciót vezérlő utasítások jelentése az egymásba ágyazott tranzakciók koncepciója miatt a futási lánc függvénye, ezért ezek automatikus fordítása nem megvalósítható. Ezen felül a táblák illetve sorok zárolási
mechanizmusa is különbözik (lásd: SELECT utasításnál a shared zárak esetét) Javaslom, hogy a tranzakciókezelés a célrendszer sajátosságait figyelembe véve mindig kerüljön manuális felülvizsgálatra. • Visszatérési értékek, eredményhalmazok: a tárolt eljárások által visszaadott eredményhalmazok hatékonyan szimulálhatóak a kimenő kurzor-változók segítségével. Ez egy jól automatizálható, fordító-programok által elvégezhető feladat. Viszont figyelembe kell venni, hogy ez az átalakítás a kliens-alkalmazások módosítását is igényli. A visszatérési értékek szimulálására nincs mód, szintaktikailag a tárolt eljárás függvénnyé alakítása jelenthet (rész-)megoldást. Ezen eseteket egyedileg kell elbírálni • Egyéb nyelvi különbségek: ezalatt azokat a nyelvi elemeket értem, amik hasonló formában léteznek mindkét rendszerben, közöttük meghatározható transzformáció. A következő fejezetben bemutatott
fordító program erre a problémára kíván megoldást nyújtani 23 2. fejezet Fordító tervezése Az előző fejezetben bemutattam a Transact-SQL és a PL/SQL nyelvek közötti főbb különbségeket illetve hasonlóságokat. Bemutattam három szoftvert is, amik képesek – többé-kevésbé – helyes PL/SQL kódot előállítani T-SQL kódból. Habár mindkét szoftvernek vannak hibái, limitációi, a migrációs folyamatban az adatbázis-objektumok, ezen belül is a tárolt programok átfordítását képesek – az ismertetett korlátok között – automatizálni, ami egy nagyobb rendszer migrációjakor hatalmas előny. Ebben a fejezetben egy saját fordítót fogok tervezni. A cél nem az SQL Developer Migration Workbench-nél vagy a SwisSQL-nél jobb szoftver készítése, hiszen ezek sokkal több funkciót ellátnak a fordításnál, például: adatbázis-objektumok kódjának kiolvasása az adatbázisból (Capture), szintaxis-kiemelés. Egy komplex migrációs
szoftver tervezése, implementálása meghaladja ezen dokumentum korlátait Ezért én kifejezetten a fordításra fogok koncentrálni: az elkészített program a bemenetként kapott T-SQL kódból PL/SQL kódot fog előállítani kimenetként. Más funkciója nem lesz A fejezetben először definiálom a formális nyelvi elemzés használt alapfogalmait, majd vázlatosan bemutatom a fordító készítéséhez használt keretrendszert: az ANTLR-t1 . Az ANTLR is hagyományos értelemben egy fordító: a bemenetként kapott nyelvtanból programkódot állít elő. Készíttethetünk vele nyelvi elemzőket2 , interpreter-eket, illetve fordítókat. A fejezet második része a funkcionális terv: részletesen bemutatom a rendszer célját, komponenseit, specifikálom, hogy mely utasításokat kell, hogy lefordítsa a fordító, és melyeket nem (pl.: SQL Server specifikus rendszer-szintű tárolt eljárás) 1 http://www.antlrorg egy nyelvi elemző (parser) a levezetési fa
előállításán keresztül eldönti egy mondatról, hogy az adott nyelv része-e 2 24 2.1 Formális nyelvi elemzés A nyelveket formálisan nyelvtanok segítségével írhatjuk le. Nyelvtanok segítségével a nyelv mondatai levezethetőek, generálhatóak.[1] A gyakorlatban a nyelvtanokat mégis arra használjuk, hogy eldöntsük, egy mondat eleme-e az adott nyelvnek A nyelvtanokat egy négyes határozza meg: G = (N, Σ, P, S) ahol N a nyelvtani szimbólumok véges halmaza, Σ a nyelv karakterkészlete, alfabetája, P a levezetési, vagy másnéven helyettesítési szabályok összessége, S pedig a mondatszimbólum: a kályha – innen indul a levezetés. Egy levezetés mondatszerű formáknak nevezett jelsorozatok egymásutánja. A mondatszerű forma abban különbözik a mondattól, hogy abban nem csak a nyelv szimbólumai, vagyis a Σ elemei, hanem grammatikai szimbólumok is szerepelhetnek. A levezetés a mondatszimbólumtól indul: innen kezdve a helyettesítési
szabályok alkalmazásával juthatunk el a nyelv egy mondatához. A fentiek illusztrálásához képzeljünk el egy olyan nyelvet, aminek a karakterkészlete: Σ = (a, b), és csak két szava lehet: aa, bb. Az ezt leíró nyelvtan például: SA SB A aa B bb Az S mondatszimbólumtól indulva az A vagy B mondatszerű formán keresztül juthatunk el a nyelv szavaiig. A nyelvtanokat meghatározó helyettesítési szabályok (vagy másnéven levezetési szabályok) bonyolultsága alapján Chomsky a nyelveket osztályokba sorolta.[1] 3-as nyelvosztály A 3-as nyelvosztály nyelvtanaiban csak kétféle levezetési szabálytípus engedélyezett: A a illetve A aB ahol a nemterminális szimbólumokat nagy, a terminális szimbólumokat kis betűvel jelöljük. Az ilyen alakú nyelvtanokat reguláris (jobbreguláris) nyelvtanoknak nevezzük. Ezek a nyelvtanok generálják a reguláris nyelveket 25 2-es nyelvosztály A 2-es nyelvosztály helyettesítési szabályainak alakja: Aα ahol
α tetszőleges terminális és nemterminális szimbólumokat tartalmazható jelsorozat. Az ilyen alakú szabályokat úgy lehet értelmezni, hogy az A nemterminális szimbólum a környezetétől függetlenül bármikor helyettesíthető az α jelsorozattal. Ezért ezen nyelvosztály nyelveit környezetfüggetlen, Context Free (CF) nyelveknek nevezik. 1-es nyelvosztály Az 1-es nyelvosztály helyettesítési szabályainak alakja: βAγ βαγ azaz a A α szabály csakis a β − γ környezetben alkalmazható. Ezért ezeket a nyelveket környezetfüggő, Context Sensitive (CS) nyelveknek nevezik. 0-ás nyelvosztály A 0-ás nyelvosztályban alkalmazható szabályokra nézve nincsen korlátozás. Fordítók Egy fordító általánosságban nem más, mint egy olyan program, ami valamilyen be- meneti jelsorozatra valamilyen kimeneti jelsorozattal válaszol.[5] A különböző bemenetekhez általában különböző kimeneteket kell rendelni, azaz a fordítónak meg kell tudnia
különböztetni egymástól az ilyen mondatokat, fel kell őket ismernie. A felismerés két fázisra bontható: lexikai és szintaktikai elemzés (lexing, parsing). A lexikai elemzés a bemeneti jelsorozatot tokenekre bontja. A szintaktikus elemző bemenetként már nem karakterfolyamokat, hanem tokeneket ( szavakat) kap, és ezekből a tokenekből próbálja meg előállítani a levezetési fát. Ha sikerül, akkor az adott mondat a nyelv része A szintaktikus elemzők általában interpreterek vagy fordítók részei, a feladatuk ebben az esetben a bemeneti tokenekből valamilyen belső, köztes adatstruktúra felépítése. Ez a struktúra általában egy absztrakt szintaxis fa (AST).[5] A fordítás utolsó fázisa a kimenet előállítása. Ez egyszerűbb esetekben történhet a szintaktikus elemző kódjába akciók illesztésével (pl: ha ’+’-t látsz, írj ki ’-’-t, és fordítva), összetettebb esetekben pedig az AST bejárása közben lépésről
lépésre történik a kimenet előállítása, amit egy utolsó, emitter fázis bocsát ki magából. A kimenet előállítása történhet kézzel (pl.: print utasításokkal), vagy sablonok segítségével 26 2.11 Az ANTLR elemző-generátor bemutatása Az ANTLR egy olyan nyelvi elemző generátor, ami képes lexikai, szintaktikai elemzőket készíteni, absztrakt szintaxis fákat építeni, illetve felhasználói akciókat (beágyazott programkódokat) végrehajtani.[6] A bemenetként kapott nyelvtanból az ANTLR egy rekurzív-leszálló LL(*) elemzőt készít: olyan LL(k) elemzőt, ami tetszőlegesen nagy előre tekintést tesz lehetővé.[5] Ezen túlmenően szemantikus és szintaktikus predikátumok segítségével lehetővé teszi számos környezetfüggő nyelv elemzését is.[6] A szemantikus predikátumokkal azt ellenőrízhetjük, hogy egy levezetési szabályt alkalmazhatunk-e az adott környezetben. Egy szemantikus predikátum tetszőleges logikai
kifejezés lehet Például, a levezetés attól függhet, hogy egy adott azonosító korábban miként volt deklarálva. Ebben az esetben egy szemantikus predikátummal belenézhetünk egy szimbólum-táblába, és eldönthetjük, hogy olyan típusú-e, mint amit keresünk. A szintaktikus predikátumok környezetfüggetlen nyelvtanok.[6] Segítségükkel sorrendet állíthatunk fel a többértelmű alternatívák között Az ANTLR megpróbálja a szintaktikus predikátumban szereplő nyelvtannak megfelelő levezetést előállítani Ha sikerül, akkor az adott alternatíva érvényes, ha nem, akkor a következő alternatívával próbálkozik Az LL(*) elemző az alternatívák között egy véges automata (DFA – Deterministic Finite Automaton) segítségével dönt. Az ANTLR EBNF (Extended Backus-Naur Form) nyelvtanokat támogat[5], azaz támogatja a x+ (1 vagy többszöri ismétlődés), x* (0 vagy többszöri ismétlődés), x? (0 vagy egyszeri előfordulás) posztfix
operátorokat, ahol az x tetszőleges nyelvi szimbólum, illetve a zárójelezést. Ezen kívül ha az elemzővel egy szintaxis fát akarunk készíteni, lehetőség van úgynevezett fa-építő operátorok, vagy szabályok alkalmazására: • Először meg kell adnunk, hogy kimenetként egy AST-t akarunk előállítani: options { output=AST; } • A fa megépítéséhez használhatunk posztfix operátorokat: subTableSource : dbObject^ (alias)? | ’(’! derivedTable^ ’)’! (alias)? ; 27 A ˆ operátorral mondhatjuk meg, hogy melyik elem legyen a gyökér, és a ! operátorral, hogy mely elemeket szeretnénk kihagyni. A fenti példában a dbObject lesz a gyökér-elem, aminek lehet egy opcionális alias gyereke. • vagy szabályokat: delete statement : DELETE FROM? dbObject fromClause? whereClause? -> ^(DELETE ^(FROM dbObject) ^(FROM fromClause)? ^(WHERE whereClause)?) ; A -> operátor után ˆ (ROOT child1 child2 . childn) formában adhatjuk meg az elemeket,
ahol ROOT a gyökér, és utána tetszőleges számú gyerek szerepelhet. 2.1 ábra Absztrakt szintaxis fa egy DELETE utasításból A 2.1 ábrán egy AST-re láthatunk példát Az elemzőnek az alábbi utasítás volt a bemenete: DELETE FROM MyTable WHERE col1 > 50 Mint a példából is látható, egy szabályban egyszerre több gyökérelemet is megadhatunk, ezzel további szinteket képezhetünk a fában. Egy AST-vel szemben az alábbi követelményeket állíthatjuk[5]: • Tartalmazza a jelentéssel bíró tokeneket (és csak azokat), azaz szűrjük ki a szintaktikai édesítőszereket. 28 • Kétdimenziós formában kódolja a mondat nyelvtani szerkezetét. • Legyen könnyen értelmezhető, illetve könnyen bejárható egy program számára. Ezeket a követelményeket a nyelvtanok készítésekor általános irányelvekként kezelhetjük. Előfordulhatnak olyan esetek, amikor a mondat értelme csak implicit szerepel a szövegben, a bemenetből nem tudunk olyan
tokent kiválasztani, ami jó gyökér elem lenne. Például egy C-szerű nyelvben egy változó deklarációja így nézhet ki: int i;. Ennek a mondatnak a jelentése: „deklarálj egy int típusú, i nevű változót”. Ilyenkor felvehetünk absztrakt, vagy másnéven képzeletbeli csomópontokat, amik értelmesen kódolják a jelentést. Ezek a képzeletbeli csomópontok olyan tokenek, melyekhez nem tartozik bemenet, hanem a levezetés közben születtek. Az ANTLR segítségével tehát viszonylag kényelmesen készíthetünk nyelvi elemzőket: megadjuk a nyelvtant, melyet kiegészíthetünk tetszőleges programkóddal, hogy akár környezetfüggő nyelveket is képes legyen leírni, majd az ANTLR elkészíti a nyelvtanhoz tartozó elemzőt, mely képes eldönteni egy bemeneti karaktersorozatról, hogy a nyelv része-e, és ha igen, akkor esetleg elkészíti a hozzá tartozó AST-t. Ha fordítót szeretnénk készíteni, akkor a feladat ezen AST bejárása, és
transzformálása. Egy AST bejárásához készíthetnénk saját algoritmusokat, de ez fölösleges: egy AST is tekinthető egy nyelvi elemző bemeneteként, azaz az ANTLR-t használhatjuk arra is, hogy eldöntsük, egy AST eleme-e egy fa-nyelvtannak (tree grammar) vagy sem. Mindösszesen annyit kell tennünk, hogy a nyelvtan fejlécében megadjuk, hogy a bemeneti tokeneket nem a lexer szabályaink állítják elő, hanem azokat vegye a parser kimenetéből: options { tokenVocab=TSQL; } ahol a parser nyelvtan neve TSQL. A parser kimenete egy AST, melyet fa-készítő operátorokkal, vagy szabályokkal készíthetünk el. Egy ilyen nyelvtant viszonylag egyszerűen alakíthatunk át fa-nyelvtanná: a szabály jobb oldalán levő kifejezést megtartjuk, a bal oldalán levőt pedig egyszerűen eldobjuk: argumentList: argumentItem (’,’ argumentItem)* -> ^(ARGS argumentItem) ; a fenti szabály egy képzeletbeli ARGS token gyökérrel rendelkező fát készít, melynek
tetszőlegesen sok argumentItem gyereke lehet. A bemenetben az elemek vesszővel vannak elválasztva Ebből a szabályból a fa nyelvtanban csupán a jobb oldalt tartjuk meg: 29 argumentList: ^(ARGS argumentItem*) ; Egy AST bejárása során két lehetőségünk van a fordításra: illeszthetünk kódrészleteket a szabályok közé, melyek valamilyen kimenetet generálnak (pl.: print utasítások), vagy használhatunk egy sablon-motort Az ANTLR sablon-készítő szabályokkal (template construction rules) integrálható a StringTemplate nevű sablon-motorral, mely használatával elkülöníthetjük a logikát a megjelenítéstől és így átláthatóbb, sőt, akár újra felhasználható kódot kapunk.[5] 2.12 A StringTemplate sablon-motor bemutatása Egy sablon alapvetően nem más, mint egy dokumentum „lyukakkal”[8]. Ezeket a lyukakat – melyeket attribútumoknak is nevezhetünk – úgy lehet adatokkal kitölteni, mintha paramétereket adnánk át a
sablonnak[5]. A Model-View-Controller (MVC) paradigmának megfelelően a sablon tekinthető a nézetnek, a modell az attribútumokat megalkotó kód, a kontroller pedig a sablont attribútumokkal kitöltő kód[8]. A StringTemplate szigorúan megköveteli a modell és a nézet elkülönítését. Az elkülönítés úgy valósítható meg, ha a modell és a nézet is egységbe zárt. Ezen fogalmak formális definícióját Terence Parr [7]-ben adja meg: Definíció Egységbe zárt modell: a modell egységbe zárt, ha minden adatfüggő számítás és logika a modellben valósul meg. Definíció Egységbe zárt nézet: a nézet egységbe zárt, ha minden megjelenítési és elrendezési információt a nézet tartalmaz. Definíció Szigorú elkülönítés: a modell és a nézet szigorúan elkülönített, ha mindkettő egységbe zárt, továbbá a nézet nem képes módosítani a modellt, és a nézet nem feltételez semmit a modell adatainak típusáról. A fentieknek
megfelelően a megjelenítésre vonatkozó információkat a sablonban kell elhelyezni, ahol már nem lehet az attribútumokon számításokat, vagy logikai vizsgálatokat végezni, leszámítva egy attribútum létezésének ellenőrzését. A sablon függvényekből áll, melyek attribútumokat attribútumokra képeznek le az alábbi formában: F (a1 , a2 , ., am ) ::= t0 e0 ti ei ti+1 tnt ene ahol ai attribútum, ti kimeneti szöveg, ei pedig kifejezés. A kifejezéseket az különbözteti meg a kimeneti szövegektől, hogy < > jelek között szerepelnek[8]. 30 A kifejezéseknek négy típusa megengedett: • hivatkozás egy attribútumra: <a1> • egy sablon meghívása: <F1()> • feltételes kifejezés: <if(a1)>teszt szöveg<endif> • sablon alkalmazás: <vars:decl()> – értsd: a vars attribútum (vagy attribútum-lista) minden értékére alkalmazd a decl() sablont. Az ANTLR és a StringTemplate Az ANTLR – a fa-építő
szabályokhoz hasonlóan – tartalmaz sablon-készítő szabályokat is, melyek segítségével az elemzés közben formázott kimenetet állíthatunk elő. Ezen szabályok általános formája: ruleN : ^(ROOT childItem) -> template( attrName = {<<value>>}, . ) ; Ezen kívül a beágyazott kódrészletekben is használhatunk pár sablon-készítő operátort[5]: • %foo(a={},b={}, . ) – A foo nevű sablonból készít egy példányt, beállítva az a, b attribútumok értékét • %({«stringExpr»})(a={}, .) – Az előzőhöz hasonló, azt leszámítva, hogy a sablon nevét a stringExpr kifejezés értéke tartalmazza. • %x.y = «z» – Beállítja az x sablon y attribútumát z-re • %{«stringExpr»} – Egy névtelen sablont készít a stringExpr szövegből. Ezen a ponton minden adott egy fordító elkészítéséhez. A lépéseket illetve komponenseket a 2.2 ábrán foglalom össze Az ábrán a bemeneti karaktersorozatból a második komponens, a
Lexer tokeneket készít, melyek a Parser bemeneteként szolgálnak. A parser ezekből a tokenekből eldönti, hogy a bemeneti karaktersorozat eleme-e a nyelvnek, vagy sem A levezetés közben a releváns elemekből szintaxis-fát (AST) készít, ami a Treewalker bemeneteként fog szolgálni. A Treewalker a bejárás közben StringTemplate sablon-hívásokkal, vagy szabályok közé beágyazott kód futtatásával formázott kimenetet készít elő. Ezt a kimenetet a Translator alakítja szöveggé A 2.2 ábrán kék színnel jelzett komponens, a Translator saját készítésű, a zöld színűeket az ANTLR generálta (a nyelvtanok alapján), míg a sárga a StringTemplate osztálykönyvtár. 31 2.2 ábra A fordítás folyamata ANTLR+StringTemplate-el 2.2 Funkcionális terv Ebben a részben specifikálom, hogy az elkészített Transact-SQL PL/SQL fordítónak mi a célja, milyen funkciókkal rendelkezik, illetve ezeket hogyan valósítja meg. Ezen a ponton is szeretném
kihangsúlyozni, hogy nem célom teljes értékű fordítót készíteni, ugyanis – ahogy ezt az 1. fejezetben részletesen megindokoltam – ilyen nem létezhet a két adatbáziskezelő rendszer közötti elvi különbségek miatt. 2.21 A feladat leírása A feladat a bemenetként kapott T-SQL tárolt eljárás lehető legjobb PL/SQL fordításának elkészítése. A rendszer a bemenetet szöveges formában kapja, azaz nem képes azt az adatbázisból venni A kimenetet is ehhez hasonlóan szöveges formában állítja elő, tehát nem rendelkezik kapcsolattal sem az SQL Server, sem az Oracle adatbáziskezelő felé. Számos olyan szituáció képzelhető el, amikor egy nyelvi elem fordításának nincs értelme: az SQL Server rendelkezik számos rendszer-szintű tárolt eljárással, melyek speciális (adminisztratív, biztonsági, automatizálási, stb.) funkciókat látnak el3 Ezek egy részének van Oracle oldali megfelelője, más részének vagy nincs, vagy teljesen
más formában áll rendelkezésre. Ezek az eljárások – jó esetben – nem képezik az üzleti logika részét, hanem az SQL Server szerves részének tekinthetők, ezért a továbbiakban nem foglalkozok velük, minden tárolt eljárás-hívást egységesen fogok kezelni: feltételezem, hogy az eljárás létezik, és a felhasználó által készített 3 SQL Server 2005 alatt a rendszer-szintű tárolt eljárások teljes listájáért lásd: http://msdn.microsoftcom/enus/library/ms187961(SQL90)aspx 32 eljárásról van szó. A fordítási nehézségekre másik példa lehet a dinamikus SQL utasítások esete: az SQL Server is megengedi futásidőben összeállított utasítások végrehajtását. Azaz, lehetőségünk van arra, hogy egy T-SQL utasítást szöveges formában, akár paraméterként, akár egy adatbázistáblából véve adjunk meg. Mivel az utasítás csak futásidőben áll rendelkezésre, ezért az ilyen konstrukciókat nem lehet átfordítani. A fentiek
miatt én az alapvető T-SQL szerkezetekre fogok koncentrálni: DML utasítások (SELECT, INSERT, UPDATE, DELETE), vezérlő szerkezetek (IF, WHILE), változó-deklarációk, tárolt eljárás-hívások, hibakezelés (RAISERROR), valamint a tárolt eljárásokból visszaadott eredményhalmazok kezelése. 2.22 A rendszer célja A rendszer célja, hogy SQL Server Oracle adatbázis migráció során a tárolt eljárások fordításához hatékony segédeszköz legyen. Az eddigiek során azt indokoltam meg, hogy miért nem lehet teljes-értékű fordítót készíteni, most viszont azt fogom, hogy miért van értelme egy részleges megoldásnak is. Egy adatbázis migráció során általános esetben akár többezer sor T-SQL kódot is át kell fordítani. Egy fordító ezt kétféleképpen tudja segíteni: • Ötleteket adhat: bemenetként megadhatunk teszt-eseteket, melyek fordításából tanulhatunk: akár azt, hogy az adott konstrukciót nem lehet átfordítani, vagy pedig
kaphatunk egy lehetséges megoldást. • Részfeladatokat automatizálhat: ha a fordítandó kódunk többszáz helyen tartalmaz olyan UPDATE utasításokat, amiknek van második FROM klauzulájuk, akkor igen unalmas, monoton feladat ezeket manuálisan MERGE utasításokká alakítani. Viszont egy fordító képes ezt elvégezni helyettünk. 2.23 Komponensek leírása A rendszer alapvetően három komponensből áll, melyek fölött egy egyszerű grafikus kezelői felület biztosítja a felhasználói interfészt. A komponensek az ANTLR segítségével automatikus kódgenerálással készültek a nyelvtanok (parser és treewalker) alapján. Célnyelvként a Java-t választottam4 , ennek megfelelően a nyelvtanokba beágyazott kódrészletek is Java nyelven íródtak. 4 Az ANTLR jelenleg C, C#, Java, Python, ActionScript, JavaScript célnyelveket támogat. Forrás: http://www.antlrorg/wiki/display/ANTLR3/Code+Generation+Targets 33 A rendszer szerves részét képezi a
StringTemplate sablon-file, melynek elkészítéséhez nagy segítségemre voltak az Oracle SQL Developer StringTemplate sablonjai. A következőkben a komponenseket, illetve azok generálásához szükséges nyelvtanok főbb tulajdonságait mutatom be. Lexer A Lexer, vagy lexikai elemző feladata egy karakterfolyam tokenekre bontása. Például, a SELECT * FROM myTable bemenetből 4 tokent hoz létre, a szóközöket eldobja: SELECT, STAR, FROM, myTable. A Lexer szabályok nevét nagy kezdőbetűvel kell kezdeni: SELECT : ’SELECT’ ; Az ANTLR (pontosabban a generált Java kód) megkülönbözteti egymástól a kis és nagybetűket, viszont sem a T-SQL, sem a PL/SQL nem, ezért a Lexerbe bele kell építeni, hogy ne tegyen különbséget. Ehhez használhatjuk Jim Idle által készített ANTLRNoCaseFileStream osztályt, ami felüldefiniálja az LA (lookahead – előre tekintés) metódust5 . Ezután a Lexer szabályokban a szavakat csupa nagybetűvel kell megadni A Lexer
az egy- illetve többsoros megjegyzéseket figyelmen kívül hagyja. Ezt az ANTLR úgynevezett csatornák segítségével támogatja: a szabályban megadhatjuk, hogy az adott tokent rejtse el a Parser elől: SL COMMENT : ’--’ ~(’ ’|’ ’)* ’ ’? ’ ’ {$channel=HIDDEN;} ; Ezek a tokenek nem vesznek el, pusztán a rejtett csatornára kerülnek, amihez – ha akarunk – hozzáférhetünk. Parser A Parser feladata, hogy a bemeneti tokenekből eldöntse, egy adott mondat része-e a nyelvnek, vagy sem, illetve elkészítse a mondathoz tartozó absztrakt szintaxis-fát. A Parser szabályok nevét kis kezdőbetűvel kell kezdeni. Fa építésére egyaránt használok operátorokat és fa-készítő szabályokat. 5 Forráskódja elérhető: http://www.antlrorg/wiki/pages/viewpageaction?pageId=1782 34 A nyelvtan nem ellenőrzi a bemenet szemantikai helyességét (pl.: egy változó értékadásnál nem ellenőrzi, hogy a változó deklarálva lett-e, illetve
megfelelő-e a típusa), pusztán szintaktikai ellenőrzést végez. Megjegyzendő, hogy ezek nagyrészét meg lehetne valósítani szemantikus predikátumokkal, illetve beágyazott kódrészletekkel, viszont a fordítás szempontjából nincs értelme: adatbázis migráció során feltételezzük, hogy a meglévő kódbázis működik. A mondatszimbólum a start rule, innen indul a levezetés. Ezután utasítások sorozatának kell következnie, amiből kimenetként egy AST-t készít Például az alábbi kódrészletből: CREATE PROC sp testbed ( @foo INT ) AS SELECT col1 FROM myTab WHERE col2 = @foo a 2.3 ábrán látható szintaxis-fa készül 2.3 ábra Egy T-SQL tárolt eljáráshoz tartozó absztrakt szintaxis fa Az ábráról könnyen leolvasható az eljárás szerkezete: egy paraméterrel rendelkező tárolt eljárás létrehozása, ami összesen egy utasításból álló blokkot tartalmaz. Az utasítás egy SELECT utasítás, ami három klauzulából áll: select,
from és where. Érdemes még felfigyelni arra, hogy a változó (pontosabban paraméter) neve foo, nincs az elején a változók jelölésére szolgáló ’@’ 35 karakter. Ez az információ viszont nem veszik el, a változónév gyökér-eleme a szintaxisfában a VARIABLE csomópont, ami annak megjelölésére szolgál, hogy valójában egy változóról van szó, nem pedig adatbázis objektumról (pl.: oszlopról) Treewalker A Treewalker feladata, hogy a bemeneti szintaxis-fát bejárva kimenetet állítson elő. A Treewalkerhez szükséges nyelvtant legkönnyebben a Parser nyelvtanból állíthatjuk elő: elhagyjuk a fában nem szereplő tokeneket, illetve a fa-építő szabályok bal oldalait. A kimenet előállításához sablon-készítő szabályokat és beágyazott kódrészleteket használok. Mivel ez a komponens készíti elő a kimenetet, itt szükséges a transzformációkat elvégezni A 2.3 ábrán látható példán keresztül részletesen bemutatom, hogy
erre milyen lehetőségek vannak A példa eljárás egyszerűsége ellenére egy fontos problémára mutat rá: egy T-SQL tárolt eljárás képes eredményhalmazokat visszaadni, míg a PL/SQL eljárások nem. Tehát ezt a konstrukciót valahogy szimulálni kell Erre egy lehetséges módszer kimeneti kurzorváltozók használata Viszont nem elég pusztán a SELECT utasításhoz tartozó részfát kozmetikázni, deklarálni is kell a kimeneti változót az eljárás fejlécében. Viszont az a tény, hogy szerepel az eljárás-blokkban valahol egy SELECT utasítás, még nem elég, ugyanis lehet, hogy egy változónak történő értékadást tartalmaz. Összefoglalva: akkor kell kimeneti kurzorváltozót deklarálni, ha tárolt eljárás blokkjában vagyunk, és nem változónak történő értékadásról van szó. A SELECT utasításhoz tartozó szabály a következő: select statement scope { boolean resultSet; String cursorName; } : ^(SELECTSTMT query expression orderbyClause?)
-> {$select statement::resultSet}? selectStatement(queryExpression={$query expression.st}, orderList={$orderbyClause.st}, cursor={$select statement::cursorName}) -> selectStatement(queryExpression={$query expression.st}, orderList={$orderbyClause.st}) ; Azaz a levezetési szabályhoz tartozik két változó: az egyik azt tartalmazza, hogy van-e 36 visszaadott eredményhalmaz, vagy sem, míg a másik az eredményhalmaz visszaadásához használandó kurzor nevét tartalmazza. Az előbbi változótól függ (szemantikus predikátum), hogy milyen attribútumokkal lesz meghívva a sablon. Természetesen ezen a szinten még nem derül ki, hogy melyik alternatíva fog érvényesülni, ezt a levezetési fában lentebb levő szabályok döntik majd el. Amikor kiderül, hogy fel kell venni egy új kimenő paramétert a kurzorváltozónak, akkor egyrészt lefut egy Java eljárás, ami generál egy azonosítót, másrészt az eljárás paraméter-listájához hozzáfűzésre kerül
az új paraméter. Ezt úgy lehet megvalósítani, hogy a paraméter-lista szó szerint egy (Java) lista, amihez az add metódussal lehet új elemeket hozzáfűzni: create proc scope { List parameters; } A fordító kimenetként végül az alábbi kódot állítja elő: CREATE OR REPLACE PROCEDURE sp testbed ( v foo NUMBER , cv 1 IN OUT SYS REFCURSOR ) AS BEGIN OPEN cv 1 FOR SELECT col1 FROM myTab WHERE col2 = v foo; END; A fenti példából jól látszódik, hogy a bejárás során gyakorlatilag bármelyik részfába beszúrhatunk vagy elhagyhatunk tetszőleges elemeket. Ezért – ha definiálható, hogy milyen transzformációkat kell elvégezni – a fordítást meg lehet valósítani A gyakorlatban a problémát az jelenti, hogy ezeket a transzformációkat nem mindig lehet meghatározni, mert egy adott nyelvi konstrukciónak nem létezik ekvivalens párja, és a pontos működést elvi eltérésekből adódóan szimulálni sem lehet. A következőekben az elvégezhető
transzformációkat fogom specifikálni Ezeket [16], [18], [12] alapján dolgoztam ki. 37 SQL Server Oracle FROM klauzula opcionális, ha a SELECT A FROM klauzula kötelező. lista nem tartalmaz oszlopneveket Ha hiányzik, szimulálni kell a DUAL táblából való lekérdezéssel: SELECT 1 FROM DUAL A DUAL tábla egy egy oszlopból és egy sorból álló tábla. Oszlop aliasoknál megengedett szintaxis: Az oszlop aliasok szintaxisa: alias1 = column1 column1 [AS] alias1 Létezik INTO klauzula: Ennek az utasításnak nem lehet autoSELECT <select list> INTO matikusan elkészíteni a szemantikus ek<new table> vivalensét! Az adatokat táblába beszúró részt lehet szimulálni az INSERT INTO.SELECT utasításokkal, de a tábla létrehozását nem lehet megoldani a kifejezések futásidejű kiértékelése nélkül! 2.1 táblázat A SELECT utasításnál szükséges transzformációk SQL Server Az INTO kulcsszó opcionális Oracle Az INTO kulcsszó kötelező. Ha
hiányzik, be kell szúrni. 2.2 táblázat Az INSERT utasításnál szükséges transzformációk SELECT A SELECT utasítással kapcsolatos transzformációkat a 2.1 táblázat tartalmazza A táblázatban utolsóként említett SELECT. INTO problémát egy fordító nem tudja teljes értékűen megoldani. Ezért én azt a kompromisszumos megoldást választottam, hogy feltételezem, a felhasználó gondoskodik a cél-tábla létrehozásáról INSERT Az INSERT utasítással kapcsolatos transzformácókat a 2.2 táblázat tartalmazza DELETE A DELETE utasítással kapcsolatos transzformációkat a 2.3 táblázat tartalmazza UPDATE Az UPDATE utasítással kapcsolatos transzformációkat a 2.4 táblázat tartalmazza Figyeljük meg, hogy az UPDATE utasítás esetében szükség lehet egy másik utasítás használata: a MERGE utasítás képes ellátni ugyanazt a funkciót, mint az UPDATE. FROM (és még sokkal többet is). Érdekesség még, hogy annak függvényében, hogy
hány forrás-táblát használunk az UPDATE utasításban, más és más transzformációt célszerű alkalmazni. Megjegyzendő, hogy az SQL Server 2008-as verziótól kezdve már támogatja a MERGE utasítást is, ezért ha az ANSI szabványt szeretnénk követni, programjainkban használjuk inkább azt az UPDATE. FROM helyett 38 SQL Server Második FROM klauzula: törlési feltétel megadása más táblák adatai alapján: DELETE FROM <table name> FROM <table source> WHERE <conditions> Oracle Második FROM klauzula esetén egy lehetséges transzformáció: DELETE FROM <table name> WHERE ROWID IN (SELECT <table name>.ROWID FROM <table source> WHERE <conditions>) 2.3 táblázat A DELETE utasításnál szükséges transzformációk SQL Server FROM klauzula: adatok/logikai feltétel egyéb táblákból: UPDATE <table name> SET <setters> FROM <table source> WHERE <condition> Oracle FROM klauzula helyett MERGE
utasítás: ha két tábla szerepel a FROM klauzulában: MERGE INTO <table name> USING ( SELECT * FROM <table2> ) <alias> ON <condition> WHEN MATCHED THEN UPDATE <setters> ha kettőnél több tábla: MERGE INTO <table name> USING ( SELECT <table1>.ROWID row id, <setter values> FROM <table source> WHERE <condition> ) <alias> ON (<table1>.ROWID = <alias>.row id) WHEN MATCHED THEN UPDATE <setters> 2.4 táblázat Az UPDATE utasításnál szükséges transzformációk 39 RETURN A RETURN utasítással, illetve a visszatérési értékekkel kapcsolatos problémákat az 1.23 fejezetben részletesen bemutattam A fordító implementálásakor az Oracle SQL Developer által is járt utat választottam, azaz, ha egy tárolt eljárásnak van visszatérési értéke, akkor függvénnyé alakítom, azzal a különbséggel, hogy a visszatérési érték típusának mindig NUMBER-t (INTEGER-t) választok.
Ugyanis – habár szintaktikailag értelmes egy karaktersorozat visszaadása – egy tárolt eljárás csak egész számot adhat vissza Ha más típusú adatot próbál, aminek nem lehetséges az implicit konverziója integer típusra, futás időben hibát fogunk kapni. IF EXISTS. Feltételes elágazás van a T-SQL és a PL/SQL nyelvben is Viszont abban különböznek, hogy milyen kifejezést lehet megadni feltételként A T-SQL programokban igen gyakran használt IF EXISTS szerkezet sajnos nem elérhető PL/SQL nyelven, a működését szimulálni kell. Erre a 25 táblázatban három lehetséges transzformációt mutatok be A 2.5 táblázatban szereplő konstrukciók szemantikailag ekvivalensek egymással, a szemléletességükben, illetve a transzformáció elvégzésének nehézségében viszont különböznek Jól látható, hogy az első megoldás érintetlenül hagyja az eredeti SELECT utasítást, pusztán „köré” épít, a második megoldás kiegészíti a WHERE
klauzulát plusz egy feltétellel, míg a harmadik megoldás a lekérdezési listát is módosítja. Mindhárom megoldás közös tulajdonsága, hogy igényel egy ideiglenes változót is. Grafikus kezelői felület A rendszer magját képező három komponens fölé egy grafikus kezelői felületet illesztettem, aminek szerepe a használat megkönnyítése. A felület – melynek képernyőképe a 2.4 ábrán látható – két szövegdobozból, és egy gombból áll A gomb megnyomására a bal oldali szövegdobozban szereplő szöveget a fordító megpróbálja lefordítani PL/SQL nyelvre, aminek az eredményét a jobb oldali szövegdobozban jeleníti meg A háttérben a 2.2 ábrán bemutatott folyamat zajlik Ha esetleg kiváncsiak vagyunk a forrás absztrakt szintaxis-fájára, ezt a fordító az alapértelmezett kimenetre kiírja. 2.3 Összefoglalás A fejezetben bemutattam, hogy az ANTLR és a StringTemplate segítségével hogyan lehet egy fordítót elkészíteni.
Szándékosan nem mentem bele az implementációs részletekbe, ugyanis a cél nem ezen eszközök részletes bemutatása volt6 , hanem egy fordító megtervezése. 6 Ezzel kapcsolatban lásd: [5] és http://www.antlrorg valamint http://wwwstringtemplateorg 40 SQL Server IF EXISTS(<select statement>) Oracle DECLARE v temp NUMBER(1, 0) := 0; BEGIN SELECT 1 INTO v temp FROM DUAL WHERE EXISTS ( <select statement> ); IF v temp = 1 THEN - . END IF; END; második alternatíva: DECLARE found boolean := false; BEGIN for x in (<select statement> AND ROWNUM=1) loop found := true; - . exit; end loop; END; harmadik alternatíva DECLARE v temp INTEGER; BEGIN SELECT COUNT(*) INTO v temp FROM <table source> WHERE <condition> AND ROWNUM = 1; IF v temp = 1 THEN - Do something END IF; END; 2.5 táblázat Az IF EXISTS szerkezetnél lehetséges transzformációk 41 2.4 ábra A fordítóhoz tartozó grafikus kezelői felület A fejezet számos elméleti és
gyakorlati témakörre is rámutat, amik önmagukban is témáját képezhetik további munkáknak: az LL(*) elemzők működése, környezetfüggő nyelvek elemzése szintaktikus és szemantikus predikátumok segítségével, illetve amiről nem esett szó, de a fordításhoz szükséges nyelvtanok implementálásakor erősen támaszkodtam rá: az ANTLR úgynevezett backtracking lehetősége, ami azt teszi lehetővé, hogy a levezetési szabályok alkalmazásakor ha az egyik alternatívát nem sikerült alkalmazni, az ANTLR visszatekeri a bemenetet, és megpróbálja a következő alternatívával. A következő fejezetben az elkészített rendszert értékelem: bemutatom, hogy a funkcionális tervnek mennyire felel meg a fordító, illetve, hogy a kitűzött célt sikerült-e elérnem. 42 3. fejezet Értékelés Egy rendszer értékelésekor azt kell vizsgálni, hogy megfelel-e a vele szemben támasztott követelményeknek. A jelen esetben ez azt jelenti, hogy
elvégzi-e, illetve helyesen végzi-e el a funkcionális tervben specifikált átalakításokat. Ennek vizsgálatára teszt-eseteket állítottam fel (lásd: B. függelék) A teszt-esetek jól lefedik egy átlagos T-SQL tárolt eljárásban használt utasításokat, szerkezeteket, ugyanis tartalmazzák a DML utasításokat (SELECT, INSERT, UPDATE, DELETE), feltételes szerkezeteket, programvezérlő utasításokat, változók deklarációját, értékadásokat, illetve a hibajelzéshez kapcsolódó utasításokat. A tranzakció kezelő utasítások tudatosan maradtak ki: az 1.23 fejezetben bemutatott problémák miatt ezen utasítások egy részének nem létezik szemantikailag ekvivalens megfelelője Szimulációjuk megítélésem szerint kifejezetten káros lenne: szintaktikailag helyes kódot kaphatunk ugyan, de a két rendszer közötti elvi különbségekből adódóan futás közben előjöhetnek nem várt hatások. Ezért automatizált fordításukra adott bármilyen
kísérlet abban a tévhitben tarthatja a felhasználót, hogy létezik általános megoldás, pedig épp ellenkezőleg: ilyen esetekben a programkód és az azt hívó eljárások kódjának alapos analízise szükséges. Az előállított kód jól olvashatóságát és szintaktikai helyességét a sablon-alapú megközelítés biztosítja. Ugyanis a fordító kimenetként a sablonban megadott formájú és szerkezetű szöveget fog adni. Belátható, hogy ha a sablont a célnyelv szintaktikai szabályainak megfelelően készítjük el, akkor a kimenet vagy helyes lesz, vagy hiányozni fog Megjegyzendő, hogy a fordító csak szintaktikailag helyes T-SQL kódból képes PL/SQL kódot előállítani, az esetleges hibák kijavítása vagy jelzése nem volt cél. Ilyen esetekben a Parser nem képes az adott utasítást elemezni, és az ANTLR hibakezelési stratégiájának megfelelően megpróbál újra szinkronba kerülni. Ennek illusztrálásáért lásd a 3.1 ábrát, ahol a
fordító bemenetként egy olyan tárolt eljárást kapott, ami tartalmaz szintaktikailag hibás utasítást. Jól látható, hogy az INSERT utasítás hibát tartalmaz: hiányzik a VALUES kulcsszó. A fordító a meglévő darabokból nem képes értelmes rész-utasítást előállítani (hiányzik egy kötelező 43 3.1 ábra Szintaktikailag hibás bemenet fordítása a fordító segítségével klauzula), ezért a kimenetből ez teljes egészében kimarad. A hibát az ANTLR jelzi is, amit a fordító az alapértelmezett kimenetre ír ki: line 6:21 no viable alternative at input ’@’ (CREATEPROC sp wrong (PARAMS (PARAM (VARIABLE id) int (DEFAULT (CONSTANT null)))) (SQLBLOCK (if (EXPR (exists (SELECTSTMT (SELECT (SELECTLIST (CONSTANT 1)) (FROM (DBOBJECT myTab)) (WHERE (EQUAL (DBOBJECT id) (VARIABLE id))))))) return))) (insert myTab (DERIVED (SELECTSTMT (( <unexpected: [@66,129:129 =’@’,<188>,6:21], resync=@id, ’hibas utasitas’>)))) X:ANTLRTSQLTree.g:
node from line 0:0 no viable alternative at input ’@id, ’hibas utasitas’’ Az első üzenetet a Parser adta, jelezve, hogy nem tud mit kezdeni a @ szimbólummal azon a helyen, ahol érkezett. A második sorban az AST látható, amiből leolvasható, hogy egy INSERT INTO . SELECT utasításnak próbálta értelmezni (a VALUES kulcsszó hiánya miatt), viszont a zárójel után egy SELECT kulcsszónak kellett volna következnie A harmadik üzenetet a Treewalker adta, akinek hasonló gondja volt, mint a Parsernek: hibás, hiányos a részfa. Az ANTLR hibakezelési és újraszinkronizálási stratégiájának részletes bemutatásához lásd [5] 10. fejezetét 44 3.1 Bővíthetőség Az ANTLR és a StringTemplate által biztosított fordítási architektúra (melynek általános vázlatát a 2.2 ábra mutatja) egyfelől igen rugalmas, annak köszönhetően, hogy a komponensek szerepköre jól definiált, egymástól elhatárolt. A végső kód megjelenítéséért,
formázásáért egyedül a StringTemplate sablon-leírás felelős. Ezen sablon-motor használata megköveteli a szigorú Model-View-Controller szeparációt, aminek következtében logikát, utasításokat nem tartalmaz. Ahogy a dolgozatban végig hangsúlyoztam, általános esetben teljes értékű automatizált TSQL PL/SQL fordító nem készíthető. Viszont elképzelhetőek olyan speciális esetek, amikre testreszabva a fordítót, közel hibamentes működés érhető el. Ehhez a Treewalkerben megvalósított transzformációk módosítása, finomítása szükséges Mivel a Treewalker a nyelvtan jórészt deklaratív leírásából kódgenerálással készült, ezért a testreszabás nagyságrendekkel egyszerűbb feladat, mintha a forráskódhoz kéne hozzányúlni. Ha pedig a forrás-nyelv változik (pl.: más T-SQL verzió esetén), akkor a Parser nyelvtanát kell módosítani. Amennyiben megtartjuk a kimenet, tehát az AST szerkezetét (pontosabban fogalmazva az
előállított kimenet ugyanannak a nyelvnek a része, mint eddig), a többi komponens módosítása nem szükséges. 3.2 Limitációk Az ANTLR+StringTemplate használata rendkívül kényelmes befektetésnek bizonyul, ha a forrás és a célnyelv hasonló. Azaz az utasításokat, blokkokat hasonló szintaxisfával tudjuk leírni, a köztük levő transzformációkat meg tudjuk határozni Sajnos ezt a módszert nem lehet kiterjeszteni általános célú magas szintű nyelvek közötti fordításra [11]. A 2.22 fejezetben a rendszer célját úgy határoztam meg, hogy képes legyen fordítási ötleteket adni, illetve automatizáljon bizonyos részfeladatokat Ez a B függelékből jól látható, hogy megvalósult, bizonyos esetekben jobb eredményt elérve, mint az Oracle Migration Workbench (lásd B.14 ábra) Ennél többet az 123 fejezetben bemutatott korlátok miatt nem lehet elvárni Meg kell elégednünk annyival, hogy ha rendelkezünk az adatbázis és a hozzá kapcsolódó
alkalmazások átfogó, rendszer-szintű ismeretével, és az aktuális alkalmazási környezet lehetővé teszi számunkra, bizonyos problémák kezelésére speciális megoldásokat építsünk a fordítóba. Például a túl hosszú objektumneveket egy adott algoritmus szerint rövidíthetné, amit az alkalmazásba is be lehet építeni. 45 4. fejezet Összefoglalás Adatbázisok migrációja komplex, többszintű feladat. Ebben a dolgozatban bemutattam azokat a főbb problémákat, amikkel egy migrációt végző szakember szembesülhet Ezek főleg az adatok migrációjával, illetve a nyelvi különbségekből adódó problémák. A tárgyalás fókuszában az utóbbi állt: Microsoft SQL Server-ről Oracle adatbáziskezelőre való áttérés során a tárolt programok, tárolt eljárások fordításának problémáit, illetve az ezekre adott megoldások elvi korlátait mutattam be. Bemutattam, hogy – habár jelenleg is léteznek a migrációs folyamatot
támogató eszközök – ezek a két rendszer közötti elvi különbségekből adódóan nem lehetnek képesek általános esetben teljes értékű megoldást nyújtani. Az Oracle által készített és használt módszertan, az Oracle Relational Migration Maps egyfajta keretet nyújt a munka szervezéséhez, elvégzéséhez. Fontos, hogy a módszertan szerint a migrációt szakemberek végzik, a migrációs eszközök csupán segítik őket, munkájukat nem helyettesítik. A második fejezetben a formális nyelvi elemzés alapjainak bemutatása után egy T/SQL PL/SQL fordító funkcionális tervét ismertettem, amit az ANTLR és a StringTemplate rendszerek felhasználásával valósítottam meg. A fordítóval a célom az volt, hogy a migrációt végzők számára egyrészről fordítási ötleteket adjak, másrészt segítségével automatizálni lehessen részfeladatokat. Bízom benne, hogy hasznos segédeszközt hoztam létre, amit – kisebb átalakítások,
testreszabások után – éles projektekben is fel lehet használni. Ezúton szeretnék köszönetet mondani az Fx Software Zrt. munkatársainak, kollégáimnak egyrészről a szakmai támogatásért, ötletekért, másrészről, hogy engedték, hogy adatbázis migrációs kérdésekkel foglalkozzak. Remélem, hogy a dolgozat elkészítése során szerzett tudást kamatoztathatom a jövőben. Köszönet illeti belső és külső konzulenseimet, Kardkovács Zsoltot, Marton Józsefet és Herczegh Gézát, amiért konstruktív ötleteikkel, kritikáikkal segítették a munkámat, illetve rámutattak számos hibára, hiányosságra. Szeretném még megköszönni Sárecz Lajosnak az Oracle Hungary-tól, hogy már az önálló 46 labor tárgytól kezdve nyomon követte munkámat, segített megválaszolni a felmerült kérdéseket, illetve lehetőséget biztosított, hogy egy előadást is tartsak az Oracle-nél migráció témában. Végül, de nem utolsó sorban Terence Parr
munkája, az ANTLR és a StringTemplate nélkül valószínűleg nagyságrendekkel nehezebb dolgom lett volna. 47 A. függelék Oracle SQL Developer Migration Workbench Migrációs beállítások Az SQL Developer Tools Preferences menüpontja alatt lehetőség van pár, migrációval kapcsolatos beállítás elvégzésére. Ezek közül kiemelendő: • Az üres sztringek kezelése: Oracle alatt az üres sztring (”) a NULL értékkel ekvivalens. Ezért itt beállíthatjuk, hogy adatok migrálásakor a forrásrendszeri üres sztringeket mivé fordítsa át az OMWB: space-é, vagy NULL értékké. • Az azonosító nevekkel kapcsolatban be lehet állítani, hogy a migrált objektumok neve elé milyen prefixet szúrjon a rendszer. Például, ha itt prefixnek az ’xyz ’-t állítjuk, akkor a forrásrendszerbeli EMPLOYEES táblából xyz EMPLOYEES lesz. • A forrásrendszerben be van-e kapcsolva a QUOTED IDENTIFIER: ezzel a kapcsolóval lehet megadni, hogy a dupla
idézőjelek között szereplő sztringeket azonosító névként értelmezze a rendszer. • A változónevek elé milyen prefixet szúrjon be a rendszer: külön meg lehet adni, hogy a változónevek elé, illetve a bemenő paraméterek neve elé milyen prefixet szúrjon be. Alapértelmezetten a v és az iv van beállítva. Amennyiben ennél részletesebb beállításokat szeretnénk tenni, akkor a Migration Repository-ban szereplő MIGRATION, MIGRATION REPORT, MIGRATION TRANSFORMER PL/SQL csomagok kódját kézzel kell módosítani az igényeinknek megfelelően. Migráció A migráció elvégzésére alapvetően két mód áll rendelkezésre: • Quick Migrate: egy varázsló által vezérelt mód, ami minimális beleszólást tesz lehetővé • Manual Migrate: az ajánlott, kézi mód 48 A kézi migráció az alábbi lépésekből áll: • Associate Migration Repository: egy meglévő Oracle Database kapcsolaton ki kell jelölni a Migration Repository-t. Ez a
gyakorlatban a PL/SQL csomagok, illetve táblák létrehozását jelenti Ajánlott egy erre dedikált sémát használni • Capture: a forrás adatbázis-kapcsolaton ki kell választani a migrálandó sémát, majd a Capture Model parancs hatására az SQL Developer beolvassa a forrásrendszerből a migrációhoz szükséges metaadatokat. • Convert to Oracle Model: ez a lépés jelenti a migrációt magát. Először beállíthatjuk az adattípusok közti konverziót (erről lásd bővebben az 1.23 fejezetet), majd az SQL Developer elvégzi a sémaobjektumok átfordítását • Generate DDL: az előző lépés hatására csak egy úgynevezett Converted Model jön létre a Migration Repository-ban, ezért a sémát létrehozó DDL utasításokat tartalmazó scriptet külön lépésben létre kell hoznunk. A fenti lépéseket elvégezve, illetve a DDL scriptet futtatva kapunk egy többé-kevésbé helyes, Oracle sémát. Ha szerencsések vagyunk, akkor az összes objektum
sikeresen létrejött, és a Migration Migrate Data menüponttal az adatok migrációját is megkezdhetjük. Az sqlserver utilities csomag Az sqlserver utilities PL/SQL csomag pár SQL Server specifikus függvényt próbál emulálni Oracle környezetben. Mivel hivatalos dokumentációja nincs, ezért az itt leírtakat kizárólag tapasztalati úton következtettem ki Az emulált SQL Server függvények: convert patindex year hex* month oct* day radians dateadd degrees isdate to base* datepart rand datediff isnumeric parsename quotename stuff fetch status* round difference reverse pi datename str to date* stats date ident incr ident seed str A.1 táblázat Az sqlserver utilities csomagban található függvények Az A.1 táblázatban a *-al jelölt függvényeknek nincs SQL Server oldali megfelelőjük. A to base függvény az első paraméterében kapott decimális számot váltja át a második paraméterében kapott alapú számmá, és VARCHAR2 típusúként adja
vissza. Maximum 16-os 49 számrendszerig képes kezelni az átváltásokat, de erre vonatkozó ellenőrzés nincs megvalósítva. A hex és oct függvények a to base függvényt hívják 16 illetve 8 paraméterrel. A fetch status függvény némileg kilóg a sorból: ezzel a függvénnyel a készítői a @@FETCH STATUS globális változó működését próbálták emulálni. Például az OMWB a lenti Transact-SQL kódot: FETCH NEXT FROM c1 INTO @myVar WHILE (@@FETCH STATUS <> -1) BEGIN IF (@@FETCH STATUS <> -2) -- do something FETCH NEXT FROM c1 INTO @myVar END ilyen módon fordítja PL/SQL-re: FETCH c1 INTO v myVar; WHILE ( sqlserver utilities.fetch status(c1%FOUND) <> -1 ) LOOP BEGIN IF (sqlserver utilities.fetch status(c1%FOUND) <> -2 ) THEN -- do something END IF; FETCH c1 INTO v myVar; END; END LOOP; Ebből már rögtön látszódik a működési elve: egy PL/SQL kurzoron a %FOUND attribútum igazat ad vissza, ha a fetch művelet sikeresen
végrehajtódott, és hamisat, ha nem. Ezt a logikai értéket alakítja a fetch status függvény rendre 0-ra és -1-re. Viszont a @@FETCH STATUS változónak lehet egy harmadik értéke is: -2, ha az adott sor már nincs meg (például a kurzor megnyitása és a fetch művelet között valaki kitörölte). A másik szembeötlő különbség az, hogy a T-SQL-es változó globális, míg a fetch status függvény értéke a megadott kurzorra vonatkozik. Ehhez hasonló hibákat, hiányosságokat a többi függvény esetében is találhatunk. Ökölszabályként kijelenthető, hogy ha az OMWB egy kódrészletet úgy fordít át, hogy valamelyik cso50 magbeli függvényt használja, érdemes megnézni, nem lehetne-e közvetlenül PL/SQL-es változatot használni (pl.: kurzor-attribútumok, convert helyett cast, stb), vagy ha mégis a csomagot szeretnénk használni, akkor alaposan nézzük végig az egyes függvények implementációját, nehogy működés közben érjen minket
meglepetés. 51 B. függelék A funkcionális tervben specifikált transzformációk működés közben B.1 ábra Kurzorváltozók használata kimeneti eredményhalmaz előállításához A T-SQL tárolt eljárások által használt eredményhalmazokat a PL/SQL tárolt eljárásokban kimeneti kurzorváltozókkal lehet szimulálni. A B1 ábrán a 223 fejezetben bemutatott transzformációt illusztrálom Ha egy SELECT utasításnak nincs FROM klauzulája, akkor ezt „pótolni” kell: a 2.1 táblázatban definiált transzformációt a B2 ábra mutatja Az oszlop alias-okra a 2.1 táblázatban definiált transzformációt a B3 ábra mutatja A SELECT . INTO utasítás átalakítását egy INSERT és egy SELECT utasításra a B4 ábra mutatja. A transzformációt a 21 táblázatban definiáltam A B.5 és a B6 ábrán az UPDATE utasítás átalakítását mutatom be akkor, ha két, illetve ha több forrás-tábla van megadva a FROM klauzulában. Ezt a transzformációt a 24
táblázatban definiáltam. 52 B.2 ábra SELECT utasítás FROM klauzula nélkül B.3 ábra SELECT utasítás kétféle alias szintaxisa B.4 ábra SELECT utasítás INTO klauzulájának fordítása 53 B.5 ábra UPDATE utasítás átalakítása MERGE utasításra 1 B.6 ábra UPDATE utasítás átalakítása MERGE utasításra 2 B.7 ábra IF EXISTS szerkezet szimulálása 54 PL/SQL-ben nincs az IF EXISTS feltételes szerkezetnek közvetlenül megfelelő elem. Ezt a 2.5 táblázatban definiált módon szimulálni kell Erre mutat példát a B7 ábra B.8 ábra Tárolt eljárás visszatérési értékkel Egy PL/SQL tárolt eljárás nem rendelkezik visszatérési értékekkel. Ahogy a 223 fejezetben bemutattam, ezeket a tárolt eljárásokat a fordító függvényekké alakítja A B8 ábrán is látható a figyelmeztetés, hogy a két változat szemantikailag nem ekvivales egymással. B.9 ábra WHILE ciklus A WHILE ciklus nem igényel átalakításokat pár apró
szintaktikai különbséget leszámítva. A B.9 ábra azt illusztrálja, hogy a fordító az absztrakt szintaxis fából a PL/SQL szintaxisnak megfelelően PL/SQL szintaktikai szabályainak megfelelő kimenetet állít elő, azaz magán a 55 szintaxis fán nem végez átalakításokat. B.10 ábra Hibajelzés: RAISERROR fordítása B.11 ábra INSERT utasítás INTO kulcsszó nélkül A 2.2 táblázatban bemutatott átalakítást illusztrálja a B11 ábra B.12 ábra DELETE utasítás második FROM klauzulával A 2.3 táblázatban bemutatott átalakítást a B12 és a B13 ábra illusztrálja Figyeljük meg, hogy a B.14 ábrán láthatóan az Oracle Migration Workbench bizonyos esetekben hibásan fordítja le azokat a DELETE utasításokat, amik tartalmaznak második FROM 56 B.13 ábra DELETE utasítás második FROM klauzulával – összetettebb példa B.14 ábra DELETE utasítás hibás fordítása az Oracle Migration Workbench-el klauzulát is. A hiba abból adódik,
hogy az első és a második, join-nal összekapcsolt táblát egy táblaként kezeli, és ennek próbálja a ROWID oszlopát venni. Az általam készített fordító ezeket az eseteket is helyesen kezeli (lásd: B.13) 57 Irodalomjegyzék [1] Bach, Iván (2001): Formális nyelvek (Typotex) [2] Bansleben, Erik Peter (2004): Database Migration: A Literature Review and Case Study (diplomamunka, konzulens: Stephanie W. Haas) [3] Kisil, Vladimir; Sevastyanov, Denis; Fomenko, Valery; Rusakov, Yuri (2007): Guide to Migrating from Oracle to SQL Server 2005 (SQL Server Technical Article) [4] Lin, Chang-Yang (2008): Migrating to Relational Systems: Problems, Methods & Strategies (CMR Journal) [5] Parr, Terence (2007): The Definitive ANTLR Reference: Building Domain-Specific Languages (Pragmatic Bookshelf) [6] Parr, Terence; Quong, R. W (1995): ANTLR: A Predicated-LL(k) Parser Generator (John Wiley & Sons) [7] Parr, Terence (2004): Enforcing Strict ModelView Separation in Template
Engines (WWW ’04: Proceedings of the 13th international conference on World Wide Web) [8] Parr, Terence (2006): A Functional Language For Generating Structured Text http://www.csusfcaedu/ parrt/papers/STpdf (hozzáférve: 2009-05-08) [9] Rahm, Erhard; Hai Do, Hong (2000): Data cleaning: Problems and current approaches (IEEE Data Engineering Bulletin) [10] Terekhov, A.A; Verhoef, C (2000): The Realities of Language Conversions (IEEE Software) [11] Tripp, Andrew H. (2006): Why I Don’t Use StringTemplate For Language Translation: http://www.antlrorg/article/1136917339929/stringTemplatehtml (hozzáférve: 2009-04-29) 58 [12] Sz.n (2008): SQL Developer Supplementary Information for Microsoft SQL Server and Sybase Adaptive Server Migrations, Release 1.5, Part no: E12156-01 [13] Sz.n (2007): SQLWays Documentation – Database Migration Software Version 39 http://www.ispirercom/doc/sqlways39/SQLWayspdf (hozzáférve: 2009-04-30) [14] Sz.n: Oracle Relational Migration Maps:
http://www.oraclecom/technology/tech/migration/maps/indexhtml (hozzáférve: 2009-03-29) [15] Sz.n: Migrating Applications from Microsoft SQL Server to Oracle9i Database – An Oracle White Paper (November 2003) [16] Sz.n: MSDN Library – Microsoft Developer Network http://msdn.microsoftcom/en-us/library/bb545450aspx [17] Sz.n: Oracle Database Concepts, Release 1 (111) Part no: B28318-05 [18] Sz.n: Oracle Database SQL Language Reference, 11g Release 1 (111) Part no: B2828605 59