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

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.

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.