A következő címkéjű bejegyzések mutatása: migráció. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: migráció. Összes bejegyzés megjelenítése

Mérnök lettem

Ma megvolt a diploma védése, melynek során mérnökké nyilvánítottak. Juppi!

Diplomamunkám ezen a címen érhető el, a bizottság ötösre értékelte az elvégzett munkám, és így végeredményben a diplomám minősítése négyes lett. A védésen tartott prezentációm diái itt vannak.

Fordító

Rövid bejegyzés lesz, mostanában picit kevés a szabadidőm (diplomamunka, záróvizsgák):


A fenti linken pár screenshot látható a diplomamunka keretében implementált T-SQL - PL/SQL fordítóról. Amolyan proof-of-concept munka, ANTLR+StringTemplate segítségével. Ha leadtam a leadni valókat és befejeztem a befejezni valókat, majd írok egy összefoglalást, hogy mennyire fényjézus ez az ANTLR.

Tranzakciókezelés és zárolás SQL Server és Oracle alatt

Az SQL Server és az Oracle adatbáziskezelő tranzakció kezelése különbözik egymástól.

Arról már írtam egy korábbi bejegyzésben, hogy egy tranzakció SQL Server alatt (alapértelmezetten) explicit, azaz begin transaction … commit/rollback transaction utasítások jelölik az elejét illetve a végét, míg Oracle alatt az első végrehajtható SQL utasításkor kezdődik, és commit/rollback (vagy hiba, disconnect) hatására ér véget. Viszont nem ez az egyedüli különbség, a két rendszer a zárakat is eltérően kezeli.

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 lock). Ennek következtében a SELECT és az UPDATE utasítások fizikailag szerializáltan hajtódnak végre.

Ezzel szemben Oracle alatt egy SELECT utasítást nem blokkolja semmi, és egy SELECT utasítás nem blokkol senkit (FOR UPDATE klauzula nélkül).

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
-- 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 alábbi ábrán látható módon indul el két tranzakció.
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 (akkor nem várt, ha nem tudjuk, hogy működik :)):
  • 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.

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 „ész nélkül” (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 olyan táblát szeretnének módosítani, amit egy másik – még el nem commitált – tranzakció már módosított, akkor hibát kapunk:

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.

PLS-00990 és PLS-00989

Ez a két hibaüzenet kissé elkeserített, ugyanakkor megmagyarázza számomra, hogy az SQL Developer Migration Workbench miért több kurzor változó segítségével oldja meg az eredményhalmazok visszaadását a tárolt eljárásokból, és miért nem egy kurzor változókat tartalmazó tömb segítségével.

A válasz egyszerű: mert nem lehet kurzor változókat tartalmazó tömböt készíteni:
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 "this release" pedig az Oracle Database 11g Enterprise Edition Release 11.1.0.6.0. Pedig milyen jó ötletnek tűnt.. :)

ANTLR + StringTemplate

A hétvégén tovább folytattam az ANTLR-el való ismerkedést. Az ANTLR egy parser-generátor, ami egy nyelvtan alapján generál egy rekurzív leszálló (recursive descent) szintaktikai elemzőt.

Egy elemző alapesetben nem csinál mást, minthogy eldönti, hogy egy adott mondat része-e a nyelvnek, szintaktikailag értelmes-e. Az ANTLR segítségével viszont könnyedén készíthetünk olyan nyelvtanokat, amikből a generált parser egy bemenő mondatból kimenetként egy absztrakt szintaxis fát (AST) állít elő. Ezt a fát bejárva akár kiértékelhetjük a bemenetet (utasításokat), akár transzformációkat végezve átfordíthatjuk egy másik nyelvre.

Terence Parr a könyvében azt javasolja, hogy AST-k bejárásához is használjunk nyelvtanokat, pontosabban egy nyelvtan leírásából generált parsert, aminek a bemeneti mondatai az AST-k, kimenete meg mondjuk egy StringTemplate sablon megfelelő kitöltése.

Ezt a folyamatot szemlélteti - szintén Parr könyvéből származó - ábra:

A Code Generation honlapján 2005-ben megjelent egy cikk, "Language Translation Using ANTLR and StringTemplate" címmel, amiben Terence Parr bemutatja, hogyan lehet egy fordítót készíteni, ami egy C-szerű nyelvből Java, Python, vagy Java bytecode kódot állít elő. A példa nyelvtanok sajnos még az ANTLR 2-es verziójával készültek, amit azóta több szempontból is felülmúlt a legújabb, 3-as verzió. A hétvégén ezt alakítottam át, hogy működjön a 3-as verzióval, és először egy AST-t generáljon, majd az AST-t bejárva töltse ki a sablonokat.

A nyelvtanok itt találhatóak:
A tesztelést megvalósító Java kód:
Működése pedig:

A teszt bemenet legyen a honlapon is fentlevő kódrészlet:

char c;
int x;
int foo(int y, char d) {
int i;
for (i=0; i<3; i=i+1) {
x=3;
y=5;
}
}

Ezt Python-ra fordítva az alábbi kimenetet kapjuk:

kelda@psycho:~/ANTLR/cminus$ cat input.txt | java Test
(GLOBALS (VAR char c)) (GLOBALS (VAR int x)) (FUNCTIONS
(FUNCTION int foo (PARAMS (PARAM int y) (PARAM char d))
(BLOCK (LOCALS (VAR int i)) (STATEMENTS (FOR (INIT (= i
0)) (COND (< i 3)) (STMT (= i (+ i 1))) (BLOCK LOCALS
(STATEMENTS (= x 3) (= y 5))))))))

def foo(y, d):
i = 0
while ( i < 3 ):
x = 3
y = 5
i = i + 1

Az elején az AST látható (gyökér levél1 levél2 ... levéln) formában, utána pedig a kimenet.

Ez a bejegyzés igazából nem akart másról szólni, minthogy illusztrálja az ANTLR és a StringTemplate erejét, a részletek bemutatása nélkül. Később részletesen is bemutatom majd a működését, a nyelvtanok felépítését, és hogy az egész hogyan is illeszkedik a blogomba :)

Egy kis segítségként annyit elárulhatok, hogy az SQL Developer egyik könyvtárában van egy oracle.sqldeveloper.migration.translation.sqlserver.jar file, aminek a belsejében található egy generic.stg és egy tsql.stg StringTemplate sablon. Azaz az SQL Developer Migration Workbench is az ANTLR és a StringTemplate segítségével végzi a nyelvi fordítást.

Diplomaterv

Ebben a félévben főleg a diplomatervem elkészítésével fogok foglalkozni, melynek címe:

Adatbázis migráció Microsoft SQL Server adatbázisról Oracle adatbázis-kezelőre

azaz az előző féléves önálló labor témámat folytatom. A diplomaterv kiírásom pontjai:
  1. Ismertesse az adatbázisok migrációinak általános problémáit a hazai és nemzetközi szakirodalom alapján!
  2. Mutassa be a Microsoft SQL Server 2005 és Oracle 10g közötti migrációs eljárások és megoldások lehetőségeit és korlátait!
  3. Készítsen funkcionális tervet a T-SQL nyelven írt üzleti logika szemantikailag helyes PL/SQL nyelvre fordítására a nyelvek kifejezőerejéből adódó korlátok között!
  4. Implementálja és értékelje az elkészített rendszer egyes komponenseit, különös tekintettel
    • a PL/SQL kód olvashatóságára,
    • a PL/SQL kód szintaktikai helyességére,
    • a fordítási hibák jellegére és arányára.
Az első pont egy kicsit trükkös, ugyanis eddig igen kevés releváns szakirodalmat sikerült találnom. Amit találtam, azok is főleg az adatok migrációjával foglalkozik. Ezek közül kiemelendő Erik Peter Bansleben diplomamunkája az esettanulmány hasonlósága miatt:
The source systems consisted of a combination of Microsoft SQL Server and Access databases, while the target platform was an Oracle 9i server which was to serve as the backend database for the new system.
Ha esetleg tud valaki a témába vágó irodalomról, megköszönném, ha jelezné akár hozzászólásban, akár e-mailben: orveny kukac gmail pont com. Nagy segítség lenne :)

Az előző félévben gyakorlatilag a második ponttal foglalkoztam, így úgy érzem, ez nem fog különösebb kihívást jelenteni.

Ami érdekesebb, az a 3. és 4. pont: gyakorlatilag egy Transact-SQL -> PL/SQL fordítót kell készítenem. Szerencsére rátaláltam az ANTLR framework-re, aminek segítségével egy nyelvtanból viszonylag könnyedén lehet szintaktikus elemzőket, fordítókat készíteni.

A honlapján megtalálható Dermott O'Neill-től, az SQL Developer egyik fejlesztőjétől egy idézet:
The decision to use Antlr and StringTemplate for Oracles next generation Migration and SQL Developer features was easy due to the fantastic support on the forums, extensive documentation and great tools. In particular, the ability to parse trees and define target languages using StringTemplate, provided the end to end language translation technology we required. Other parser generators left us high and dry with only half the solution.
Egyelőre még csak az irodalom feldolgozás fázisában tartok, de mindenesetre igen bíztató, hogy egy olyan eszköz használatát tanulom, amivel az SQL Developer Migration Workbench is működik :)

Összefoglalás

Ma este lezárult egy szakasz: teljesítettem az önálló laborokat. A dokumentumokat feltöltöttem a netre:
Természetesen bármiféle hozzászólást, kritikát szívesen fogadok!

A héten megérkezett az idei Oracle Szeminárium vizsga eredménye is: 74,15%-ot értem el. Bevallom, jobbra számítottam, de hát ez van.

A blog írását folytatni fogom, az önálló labortól függetlenül is, és hát a diplomatervezés még hátra van..

Mentegetőzések

Több mint egy hónap telt el az utolsó blogbejegyzés óta. Ezt a hosszú szünetet nem üdüléssel töltöttem, hanem készült mindkét önálló labor doksim (migráció és adattárházak), meg persze egyéb házi feladatok, zh-k, előadások, munka, stb. Tehát csak a szokásos.

Menjünk időrendben: részt vettem az Oracle BI Fórumán, ahol több igen érdekes, és pár kevésbé érdekes előadást hallgattam meg. Sajnos, amit leginkább vártam, az FHB adattárház megvalósításáról szóló előadás sikeredett szerintem a leggyengébbre. Az előadás témája nagyon jó volt, csak maguk az előadók lehettek volna picit "meggyőzőbbek". Nekem valahogy úgy jött le az egész, hogy vagy álmosak, vagy nem akarják elmondani a "tutit" :) Egy hónap távlatából összesen annyira emlékszek, hogy több rendszerrel egy időben történt az adatbázis bevezetése, kialakítása, és nagyon nagyon összetett és bonyolult és komplex.

Az Essbase-es előadások viszont felkeltették a figyelmem. Szerintem majd "szabadidőmben" utána fogok olvasni, mi is az az essbase :)

Készülnek az önálló labor dokumentumaim is. Pontosabban a migrációról szóló már kész van, az adattárházak tervezéséről szóló pedig folyamatban van. Amint befejezem (abbahagyom?), felrakom azt is az internetre.

Ezen a héten volt az Oracle Szeminárium záró eseménye: a vizsga. Szerintem nem volt nehéz, de a jellege miatt (teszt, több helyes válasz is lehet, rossz válasz mínusz pont) akár igen gyengén is sikerülhetett.. Mindenesetre várom az eredményt :)


Oracle Blogger Dinner - 2008. ősz

Holnap délután 17 órakor az Oracle irodaházban kerül megrendezésre a harmadik Oracle Blogger Dinner, ahol magyar bloggerek beszélgetnek, esznek, isznak, jót mulatnak, de elsősorban a migrációról szóló előadásomat hallgatják :)

A slide-okat most is feltöltöttem a netre, a jelszó a szokásos (titok), a szokásos okból.

Habár a téma nem a legegyszerűbb, és én is belemegyek majd technikai részletekbe, remélem a sok T-SQL és PL/SQL kód között azért majd lesz idő egy koccintásra is :) És nagyon remélem, hogy ezúttal látszódni fognak a screenshot-jaim a projektoron, nem úgy, mint az egyetemi önálló labor előadáson..

Migráció V.

Oracle Relational Migration Maps

Az Oracle Relational Migration Maps (röviden: Migration Maps) az Oracle által használt módszertan egy meglévő adatbázis Oracle adatbáziskezelőre migrálásához.

Nem tudom, hogy ez mennyire aktuális, ugyanis a honlapja jól láthatóan egy régebbi design-ba illeszkedik, viszont az OTN-ről jelenleg is elérhető, ezért én úgy veszem, hogy az ott leírtak még ma is tükrözik az Oracle álláspontját :) De igazából nem is a konkrét részletek az érdekesek, hanem az eddig leírt részletes technikai problémák és megoldási javaslatok helyett három lépést hátralépve madártávlatból is megnézzük egy migráció folyamatát.

A folyamatban négy fő szerepkör lett definiálva:
  • Ügyfél
  • Migrációs projekt menedzser
  • Adatbázis migrációs mérnök
  • Alkalmazás migrációs mérnök

Az ügyfél feladata, hogy technikai segítséget nyújtson a migráció során az alkalmazással kapcsolatban felmerülő kérdésekben, ugyanis ebben a módszertanban a migrációt alapvetően az Oracle szakemberei végzik az ügyfél aktív közreműködésével. Ezen kívül a projekt elején ki kell tölteni egy úgynevezett Assessment Questionnaire-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. A kérdőívben olyan kérdések szerepelnek, mint például: "What business problems does your application system solve?" vagy "What is the timeframe for your current migration project?". Talán a "Select all of the technologies and languages used in your application system." kérdésre adható válaszlehetőségekből is látszik, hogy ezt a kérdőívet bizony nem ma írták, ugyanis a listában nem szerepel a C# .NET, sőt, a .NET egyedül ASP.NET kontextusban merül fel.

Az általános kérdések közé becsempésztek pár igen komoly - technológiai különbségekre utaló - kérdést is, ezek:
  • "Does your database or application system require any precision past seconds in the DATETIME column?": ez azért érdekes, mert az Oracle DATETIME adattípusa csak másodperc pontossággal képes az időt tárolni, míg SQL Serverben ez 3.33 ms.
  • "Does your database or application system evaluate empty strings ('') and NULLs as the same?": ugyanis Oracle alatt az üres string az NULL.

A kitöltött kérdőíven felül el kell küldenie az alkalmazás forráskódját is a migrációs projekt menedzsernek.

Igazából ezek egyszerű igen/nem kérdéseknek tűnnek, de ha mondjuk nekem kéne megválaszolnom őket, nem tudom, hány napig tartana a helyes válasz kiderítése.. Persze, ha valakinek olyan rendszerrel van dolga, ahol a legutolsó kis részlet is végletekig dokumentálva van, akkor más a helyzet.

A migrációs projekt menedzser áll a középpontban, ő biztosítja a magas minőségű munkát az Oracle 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. Magyarul ő a projekt menedzser, ennek minden feladatával és felelősségével együtt :)

Az adatbázis migrációs mérnök az Oracle Migration Workbench segítségével (akkor, amikor ezt a módszertant készítették, a Migration Workbench még egy különálló alkalmazás volt, most már az SQL Developer-be integrálták) migrálja a forrás adatbázist, és teszteli, hogy valóban úgy működik, mint az eredeti verzió.

Ehhez hasonlóan, az alkalmazás migrációs mérnök az alkalmazás megfelelő módosításaiért felelős.

A migráció 6 fő lépésből áll, melyeket az alábbi ábra szemléltet:


A definíciós fázisban kell elvégezni a meglévő rendszer részletes vizsgálatát, felmérni a feladat komplexitását, hogy meg lehessen becsülni a migrációs feladatokat, azok időtartamát.

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. 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.

Ezen felül szükséges még az adatbázis biztonsági, adminisztrációs követelményeinek illetve biztonsági mentések és visszaállítások stratégiájának vizsgálata.

A tervezési fázis arról szól, hogy a definíciós és analízis fázis során szerzett ismeretek alapján megtervezzük a felmerült feladatok, problémák megoldását, azaz elkészítjük 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 (például: ideiglenes táblák, séma-objektum nevekben történő változások, stb.), az adatok migrációjának a stratégiáját, átállási stratégiát, stb.

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 történik a tesztelés is.

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ást.

Az utolsó, üzemeltetési fázisban szükség lehet, hogy a már feltelepített, éles rendszerben kell támogatást nyújtani az ügyfélnek.

Migráció IV.

Tranzakciókezelés

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 í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.

Az Oracle Migration Workbench a BEGIN TRANSACTION utasításokat a SET TRANSACTION READ WRITE utasítássá fordítja át, ami az aktuális tranzakciót úgy állítja át, hogy utasítás szintű konzisztenciát biztosítson (statement-level read consistency).

Ez annyiban nem szerencsés, hogy míg a BEGIN TRANSACTION tetszőleges helyen szerepelhet egy T-SQL blokkban, addig Oracle alatt a tranzakció tulajdonságait csak az első utasításban lehet beállítani. Azaz például, ha egy INSERT szerepel a BEGIN TRANSACTION előtt (az explicit tranzakción kívül, alapértelmezetten autocommit módban), akkor ezt a Migration Workbench segítségével PL/SQL-re fordítva valami ilyesmit kapunk:

CREATE OR REPLACE PROCEDURE sp_something
AS
BEGIN
INSERT INTO proba
VALUES ( 1, SYSDATE );

SET TRANSACTION READ WRITE;

INSERT INTO proba
VALUES ( 2, SYSDATE );

ROLLBACK;

END;

ami - habár szintaktikailag helyes, azaz le fog fordulni - futáskor hibát fog adni:

ORA-01453: SET TRANSACTION must be first statement of transaction

Gond van még a @@TRANCOUNT változóval is, mert a dokumentáció szerint hiába támogatja a Migration Workbench a globális változók migrációját, nekem összesen annyit sikerült elérnem, hogy a @@TRANCOUNT-ot v_transcount-ra cserélte. A SwisSQL (a Migration Workbench konkurrenciája) ezt a kérdést úgy oldja meg, hogy egy package-ben deklarál egy változót, amit minden egyes tranzakciókezelő utasításkor növel/csökkent, azaz BEGIN TRANSACTION helyett növeli eggyel, COMMIT után csökkenti eggyel, és ROLLBACK után pedig 0-ra állítja.

Önálló labor beszámoló

Ma délután kértek fel arra, hogy holnap tartsak egy előadást az önálló labor témámról (migráció), mivel többen visszamondták a sajátjukat. Gyorsan összedobtam egy pár slide-ból álló prezentációt, aztán majd lesz, ami lesz :) Elvégre benne vagyok a témában, tehát olyan nagy meglepetés nem érhet..

A .rar archívumhoz a jelszó: titok, csak azért védtem így le, mert benne van az email-címem, aztán a csúnya spam-robotok nehogy megszeressenek.

Október 27-én egy Oracle Blogger Dinner keretén belül egy másik előadást is fogok tartani, szintén migráció témában, de az picit nagyobb lélegzetvételű lesz: kb. 1 óra a mostani 10-15 perc helyett. Ha elkészül majd - terveim szerint a jövő heti hosszú hétvége alatt - akkor azt is közzé fogom tenni.

Migráció III.

Visszatérési értékek

A visszatérési értékek okozzák a legizgalmasabb problémákat a migráció során. 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 - ha jól tudom - tetszőleges számú lehet, persze elképzelhető, hogy van valami igen magas felső korlát.

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

Az eljárást futtatva az alábbi 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.

Mint azt már egy korábbi, migrációról szóló bejegyzésben írtam, a migráció egyik célja az lenne, hogy a .NET-es vastag kliens lényegében kizárólag tárolt eljárás hívásokkal kommunikál az adatbázissal. Mind a módosító, mind a lekérdező funkciókat tárolt eljárások valósítják meg: az eljárás által visszaadott eredményhalmazt a kliens valahogy feldolgozza (pl.: megjeleníti táblázatos formában). De természetesen vannak olyan eljárások is, amik nem adnak vissza semmit sem, hanem csak az adatbázisban módosítanak valamit.

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) lehet emulálni. Az Oracle Migration Workbench 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/SQL-es 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;

Hoppá! Az OMWB függvényt csinált az eljárásunkból! 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. Csupán azért alakította a migráló eszköz függvénnyé, mert csak azoknak lehet visszatérési értéke.

Tehát annyit már most is kijelenthetünk, hogy ha vannak olyan T-SQL tárolt eljárásaink, amiknek van visszatérési értékük, akkor célszerű elgondolkozni azon, 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 trükkösebb. Ugyanis a kimenő paramétereket az eljárás hívásakor 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. Ha viszont .NET alól szeretnénk ilyen eljárásokat meghívni, akkor a paraméterlistába kell felvenni a kimenő kurzor típusú paramétert:

cmd.Parameters.Add("cv_1", OracleType.Cursor).Direction =
ParameterDirection.Output;

Ha ezt elmulasztjuk, akkor a hívás exception-t fog dobni, PLS-00306 hibaüzenettel: "wrong number or types of arguments in call to '*spname*'". Ugyanez igaz fordítva is, azaz ha olyan eljárásnál próbálnánk hozzáadni, aminek valójában nincs ilyen paramétere.

És ezzel eljutottunk egy igen kemény problémához: a migráció célja ugye az, hogy ugyanaz a kliens legyen használható SQL Server és Oracle adatbáziskezelő esetén is. SQL Servernek nem kell explicit megmondani, hogy a tárolt eljárás fog-e visszaadni eredményhalmazt, Oracle-nek viszont igen. Egy T-SQL tárolt eljárás tetszőleges számú eredményhalmazt is visszaadhat, egy PL/SQL-es viszont pontosan annyit, ahány kimenő kurzor változó paramétere van.

Mivel a migráció során úgyis hozzá kell nyúlni minden egyes tárolt eljárás kódjához (ha másért nem, akkor azért, hogy megnézzük, úgy működik-e, ahogy szeretnénk), ezért arra a kompromisszumos megoldásra jutottunk, hogy:
  • minden tárolt eljárásnak kötelezően van egy cv_1 nevű kimenő paramétere, a paraméterlista első helyén, attól függetlenül, hogy visszaad-e eredményhalmazt, vagy sem
  • azon tárolt eljárások esetében, amik több eredményhalmazt is visszaadnának, újragondoljuk a funkciójukat, és szükség esetén feldaraboljuk több eljárássá, azaz eljárásonként pontosan egy visszaadott eredményhalmaz.

Az eljárások egymást is hívják, ezért a kódot ilyen esetekben is módosítani kell. Mit nyerünk ezzel? Azt, hogy a kliens kód megtarthatja az univerzális jellegét, és nem kell kliens oldalon is minden egyes eljáráshíváshoz hozzányúlni. (Pl.: ott is megmondhatnánk explicit módon, hogy a hívott eljárás vissza fog-e adni eredményhalmazt, vagy sem, és ennek hatására SQL Server esetén nem történne semmi, Oracle esetén viszont felkerülne a paraméterlistába a kimenő kurzor változó.)

Az MSDN-en szerepel egy viszonylag részletes leírás arról, hogy hogyan kell .NET alatt PL/SQL tárolt eljárásokat hívni, illetve érdekes olvasmány még Vadim Tropashko kurzorokról szóló blogbejegyzése. Ennek a cikknek a végén szerepel egy kemény kijelentés:

It seem that the genesis of “a better cursor demand” is a stream of programmers from SQL Server world, where a procedure which doesn’t even bother to declare its outputs as a cursor like this

CREATE OR REPLACE PROCEDURE TESTSPROC2
AS
select * from test_table order by id_no;
GO

Its a pity that TSQL designers give an impression of not understanding the difference between a procedure and a query.

Hát, ez kicsit úgy hangzik, mintha az Oracle megoldása lenne az egyetlen járható út.

Készítettem egy buta kis Windows-os alkalmazást, ami azt próbálja demonstrálni, hogy a kliens oldalon milyen változtatások szükségesek ha SQL Server helyett Oracle adatbáziskezelőt akarunk használni. Az egyedüli különbség (a használt osztályokat leszámítva) a kurzor paraméter deklarációja. A kód letölthető innen.

Migráció II.

Identity/Szekvencia

Microsoft SQL Server alatt az automatikus azonosító generálásra az IDENTITY típusú oszlopok szolgálnak. A típus megadására a CREATE TABLE illetve ALTER TABLE utasításokkal van lehetőség:

CREATE TABLE new_employees
(
id_num int IDENTITY(seed,increment),
fname varchar (20),
minit char(1),
lname varchar(30)
)

Forrás: http://msdn.microsoft.com/en-us/library/aa933196(SQL.80).aspx

ahol a seed a legelső táblába beszúrt értéket, az increment pedig a növekményt jelöli. Az IDENTITY típusú oszlopokhoz kapcsolódnak a @@IDENTITY, IDENT_CURRENT() illetve SCOPE_IDENTITY() függvények. Az IDENT_CURRENT() egy adott táblára mondja meg, hogy mi volt az utolsó IDENTITY érték, a SCOPE_IDENTITY az adott hatáskörben teszi meg ugyanezt, az @@IDENTITY pedig hatáskörtől függetlenül a legutolsó értéket adja vissza. A hatáskör lehet tárolt eljárás, trigger, függvény vagy batch.

Például ha van két tábla IDENTITY mezőkkel, T1 és T2, és a T1-be történő beszúrás hatására lefut egy trigger, ami a T2 táblába szúr be egy sort, akkor az INSERT utáni @@IDENTITY és SCOPE_IDENTITY() más értékeket fog visszaadni: az @@IDENTITY a legutolsó beszúrás eredményét, tehát a T2-be beszúrt identity értéket, a SCOPE_IDENTITY() pedig a T1-be beszúrtat.

Oracle alatt az IDENTITY oszlopok helyett szekvenciák vannak. A szekvenciák önálló séma objektumok, amik automatikusan generálnak egyedi értékeket. Az IDENTITY-hez hasonló működést szekvenciák és triggerek segítségével lehet elérni. Erről Pém Gábor is írt egy bejegyzést a blogjában. Viszont az @@IDENTITY, SCOPE_IDENTITY(), IDENT_CURRENT() függvényekkel ekvivalens nincs Oracle alatt, ezeket a szekvenciákhoz tartozó CURRVAL, NEXTVAL pszeudo-oszlopokkal lehet emulálni.

Az SQL Developer Migration Workbenchje az IDENTITY oszlopokat úgy fordítja át, hogy meggenerálja a hozzájuk tartozó triggereket és szekvenciákat, valamint beállítja az sqlserver_utilities PL/SQL package identity nevű változóját, amivel a @@IDENTITY működését próbálja szimulálni. Ha a T-SQL kódban használjuk a SCOPE_IDENTITY() vagy az IDENT_CURRENT() függvényeket, akkor minden esetben manuálisan kell átírni a logikát PL/SQL-re.

Azonosító nevek, fenntartott kulcsszavak

A kulcsszavak és fenntartott szavak (keywords and reserved words) problémájáról már írtam egy bejegyzést, amit valahogy úgy lehetne összefoglalni, hogy ne használj kulcs- és fenntartott szavakat adatbázis objektumok nevekként.

Ha az ember a nulláról indul egy adatbázis megtervezésekor, akkor természetesen ez az ajánlott út. Viszont ha már van egy kész séma, többszáz táblával, amikre többezer hivatkozás is van, akkor bizony kénytelenek leszünk kompromisszumokat kötni. 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, 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 dokumentáció szerint "-k között szereplő fenntartott szavak lehetnek azonosító nevek, pl.: "DATE", de - mint már írtam - ebből lehetnek még problémák. Feltehetően egy bug miatt ha még SQL Server oldalon átnevezzük a fenntartott szavakat úgy, hogy egy _ postfixet teszünk a végükre, pl.: Type_, akkor a Migration Workbench "visszanevezi" őket, azaz leveszi a postfixünket. Ezt eddig csak a Type-nál vettem észre.

A kívánatos út szerintem az, ha még migráció előtt SQL Server oldalon átnevezünk minden 30-nál hosszabb illetve fenntartott azonosító nevet, illetve az azokra történő hivatkozást. Nagy adatbázis esetén nagy munka, de megkímélhetjük magunkat későbbi kellemetlen meglepetésektől. Például, ha van egy tárolt eljárásunk, ami visszaad valamilyen recordsetet, ahol bizonyos oszlopnevek vagy alias-ok 30 karakternél hosszabbak, akkor a kliens oldalon külön kell kezelni, hogy az eredményt T-SQL eljárás adta-e vissza (30+ hosszú is lehet egy oszlopnév ill. alias), vagy PL/SQL (max 30 karakter) ha szerepelnek a kliens-kódban oszlopnévre történő hivatkozások. Ez nem járható út.
A migrációval kapcsolatos előző bejegyzésben az implicit és explicit konverziókról is írtam pár gondolatot. Azóta észrevettem egy furcsaságot, amit fel is vetettem az OTN fórumban, de azóta nem kaptam rá választ.

Röviden összefoglalva: az SQL Developer SQL Serverről történő migrációkor automatikusan generál egy sqlserver_utilities nevű PL/SQL package-et, ami számos T-SQL függvény implementációját tartalmazza, köztük a CONVERT() függvényét is.

Az 1.5.1-es SQL Developerben a függvény deklarációja így néz ki:

FUNCTION convert_(p_dataType IN VARCHAR2, p_expr IN VARCHAR2, p_style IN VARCHAR2 DEFAULT NULL) RETURN VARCHAR2;

Az első paraméter szöveges módon tartalmazza az adattípust, hogy mire konvertálunk, például 'varchar'. A második paraméter maga a kifejezés, amit konvertálni szeretnénk, az utolsó pedig egy opcionális stílus paraméter, ami a különböző dátum, szám illetve egyéb formátumokat határozza meg, attól függően, hogy a konvertálandó kifejezés milyen típusú.

És itt jön elő az implicit konverzió kérdése, ugyanis ha egy dátum típusú kifejezést szeretnénk yyyy/mm/dd formátumú varchar-á konvertálni, ami T-SQL-ben így néz ki:

declare @mydate datetime
select convert(varchar, @mydate,111)

azt az sqlserver_utilities.convert_() függvény segítségével - elvileg - így tehetnénk meg:

DECLARE
cv_1 SYS_REFCURSOR;
v_mydate DATE;
BEGIN
OPEN cv_1 FOR
SELECT sqlserver_utilities.convert_('VARCHAR2(4000)', v_mydate, 111);

Ebben az esetben viszont a v_mydate először implicit módon varchar-á konvertálódik, méghozzá az alapértelmezett formátumnak megfelelően:

select value from v$NLS_PARAMETERS where parameter = 'NLS_DATE_FORMAT'

Ez nálam azzal jár, hogy elveszik minden idő jellemző (óra, perc, másodperc). Például a 108-as stílus (hh:mi:ss) a convert_() függvénnyel használhatatlan.

Ezek után már csak egy költői kérdés maradt hátra, hogy a függvény kódjában a 108-as stílushoz miért a HH12-as formátum tartozik, amikor SQL Server alatt a hh 24 órás megjelenítést jelent.

Migráció I.

Lassan egy hónapja tart a migráció előkészítése, gyűjtjük a megoldásra váró problémákat, próbálunk egy átfogó képet szerezni az előttünk álló folyamatról. Ebben a bejegyzésben megpróbálom összefoglalni, hogy mire jutottunk eddig.

Jelenleg van egy .NET-es, C#-ban írt alkalmazás (kliens), ami Microsoft SQL Server-t használ adatbázisként. A kliens oldalon szinte kizárólag tárolt eljárás hívások szerepelnek. A cél az, hogy ugyanaz a kliens szoftver képes legyen SQL Server-t is és Oracle Database-t is használni adatbázisként, azaz egy klienshez lenne két külön adatbázis-oldali rész. Ezért a migrációnak csak minimális mértékben kéne visszahatnia a kliens oldali részre. Sajnos ez közel sem olyan egyszerű. Lássuk a problémás területeket!

Adattípusok

Habár az Oracle és az SQL Server által támogatott adattípusok különböznek egymástól, szerencsére az esetek igen nagy százalékában megfeleltethetőek egymásnak. Egy Oracle white paper szerint is: "Microsoft SQL Server and Oracle9i Database data types differ. However these differences are usually small enough to have little or no impact on your applications."

Az SQL Developerbe integrált Migration Workbench (OMWB) a konvertáláskor automatikusan leképezi az SQL Server adattípusokat Oracle adattípusokra. Számunkra lényeges eltérés a (N)VARCHAR típus maximális hosszában van: míg SQL Server alatt maximum 8000 karaktert képes tartalmazni, addig Oracle alatt ez a szám 4000. De szerencsére az OMWB a 4000-nél hosszabb (N)VARCHAR típusokat automatikusan (N)CLOB-okká konvertálja.

Különböznek még a dátum típusok pontosságai: míg SQL Server alatt a DATETIME 3.33 mikroszekundum pontosságú, addig Oracle alatt másodperc pontosságú a DATETIME-nak megfelelő DATE típus. Ahol ez nem elég, ott érdemes a TIMESTAMP típust használni, ami viszont már 1 mikroszekundum pontosságú. Persze kérdés az, hogy az adatbázisunkban kismillió helyen használt DATETIME-ok közül hol elég a másodperces felbontás, és hol nem. Ebből még lehetnek bonyodalmak..

Az adattípusok konvertálásáról bővebben lehet még olvasni az SQL Developer dokumentációjában és a Migrating Applications from Microsoft SQL Server to Oracle9i Database című white paper-ben.

Implicit/explicit konverzió

Az adattípusokhoz erősen kapcsolódó téma a típusok közötti implicit vagy explicit konverziók kérdése.

SQL Server alatt az explicit típuskonverziót a CAST és CONVERT függvények valósítják meg:

CAST ( expression AS data_type [ (length ) ])
CONVERT ( data_type [ ( length ) ] , expression [ , style ] )

Forrás: http://msdn.microsoft.com/en-us/library/ms187928.aspx

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ó 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.

SQL Server alatt a lehetséges implicit és explicit típuskonverziók mátrixát az alábbi kép tartalmazza:



Ugyanez az ábra Oracle alatt picit máshogy néz ki, implicit konverziók:



explicit konverziók:



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.) mi mégis több helyen alkalmazzuk, egész egyszerűen azért, mert kényelmes. Például SQL Server a képen látható módon többféle dátumformátumot is képes kezelni:


Kavarodás itt is történhet (ÉV-HÓNAP-NAP, NAP-HÓNAP-ÉV, stb.), de ha az ember ragaszkodik valami megszokott formátumhoz, nem érheti baj. Kivéve, ha a kódot más adatbázis szerver alatt is szeretné használni :)

Oracle alatt az alapértelmezett formátumot az NLS_DATE_FORMAT paraméter tartalmazza, ami az én szerveremen:

SELECT Value FROM v$nls_parameters WHERE parameter = 'NLS_DATE_FORMAT'

VALUE
-----
RR-MON-DD

Azaz a '2008-01-01' például csak explicit módon konvertálható át DATE típusúvá. Ilyen esetekben célszerűbb a TO_DATE függvény használata. A mi esetünkben tehát minden ilyen esetben kézzel át kell írni minden implicit konverziót tartalmazó kódot explicit konverzióvá.

Ideiglenes táblák, táblaváltozók

Ideiglenes táblák SQL Server és Oracle alatt is léteznek, a különbség annyi, hogy míg SQL Server alatt a táblában tárolt adatok és maga a struktúra is ideiglenes, addig Oracle alatt a struktúra (séma) állandó.

A Migration Workbench kiszedi a tárolt eljárások kódjából az ideiglenes táblákat létrehozó DDL utasításokat, és külön hozza létre őket:

/* Translation Extracted DDL For Required Objects */
CREATE GLOBAL TEMPORARY TABLE tt_foo
(
...
);

Az ideiglenes táblákat törlő DROP utasításokból pedig ez lesz:

EXECUTE IMMEDIATE ' TRUNCATE TABLE tt_foo ';

Azaz csak a tartalmát törli a táblának, magát a táblát nem.

Egyelőre úgy tűnik, hogy számunkra ez tökéletesen megfelelő. Egyedül az zavaró, hogy (hanyagságból) rengeteg #temp nevű táblát használunk SQL Server oldalon, és mivel Oracle alatt a séma nem ideiglenes, ezért ahány helyen #temp táblát használunk, annyi darab TT_TEMP_n jön létre Oracle oldalon. Az n jelenleg olyan 30 körüli (TT_TEMP_1, TT_TEMP_2, ... TT_TEMP_30, ..).

Táblaváltozók viszont nincsenek Oracle alatt, ezek kiváltására vagy ideiglenes táblákat, vagy PL/SQL collection-öket kell majd használnunk. Azaz itt is marad a kézi átírás.

OMWB - két újabb idegesítő bug?

Folytatódik a munkahelyemen a migráció előkészítése: nézegetjük, hogy egyrészt mire képes a Migration Workbench, másrészt mik azok a lehetséges buktatók, amikre még nem gondoltunk.

Eközben találtam egy újabb bug-gyanús - igen idegesítő - hibát az OMWB-ben:

Ha valamilyen Oracle kulcsszó szerepel azonosítóként az SQL Server oldali kódban, azt okosan átalakítja, például:

select @something = date
from MyTable

ez a kód teljesen jó SQL Server alatt, ha a MyTable táblának van date nevű oszlopa. Ezt az alábbi kódra alakítja át:

SELECT date_
INTO v_something
FROM MyTable ;

A konvertálás előtt egy kollégám elkészített egy olyan sémát, ahol az összes oracle-s kulcsszó mögé egy _-t szúrt. Tehát már SQL Server oldalon az összes date-ből date_ lett, a type-okból type_, és így tovább. Viszont konvertálás után az összes type_ azonosítóra való hivatkozásból újra type lett. Ami SQL Server alatt:

select @something = type_
from MyTable

az konvertálás után:

SELECT type
INTO v_something
FROM MyTable ;

ami természetesen hibás. Ugyanez más kulcsszó esetén nem jött elő, tehát a date_-ek megmaradtak :)

A másik bug a következő: amit már egy korábbi bejegyzésben írtam is, az SQL Server megengedi, hogy egy tárolt eljárás bemenő paraméterét mezei változóként kezeljük, azaz állhat értékadás bal oldalán. Ezt az OMWB kezeli is, egy belső, rendesen deklarált változó segítségével. Ha a paraméterre való hivatkozás mindenhol pontosan ugyanaz, case sensitive-en:

create procedure foo (@bar int)
as
begin
select @Bar=5
end

itt ugye a hivatkozás megegyezik, leszámítva a kis/nagy B betűt. Az OMWB emiatt megzavarodik picit:


CREATE OR REPLACE PROCEDURE foo
(
v_bar IN NUMBER DEFAULT NULL
)
AS
BEGIN
v_Bar := 5;

END;

Sajnos ezek igen idegesítő hibák, remélem valahogy meg lehet majd őket kerülni, vagy ha nem, akkor hamar kijavítják.

UPDATE: visszaigazolt bug: 7332018: FORUMS: PROCEDURE ARGUMENTS NEED TO MATCH VARIABLE NAMES IN CASE.

OMWB - Probléma a nagy 'L' betűkkel

Találtam egy remek blogot a Migration Workbench-ről, ahol csomó ismert problémán kívül szerepel egy rövid leírás, hogy milyen lépések mentén célszerű a migrációt elvégezni. Ezt végignyomkodtam, csak próbaképp, hogy lássam, mennyire működőképes.

Miután lefutott (kb. 2-3 óra alatt) meglepődve tapasztaltam, hogy az összes nagy 'L' betűt, ami valamilyen azonosítóban (táblanév, oszlopnév, stb) szerepelt, eltűntette. Tehát pl.: az FX_Leg táblából FX_eg lett, a LoginCount oszlopnévből oginCount..

Erről nyitottam is egy fórum-topicot az OTN-en, de közben elkezdtem én is nézni, hogy mi lehet a gond.

A migráció elején, amikor létrehozzuk a repository-t (Associate Migration Repository), létrejön egy MIGRATION_TRANSFORMER nevű PL/SQL package, aminek az elején szerepel egy konstans:

C_DISALLOWED_CHARS CONSTANT NVARCHAR2(100) := ' #.@`!"%^&*()-+=[]{};:,.<>?/~''L';

Hogy miért került a felsorolás végére a nagy L betű, nem tudom. Mindenesetre, ha kiszedem onnan, és újrafordítom a csomagot, már működik :)

UPDATE: visszaigazolt hiba: We saw the English pound symbol problem, we now define the character as:

C_DISALLOWED_CHARS CONSTANT NVARCHAR2(100) := ' #.@`!"%^&*()-+=[]{};:,.<>?/~'''||UNISTR('\00A3');

Oracle SQL Developer Migration Workbench

Egyik előző bejegyzésben már írtam, hogy a migrációhoz igen nagy segítséget jelent az SQL Developer Migration Workbench része (dokumentáció itt). Van egy ún. Translation Scratch Editor része, ami két panelből áll, egyikbe beírja az ember a Transact-SQL kódot, és egy gombnyomásra megjelenik a másik oldalon a PL/SQL változata. Meglepő, de a generált kód igen nagy százalékban működik, és helyes! :)

Eddig két tipikus esetet találtunk, amikor valami bug miatt mégsem jó kódot generál. Az egyik paraméterkezeléssel kapcsolatos, a másik a T-SQL-ben létező INSERT INTO ... EXEC szerkezet átalakításával.

Az SQL Server megengedi, hogy a bemenő paramétereket mezei változóként használjunk, azaz a lenti kód helyes:

CREATE PROCEDURE foo (@dummy int = null)
AS
begin
select @dummy = 5
end

PL/SQL-ben viszont bemenő paraméter nem szerepelhet értékadás bal oldalán. A konvertáló ezt a kódot helyesen az alábbira alakítja át:

CREATE OR REPLACE PROCEDURE foo
(
iv_dummy IN NUMBER DEFAULT NULL
)
AS
v_dummy NUMBER(10,0) := iv_dummy;
BEGIN
v_dummy := 5;

END;

Azaz létrehoz egy változót is, aminek kezdeti értékül a paraméter értékét adja. Ez teljesen jó.

Viszont abban az esetben, ha az értékadást nem SELECT, hanem a SET operátorral végezzük:

CREATE PROCEDURE foo (@dummy int = null)
AS
begin
set @dummy = 5
end

már nem veszi észre, hogy valójában egy paraméterről van szó, és egy az egyben átfordítja:

CREATE OR REPLACE PROCEDURE foo
(
v_dummy IN NUMBER DEFAULT NULL
)
AS
BEGIN
v_dummy := 5;

END;

Ez a kód természetesen nem fordul le.

A másik hiba akkor fordul elő, ha T-SQL-ben meglévő INSERT INTO...EXEC struktúrát próbálja átalakítani. T-SQL-ben lehetőség van arra, hogy egy táblába szúrjuk bele egy tárolt eljárás által visszaadott eredményhalmazt (ugyanis T-SQL tárolt eljárás képes eredményhalmazokkal is visszatérni, nem úgy, mint a PL/SQL eljárások). Az alábbi T-SQL kódot:

CREATE PROCEDURE foo
AS
begin
insert into sometable (foobar)
exec sp_something
end

erre fordítja át:

CREATE OR REPLACE PROCEDURE foo
AS
v_temp SOMETABLE%ROWTYPE;
BEGIN
sp_something();
LOOP
FETCH cv_1 INTO v_temp;
EXIT WHEN cv_1%NOTFOUND;
INSERT INTO sometable VALUES v_temp;
END LOOP;
CLOSE cv_1;

END;

Maga a logika teljesen jó lenne: a tárolt eljárás egy kurzorváltozóval tér vissza, amin végigmenve egyesével beszúrjuk a sorokat a táblába. A bökkenő csak az, hogy nem deklarálja a cv_1 kurzorváltozót, így a fenti kód szintén nem fordul.

UPDATE: visszaigazolt bug No: 7335256 FORUMS: INSERT INTO .. EXEC PROCEDURE HAS UNDECLARED VARIABLES.

Ezek a hibák természetesen nem vészesek akkor, ha valaki csak pár eljárást szeretne átkonvertálni. Viszont akkor, ha több ezerről lenne szó, akkor már bosszantóak, ugyanis így nehézkes automatizálni.
A migráció kapcsán rögtön belefutottunk egy érdekes problémába: SQL Server oldalon több olyan oszlopnevet is használunk, ami Oracle alatt kulcsszó vagy fenntartott szó: name, text, comment, date, number, stb.

Úgy gondoltuk, hogy ha ezeket az oszlopneveket nagybetűsen és ""-k között hozzuk létre (és így is hivatkozunk rájuk) akkor működhet. A táblák létre is jöttek, viszont több esetben egyéb gondok léptek fel. Pl.:

CREATE TABLE asdf (
"DATE" date
);

Select * from asdf;

Ez tökéletesen lefut, viszont:

declare
v_temp number;
begin

select 1 into v_temp from dual where exists (select 1 from asdf);
end;

ez már nem:

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:

Gondoltuk, hogy baj van, akkor át kell nevezni az oszlopokat. Elkezdtem próbálkozni, hogy milyen kulcsszavakkal van még gond, és nem az összes reserved word-del van baja:

CREATE TABLE asdf (
"AUDIT" date
);

Elvileg az audit is reserved word mégsem fut hibára a fenti kódrészlet.

A következő gondolat az volt, hogy a típusnevekkel van csak gondja, de ez sem teljesen igaz, ugyanis a lenti esetben:

CREATE TABLE asdf (
"DATE" int
);

Szintén hiba nélkül lefut a PL/SQL kód, ellenben, ha "NUMBER" int van (tehát nem "NUMBER" number), akkor megint csak az említett hibára fut.

Maradt még egy utolsó kombináció, méghozzá a kulcsszavak kisbetűsen, ""-k között:

CREATE TABLE asdf (
"number" number
);

Így jelenleg minden (date, number, stb.) működik. Egyelőre. Már csak az a költői kérdés maradt hátra, hogy ebben így mi a logika?