14. fejezet - Objektumrelációs eszközök

Az Oracle 10g objektumrelációs adatbázis-kezelő rendszer. A relációs adatbázisok szokásos eszközrendszerét objektumorientált eszközrendszerrel bővíti ki. Az objektumorientált nyelvek osztályfogalmának az Oracle-ben az objektumtípus felel meg. Egy objektumtípus példányai az objektumok. Az objektumtípus absztrakt adattípus, az adatstruktúrát attribútumok, a viselkedésmódot metódusok realizálják.

Az objektumtípus adatbázis-objektum, és a CREATE, ALTER, DROP SQL utasításokkal kezelhető. Objektumtípus PL/SQL programban nem hozható létre, ilyen típusú eszközök viszont kezelhetők.

Az Oracle az egyszeres öröklődés elvét vallja. Az Oracle-ben nincs bezárást szabályozó eszköz, az attribútumok és metódusok nyilvánosak. Az Oracle objektumorientált eszközrendszerének használata tehát a programozóktól kellő önfegyelmet követel meg, hogy az attribútumokat csak az adott objektumtípus metódusaival és ne közvetlenül manipulálják.

Egy objektumtípus specifikációjának létrehozását az alábbi módon tehetjük meg:

CREATE [OR REPLACE] TYPE típusnév
  [AUTHID {CURRENT_USER|DEFINER}]
  {{IS|AS} OBJECT|UNDER szupertípus_név}
  ({attribútum adattípus|metódus_spec}
  [,(attribútum adattípus|metódus_spec}]…)
  [[NOT] FINAL] [[NOT] INSTANTIABLE];
metódus_spec:
  [[NOT] OVERRIDING] [[NOT] FINAL] [[NOT] INSTANTIABLE]
  [{MAP|ORDER} MEMBER fv_spec]
  {MEMBER|STATIC|CONSTRUCTOR} alprogram_spec]

Az objektumtípus törzsét a következő módon adhatjuk meg:

CREATE [OR REPLACE] TYPE BODY típusnév
  {IS|AS}
  {MEMBER|STATIC|CONSTRUCTOR} alprogramdeklaráció
  [{MEMBER|STATIC|CONSTRUCTOR} alprogramdeklaráció]…
  [{MAP|ORDER} MEMBER fv_deklaráció]
END;

Az OR REPLACE újra létrehozza az objektumtípust, ha az már létezett, anélkül hogy azt előzőleg töröltük volna. Az előzőleg az objektumtípushoz rendelt jogosultságok érvényben maradnak.

A típusnév a most létrehozott objektumtípus neve lesz.

Az AUTHID az objektumtípus metódusainak hívásakor alkalmazandó jogosultságokat adja meg. A CURRENT_USER esetén a hívó, DEFINER (ez az alapértelmezés) esetén a tulajdonos jogosultságai érvényesek. Egy altípus örökli szupertípusának jogosultsági szabályozását, azt nem írhatja felül.

Az OBJECT olyan objektumtípust definiál, amelynek nincs szupertípusa. Ez a típus egy önálló objektumtípus-hierarchia gyökértípusa lehet.

Az UNDER a megadott szupertípus altípusaként származtatja a típust, tehát beilleszti abba az objektumtípus-hierarchiába, amelyben a szupertípus is van.

Az attribútum az objektumtípus attribútumának neve. Az adattípus bármilyen beépített Oracle-típus vagy felhasználói típus lehet, az alábbiak kivételével: ROWID, UROWID, LONG, LONG RAW, BINARY_INTEGER, PLS_INTEGER, BOOLEAN, RECORD, REF CURSOR, %TYPE és %ROWTYPE és PL/SQL csomagban deklarált típus. Minden objektumtípusnak rendelkeznie kell legalább egy attribútummal (ez lehet örökölt is), az attribútumok maximális száma 1000. Az altípusként létrehozott objektumtípus attribútumainak neve nem egyezhet meg a szupertípus attribútumainak nevével. Az attribútumoknak nem adható kezdőérték és nem adható meg rájuk a NOT NULL megszorítás. Egy objektumtípus adatstruktúrája tetszőlegesen bonyolult lehet. Az attribútumok típusa lehet objektumtípus is, ekkor beágyazott objektumtípusról beszélünk. Az Oracle megengedi rekurzív objektumtípusok létrehozását.

A szintaktikus leírás végén (a kerek zárójeleken kívül) szereplő előírások az objektumtípus öröklődését és példányosítását szabályozzák. A FINAL (ez az alapértelmezés) azt adja meg, hogy az adott objektumtípusból nem származtatható altípus. Az ilyen típus az öröklődési fa levéleleme lesz. NOT FINAL esetén az adott objektumtípusból öröklődéssel származtathatunk altípust. Az INSTANTIABLE (ez az alapértelmezés) esetén az objektumtípus példányosítható, NOT INSTANTIABLE esetén absztrakt objektumtípus jön létre, ez nem példányosítható, de örököltethető. Ez utóbbi előírás kötelező, ha az objektumtípus absztrakt metódust tartalmaz. Ekkor az adott objektumtípusnak nincs konstruktora. NOT INSTANTIABLE esetén kötelező a NOT FINAL megadása is.

A metódusok lehetnek eljárások vagy függvények. Ezeket mindig az attribútumok után kell megadni. Az objektumtípus specifikációjában csak az alprogramok specifikációja szerepelhet. A formális paraméterek típusa ugyanaz lehet, mint az attribútumé. Objektumtípushoz nem kötelező metódust megadni, ilyenkor az objektumtípusnak nem lesz törzse sem. A metódusok teljes definícióját az objektumtípus törzsében kell megadni, kivéve ha egy metódusspecifikáció előtt megadjuk a NOT INSTANTIABLE előírást. Ez azt jelenti, hogy a metódus teljes deklarációját valamelyik altípus fogja megadni (absztrakt metódus). Az INSTANTIABLE esetén a törzsbeli implementáció kötelező (ez az alapértelmezés). Az OVERRIDING polimorf metódusoknál kötelező. Azt jelöli, hogy az adott metódus újraimplementálja a szupertípus megfelelő metódusát. Az alapértelmezés NOT OVERRIDING. A FINAL azt határozza meg, hogy az adott metódus nem polimorf, és az altípusokban nem implementálható újra. Az alapértelmezés a NOT FINAL.

A MEMBER metódusok az Oracle-ben példány szintű metódusok. Ezek mindig az aktuális példányon operálnak. Első paraméterük implicit módon mindig a SELF, amely az aktuális példányt hivatkozza. Hívási módjuk:

objektum_kifejezés.metódusnév(aktuális_paraméter_lista)

A SELF explicit módon is deklarálható, de mindig csak első formális paraméterként. Alapértelmezett paraméterátadási módja IN OUT, ha a metódus eljárás, és IN, ha függvény. De a SELF függvény esetén is definiálható explicit módon IN OUT átadási móddal, így a függvény megváltoztathatja a SELF attribútumainak értékét. Ha egy SQL utasításban szereplő metódushívásnál a SELF értéke NULL, akkor a metódus NULL értékkel tér vissza. Procedurális utasításban viszont a SELF_IS_NULL kivétel váltódik ki.

A STATIC metódusok az Oracle-ben osztály szintű metódusok. Nincs implicit paraméterük. Tipikus hívási módjuk:

típusnév.metódusnév(aktuális_paraméter_lista)

A CONSTRUCTOR metódus segítségével adhatunk az objektumtípushoz konstruktort. Ez egy függvény, amelynek neve kötelezően azonos a típus nevével. A konstruktor is rendelkezik az implicit SELF paraméterrel, amely ekkor IN OUT módú és így értéke akár meg is változtatható a konstruktorban. A konstruktor visszatérési típusának specifikációja kötelezően a következő:

RETURN SELF AS RESULT

A visszatérési érték mindig a SELF értéke lesz, ezért a RETURN kifejezés forma helyett üres RETURN utasítást kell használni. Az Oracle minden egyes objektumtípushoz automatikusan felépít egy alapértelmezett konstruktort. Ez egy olyan függvény, amelynek neve megegyezik az objektumtípus nevével, paraméterei pedig az attribútumok felsorolásuk sorrendjében, a megadott típussal. A konstruktor meghívása létrehoz egy új példányt, a paraméterek értékét megkapják az attribútumok és a konstruktor visszatér a példánnyal. Az alapértelmezett konstruktor felülbírálható olyan explicit konstruktor megadásával, amelynek formálisparaméter-listája megegyezik az alapértelmezett konstruktoréval, azaz a formális paraméterek neve, típusa és módja is meg kell egyezzen. Egy konstruktort mindig explicit módon kell meghívni olyan helyen, ahol függvényhívás állhat. A konstruktor hívást megelőzheti a NEW operátor, ennek használata nem kötelező, de ajánlott. A konstruktorok nem öröklődnek.

Egy objektumtípus metódusnevei túlterhelhetők. Ekkor a metódusok specifikációjában a formális paraméterek számának, sorrendjének vagy típusának különböznie kell. A konstruktor is túlterhelhető.

Egy objektumtípushoz megadható egy MAP vagy egy ORDER metódus (egyszerre mindkettő nem). Ezek függvények és az adott objektumtípus példányainak rendezésére szolgálnak. Ha sem MAP, sem ORDER metódust nem adunk meg, akkor a példányok csak egyenlőségre és nem egyenlőségre hasonlíthatók össze. Két példány egyenlő, ha a megfelelő attribútumaik értéke páronként egyenlő. Különböző típusú objektumok nem hasonlíthatók össze.

Egy altípus nem adhat meg MAP vagy ORDER metódust, ha ezek a szupertípusban szerepelnek. Viszont a MAP metódus újraimplementálható. Az ORDER metódus viszont nem.

Egy MAP metódus visszatérési típusának valamilyen skalártípusnak kell lennie. A metódust úgy kell megírni, hogy minden példányhoz a skalártípus tartományának egy elemét rendelje. Ez a leképezés hash függvényként működik, és a skalártípus elemeinek sorrendje adja a példányok sorrendjét. A MAP metódus implicit módon meghívódik két megfelelő típusú objektum összehasonlításakor, ugyanakkor explicit módon is hívható. A MAP (miután MEMBER metódus) egyetlen implicit paramétere a SELF. Ha a MAP metódust egy NULL értékű példányra hívjuk meg, akkor NULL-t ad vissza és nem fog lefutni.

Az ORDER metódus rendelkezik az implicit SELF paraméterrel és egy explicit paraméterrel, amely az adott objektumtípus egy példánya. A metódus visszatérési típusa NUMBER (vagy valamelyik altípusa), értéke negatív, nulla vagy pozitív attól függően, hogy a SELF kisebb, egyenlő vagy nagyobb az explicit módon megadott példánynál.

Ha az ORDER metódus paramétere NULL, akkor visszatérési értéke is NULL és nem fog lefutni. Az ORDER metódus is implicit módon meghívódik, ha két objektumot hasonlítunk össze, de meghívható explicit módon is.

1. példa (Verem)

CREATE OR REPLACE TYPE T_Egesz_szamok IS TABLE OF INTEGER;
/

CREATE OR REPLACE TYPE T_Verem AS OBJECT (
  max_meret INTEGER,
  top INTEGER,
  elemek T_Egesz_szamok,

  -- Az alapértelmezett konstruktort elfedjük
  CONSTRUCTOR FUNCTION T_Verem(
    max_meret INTEGER,
    top INTEGER,
    elemek T_Egesz_szamok
  ) RETURN SELF AS RESULT,

  -- Saját konstruktor használata
  CONSTRUCTOR FUNCTION T_Verem(
    p_Max_meret INTEGER
  ) RETURN SELF AS RESULT,

  -- Metódusok
  MEMBER FUNCTION tele RETURN BOOLEAN,
  MEMBER FUNCTION ures RETURN BOOLEAN,
  MEMBER PROCEDURE push (n IN INTEGER),
  MEMBER PROCEDURE pop (n OUT INTEGER)
);
/
show errors

CREATE OR REPLACE TYPE BODY T_Verem AS
  -- Kivétel dobásával meggátoljuk az alapértelmezett konstruktor hívását
  CONSTRUCTOR FUNCTION T_Verem(
    max_meret INTEGER,
    top INTEGER,
    elemek T_Egesz_szamok
  ) RETURN SELF AS RESULT IS
  BEGIN
    RAISE_APPLICATION_ERROR(-20001, 'Nem használható ez a konstruktor. '
      || 'Ajánlott konstruktor: T_Verem(p_Max_meret)');
  END T_Verem;
  CONSTRUCTOR FUNCTION T_Verem(
    p_Max_meret INTEGER
  ) RETURN SELF AS RESULT IS
  BEGIN
    top := 0;

    /* Inicializáljuk az elemek tömböt a maximális elemszámra. */
    max_meret := p_Max_meret;
    elemek := NEW T_Egesz_szamok(); -- Ajánlott a NEW használata
    elemek.EXTEND(max_meret); -- Előre lefoglaljuk a helyet az elemeknek
    RETURN;
    end T_Verem; -- hiányzik

  MEMBER FUNCTION tele RETURN BOOLEAN IS
  BEGIN
    -- Igazat adunk vissza, ha tele van a verem
    RETURN (top = max_meret);
  END tele;

  MEMBER FUNCTION ures RETURN BOOLEAN IS
  BEGIN
    -- Igazat adunk vissza, ha üres a verem
    RETURN (top = 0);
  END ures;

  MEMBER PROCEDURE push(n IN INTEGER) IS
  BEGIN
    IF NOT tele THEN
      top := top + 1; -- írunk a verembe
      elemek(top) := n;
    ELSE -- a verem megtelt
      RAISE_APPLICATION_ERROR(-20101, 'A verem már megtelt');
    END IF;
  END push;

  MEMBER PROCEDURE pop (n OUT INTEGER) IS
  BEGIN
    IF NOT ures THEN
      n := elemek(top);
      top := top - 1; -- olvasunk a veremből
    ELSE -- a verem üres
      RAISE_APPLICATION_ERROR(-20102, 'A verem üres');
    END IF;
  END pop;
END;
/
show errors

/* Használata */
DECLARE
  v_Verem T_Verem;
  i INTEGER;
BEGIN
  -- Az alapértelmezett konstruktor már nem használható itt
  BEGIN
    v_Verem := NEW T_Verem(5, 0, NULL);
  EXCEPTION
    WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('Kivétel - 1: ' || SQLERRM);
  END;
  v_Verem := NEW T_Verem(p_Max_meret => 5);
  i := 1;
  BEGIN
    LOOP
      v_Verem.push(i);
      i := i + 1;
    END LOOP;
  EXCEPTION
    WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('Kivétel - 2: ' || SQLERRM);
  END;
  BEGIN
    LOOP
      v_Verem.pop(i);
      DBMS_OUTPUT.PUT_LINE(i);
    END LOOP;
  EXCEPTION
    WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE('Kivétel - 3: ' || SQLERRM);
  END;
END;
/

/*
Kivétel - 1: ORA-20001: Nem használható ez a konstruktor. Ajánlott konstruktor: T_Verem(p_Max_meret)
Kivétel - 2: ORA-20101: A verem már megtelt
5
4
3
2
1
Kivétel - 3: ORA-20102: A verem üres

A PL/SQL eljárás sikeresen befejeződött.
*/

2. példa (Racionális számok (példát ad STATIC és MAP metódusokra))

CREATE OR REPLACE TYPE T_Racionalis_szam AS OBJECT (
  szamlalo INTEGER,
  nevezo INTEGER,
  STATIC FUNCTION lnko(x INTEGER, y INTEGER) RETURN INTEGER,
-- Megj: van alapértelmezett konstruktor is.
  CONSTRUCTOR FUNCTION T_Racionalis_szam(p_Egesz INTEGER)
  RETURN SELF AS RESULT,
  CONSTRUCTOR FUNCTION T_Racionalis_szam(p_Tort VARCHAR2)
  RETURN SELF AS RESULT,
  MAP MEMBER FUNCTION konvertal RETURN REAL,
  MEMBER PROCEDURE egyszerusit,
  MEMBER FUNCTION reciprok RETURN T_Racionalis_szam,
  MEMBER FUNCTION to_char RETURN VARCHAR2,
  MEMBER FUNCTION plusz(x T_Racionalis_szam) RETURN T_Racionalis_szam,
  MEMBER FUNCTION minusz(x T_Racionalis_szam) RETURN T_Racionalis_szam,
  MEMBER FUNCTION szorozva(x T_Racionalis_szam) RETURN T_Racionalis_szam,
  MEMBER FUNCTION osztva(x T_Racionalis_szam) RETURN T_Racionalis_szam,
  PRAGMA RESTRICT_REFERENCES (DEFAULT, RNDS,WNDS,RNPS,WNPS)
);
/
show errors

CREATE OR REPLACE TYPE BODY T_Racionalis_szam AS
  STATIC FUNCTION lnko(x INTEGER, y INTEGER) RETURN INTEGER IS
    -- Megadja x és y legnagyobb közös osztóját, y előjelével.
    rv INTEGER;
  BEGIN
    IF (y < 0) OR (x < 0) THEN
      rv := lnko(ABS(x), ABS(y)) * SIGN(y);
    ELSIF (y <= x) AND (x MOD y = 0) THEN
      rv := y;
    ELSIF x < y THEN
      rv := lnko(y, x); -- rekurzív hívás
    ELSE
        rv := lnko(y, x MOD y); -- rekurzív hívás
    END IF;
    RETURN rv;
  END;

  CONSTRUCTOR FUNCTION T_Racionalis_szam(p_Egesz INTEGER)
    RETURN SELF AS RESULT IS
  BEGIN
    SELF := NEW T_Racionalis_szam(p_Egesz, 1);
    RETURN;
  END;

  CONSTRUCTOR FUNCTION T_Racionalis_szam(p_Tort VARCHAR2)
  RETURN SELF AS RESULT IS
    v_Perjel_poz PLS_INTEGER;
  BEGIN
    v_Perjel_poz := INSTR(p_Tort, '/');
    SELF := NEW T_Racionalis_szam(
      TO_NUMBER(SUBSTR(p_Tort,1,v_Perjel_poz-1)),
      TO_NUMBER(SUBSTR(p_Tort,v_Perjel_poz+1))
    );
    RETURN;
  EXCEPTION
    WHEN OTHERS THEN
      RAISE VALUE_ERROR;
  END;

  MAP MEMBER FUNCTION konvertal RETURN REAL IS
    -- Valós számmá konvertálja a számpárral reprezentált racionális számot.
  BEGIN
    RETURN szamlalo / nevezo;
  END konvertal;

  MEMBER PROCEDURE egyszerusit IS
    -- Egyszerűsíti a legegyszerübb alakra.
    l INTEGER;
  BEGIN
    l := T_Racionalis_szam.lnko(szamlalo, nevezo);
    szamlalo := szamlalo / l;
    nevezo := nevezo / l;
  END egyszerusit;

  MEMBER FUNCTION reciprok RETURN T_Racionalis_szam IS
    -- Megadja a reciprokot.
  BEGIN
    RETURN T_Racionalis_szam(nevezo, szamlalo); -- konstruktorhívás
  END reciprok;

  MEMBER FUNCTION to_char RETURN VARCHAR2 IS
  BEGIN
    RETURN szamlalo || '/' || nevezo;
  END to_char;

    MEMBER FUNCTION plusz(x T_Racionalis_szam) RETURN T_Racionalis_szam IS
    -- Megadja a SELF + x összeget.
    rv T_Racionalis_szam;
  BEGIN
    rv := T_Racionalis_szam(szamlalo * x.nevezo + x.szamlalo * nevezo, nevezo * x.nevezo);
    rv.egyszerusit;
  RETURN rv;
  END plusz;

  MEMBER FUNCTION minusz(x T_Racionalis_szam) RETURN T_Racionalis_szam IS
  -- Megadja a SELF - x értékét.
  rv T_Racionalis_szam;
  BEGIN
    rv := T_Racionalis_szam(szamlalo * x.nevezo - x.szamlalo * nevezo, nevezo * x.nevezo);
    rv.egyszerusit;
    RETURN rv;
  END minusz;

  MEMBER FUNCTION szorozva(x T_Racionalis_szam) RETURN T_Racionalis_szam IS
    -- Megadja a SELF * x értékét.
    rv T_Racionalis_szam;
  BEGIN
    rv := T_Racionalis_szam(szamlalo * x.szamlalo, nevezo * x.nevezo);
    rv.egyszerusit;
    RETURN rv;
  END szorozva;

  MEMBER FUNCTION osztva(x T_Racionalis_szam) RETURN T_Racionalis_szam IS
    -- Megadja a SELF / x értékét.
    rv T_Racionalis_szam;
  BEGIN
    rv := T_Racionalis_szam(szamlalo * x.nevezo, nevezo * x.szamlalo);
    rv.egyszerusit;
    RETURN rv;
  END osztva;
END;
/
show errors

/* Egy apró példa: */
DECLARE
  x T_Racionalis_szam;
  y T_Racionalis_szam;
  z T_Racionalis_szam;
BEGIN
  x := NEW T_Racionalis_szam(80, -4);
  x.egyszerusit;
  y := NEW T_Racionalis_szam(-4, -3);
  y.egyszerusit;
  z := x.plusz(y);
  DBMS_OUTPUT.PUT_LINE(x.to_char || ' + ' || y.to_char
    || ' = ' || z.to_char);
  -- Alternatív konstrukotrok használata
  x := NEW T_Racionalis_szam(10);
  y := NEW T_Racionalis_szam('23 / 12');
  /*
  Hibás konstruktorhívások:
  z := NEW T_Racionalis_szam('a/b');
  z := NEW T_Racionalis_szam('1.2 / 3.12');
  */
END;
/

/* Eredmény:
-20/1 + 4/3 = -56/3

A PL/SQL eljárás sikeresen befejeződött.
*/

3. példa (Téglalap (példa ORDER metódusra))

CREATE OR REPLACE TYPE T_Teglalap IS OBJECT (
  a NUMBER,
  b NUMBER,
  MEMBER FUNCTION kerulet RETURN NUMBER,
  MEMBER FUNCTION terulet RETURN NUMBER,
  ORDER MEMBER FUNCTION meret(t T_Teglalap) RETURN NUMBER
);
/
show errors

CREATE OR REPLACE TYPE BODY T_Teglalap AS
  MEMBER FUNCTION kerulet RETURN NUMBER IS
  BEGIN
    RETURN 2 * (a+b);
  END kerulet;
íbr>   MEMBER FUNCTION terulet RETURN NUMBER IS
  BEGIN
    RETURN a * b;
  END terulet;
  ORDER MEMBER FUNCTION meret(t T_Teglalap) RETURN NUMBER IS
  BEGIN
    RETURN CASE
             WHEN terulet < t.terulet THEN -1 -- negatív szám
             WHEN terulet > t.terulet THEN 1 -- pozitív szám
             ELSE 0
      END;
  END meret;
END;
/
show errors

DECLARE
  v_Teglalap1 T_Teglalap := T_Teglalap(10, 20);
  v_Teglalap2 T_Teglalap := T_Teglalap(22, 10);
  PROCEDURE kiir(s VARCHAR2, x T_Teglalap, y T_Teglalap) IS
  BEGIN
  DBMS_OUTPUT.PUT_LINE(s
    || CASE
        WHEN x < y THEN 'x < y'
        WHEN x > y THEN 'x > y'
        ELSE 'x = y'
    END);
  END kiir;
BEGIN
  kiir('1. ', v_Teglalap1, v_Teglalap2);
  kiir('2. ', v_Teglalap2, v_Teglalap1);
  kiir('3. ', v_Teglalap1, v_Teglalap1);
END;
/

/* Eredmény:
1. x < y
2. x > y
3. x = y

A PL/SQL eljárás sikeresen befejeződött.
*/

4. példa (Öröklődés – T_Arucikk, T_Toll és T_Sorkihuzo típusok (példát ad típus származtatására, absztrakt típusra, ORDER metódusra, objektum típusú attribútumra, valamint polimorfizmusra).)

CREATE TYPE T_Arucikk IS OBJECT (
  leiras VARCHAR2(200),
  ar NUMBER,
  mennyiseg NUMBER,
  MEMBER PROCEDURE felvesz(p_mennyiseg NUMBER),
  MEMBER PROCEDURE elad(p_Mennyiseg NUMBER, p_Teljes_ar OUT NUMBER),
  MAP MEMBER FUNCTION osszertek RETURN NUMBER
) NOT FINAL NOT INSTANTIABLE;
/

CREATE OR REPLACE TYPE BODY T_Arucikk AS
  MEMBER PROCEDURE felvesz(p_Mennyiseg NUMBER) IS
  -- Raktárra veszi a megadott mennyiségű árucikket
  BEGIN
    mennyiseg := mennyiseg + p_Mennyiseg;
  END felvesz;
  MEMBER PROCEDURE elad(p_Mennyiseg NUMBER, p_Teljes_ar OUT NUMBER) IS
  /* Csökkenti az árucikkből a készletet a megadott mennyiséggel.
  Ha nincs elég árucikk, akkor kivételt vált ki.
  A p_Teljes_ar megadja az eladott cikkek (esetlegesen
  kedvezményes) fizetendő árát. */
  BEGIN
    IF mennyiseg < p_Mennyiseg THEN
      RAISE_APPLICATION_ERROR(-20101, 'Nincs elég árucikk');
    END IF;
    p_Teljes_ar := p_Mennyiseg * ar;
    mennyiseg := mennyiseg - p_Mennyiseg;
  END elad;
  MAP MEMBER FUNCTION osszertek RETURN NUMBER IS
  BEGIN
    RETURN ar * mennyiseg;
  END osszertek;
END;
/
show errors

CREATE OR REPLACE TYPE T_Kepeslap UNDER T_Arucikk (
meret T_Teglalap
) NOT FINAL;
/
show errors

CREATE OR REPLACE TYPE T_Toll UNDER T_Arucikk (
  szin VARCHAR2(20),
  gyujto INTEGER, -- ennyi darab együtt kedvezményes
  kedvezmeny REAL, -- a kedvezmény mértéke, szorzó
  OVERRIDING MEMBER PROCEDURE elad(p_Mennyiseg NUMBER,
  p_Teljes_ar OUT NUMBER),
  OVERRIDING MAP MEMBER FUNCTION osszertek RETURN NUMBER
) NOT FINAL;
/
show errors

CREATE OR REPLACE TYPE BODY T_Toll AS
  OVERRIDING MEMBER PROCEDURE elad(p_Mennyiseg NUMBER,
  p_Teljes_ar OUT NUMBER) IS
  BEGIN
    IF mennyiseg < p_Mennyiseg THEN
      RAISE_APPLICATION_ERROR(-20101, 'Nincs elég árucikk');
    END IF;
    mennyiseg := mennyiseg - p_Mennyiseg;
    p_Teljes_ar := p_Mennyiseg * ar;
    IF p_Mennyiseg >= gyujto THEN
      p_Teljes_ar := p_Teljes_ar * kedvezmeny;
    END IF;
  END elad;
  OVERRIDING MAP MEMBER FUNCTION osszertek RETURN NUMBER IS
  v_Osszertek NUMBER;
  BEGIN
    v_Osszertek := mennyiseg * ar;
    IF mennyiseg >= gyujto THEN
      v_Osszertek := v_Osszertek * kedvezmeny;
    END IF;
    return v_Osszertek;
  END osszertek;
END;
/
show errors

CREATE OR REPLACE TYPE T_Sorkihuzo UNDER T_Toll (vastagsag NUMBER);
/

/* Egy apró példa */
DECLARE
  v_Toll T_Toll;
  v_Ar NUMBER;
BEGIN
  v_Toll := T_Toll('Golyóstoll', 150, 55, 'kék', 8, 0.95);
  v_Toll.elad(24, v_Ar);
  DBMS_OUTPUT.PUT_LINE('megmaradt mennyiség: ' || v_Toll.mennyiseg
    || ', eladási ár: ' || v_Ar);
END;
/

/* Eredmény:
megmaradt mennyiség: 31, eladási ár:3420

A PL/SQL eljárás sikeresen befejeződött.
*/

A beágyazott tábla és a dinamikus tömb típusok az Oracle-ben speciális objektumtípusnak tekinthetők. Egyetlen attribútumuk van, amelynek típusát megadjuk a létrehozásnál. Ezen kollekciótípusoknál nem adhatunk meg metódusokat, de az Oracle implicit módon felépít hozzájuk kezelő metódusokat. Öröklődés nem értelmezhető közöttük, de a példányosítás létezik és konstruktor is tartozik hozzájuk. Ezen típusok tárgyalását lásd a 12. fejezetben.

Egy létező objektumtípus csak akkor cserélhető le CREATE OR REPLACE TYPE utasítással, ha nincsenek típus- vagy táblaszármazékai. A következő utasítás viszont mindig használható egy létező objektumtípus definíciójának megváltoztatására:

ALTER TYPE típusnév
{COMPILE [DEBUG] [{BODY|SPECIFICATION}] [REUSE SETTINGS]|
  REPLACE [AUTHID {CURRENT_USER|DEFINER}]
  AS OBJECT({attribútum adattípus| metódus_spec}
  [,{attribútum adattípus|metódus_spec}]…)|
  {[NOT]{INSTANTIABLE|FINAL}|
  {ADD|DROP} metódus_fej[,{ADD|DROP} metódus_fej]…|
  {{ADD|MODIFY} ATTRIBUTE{attribútum[adattípus]|
  (attribútum adattípus
  [,attribútum adattípus]…)}|
  DROP ATTRIBUTE {attribútum|(attribútum[,attribútum]…)}}
  [{INVALIDATE|
  CASCADE [[NOT] INCLUDING TABLE DATA]
  [[FORCE] EXCEPTIONS INTO tábla]}]};

Az utasítás a típusnév nevű objektumtípust módosítja. A COMPILE újrafordítja a típus törzsét (BODY esetén), specifikációját (SPECIFICATION esetén), vagy mindkettőt. Az újrafordítás törli a fordító beállításait, majd a fordítás végén újra beállítja azokat. A REUSE SETTINGS megadása esetén ez elmarad. A DEBUG megadása esetén a fordító használja a PL/SQL nyomkövetőjét.

A REPLACE utasításrész lecseréli az objektumtípus teljes attribútum- és metóduskészletét a megadott attribútum- és metóduskészletre. A részleteket a CREATE TYPE utasításnál találjuk.

Az ADD|DROP metódus_fej új metódust ad a típushoz, illetve egy meglévőt töröl. Szuper típustól örökölt metódus nem törölhető. Hozzáadásnál csak a metódus specifikációját szerepeltetjük, a metódus implementációját egy CREATE OR REPLACE TYPE BODY utasításban kell megadni.

Az ADD ATTRIBUTE egy új attribútumot ad az objektumtípushoz, ez az eddigi attribútumok után fog elhelyezkedni. A MODIFY ATTRIBUTE módosít egy létező skalár típusú attribútumot (például megváltoztatja valamely korlátozását). A DROP ATTRIBUTE töröl egy attribútumot.

Az INVALIDATE azt írja elő, hogy az Oracle mindenféle ellenőrzés nélkül érvénytelenítsen minden, a módosított objektumtípustól függő adatbázis-objektumot.

A CASCADE megadása esetén az objektumtípus változásainak következményei automatikusan átvezetésre kerülnek az összes függő típusra és táblára. Az INCLUDING TABLE DATA (ez az alapértelmezés) hatására a függő táblák megfelelő oszlopainak értékei az új típusra konvertálódnak. Ha az objektumtípushoz új attribútumot adtunk, akkor az a táblában NULL értéket kap. Ha töröltünk egy attribútumot, akkor az a tábla minden sorából törlődik. NOT INCLUDING TABLE DATA esetén az oszlop értékei változatlanok maradnak, sőt le is kérdezhetők.

Az Oracle a CASCADE hatására hibajelzéssel félbeszakítja az utasítást, ha a függő típusok vagy táblák módosítása során hiba keletkezik. Ez elkerülhető a FORCE megadásával, ekkor az Oracle ignorálja a hibákat és tárolja azokat a megadott tábla soraiban.

5. példa

CREATE TYPE T_Obj IS OBJECT (
mezo1 NUMBER,
MEMBER PROCEDURE proc1
);
/

CREATE TYPE BODY T_Obj AS
  MEMBER PROCEDURE proc1 IS
  BEGIN
  DBMS_OUTPUT.PUT_LINE('proc1');
  END proc1;
END;
/

/* Nem lehet úgy törölni, hogy egy attribútum se maradjon. */
ALTER TYPE T_Obj DROP ATTRIBUTE mezo1;

/*
ALTER TYPE T_Obj DROP ATTRIBUTE mezo1
*
Hiba a(z) 1. sorban:
ORA-22324: a módosított típus fordítási hibákat tartalmaz.
ORA-22328: A(z) "PLSQL"."T_OBJ" objektum hibákat tartalmaz.
PLS-00589: nem található attribútum a következő objektumtípusban: "T_OBJ"
*/

ALTER TYPE T_Obj ADD ATTRIBUTE mezo2 DATE;
/* A törzs validálása */

ALTER TYPE T_Obj COMPILE DEBUG BODY REUSE SETTINGS;

ALTER TYPE T_Obj ADD
FINAL MEMBER PROCEDURE proc2;

/* A törzset nem elég újrafordítani… */
ALTER TYPE T_Obj COMPILE REUSE SETTINGS;

/* Figyelmeztetés: A típus módosítása fordítási hibákkal fejeződött be. */

/* .. ezért cseréljük a törzs implementációját */
CREATE OR REPLACE TYPE BODY T_Obj AS
  MEMBER PROCEDURE proc1 IS
  BEGIN
  DBMS_OUTPUT.PUT_LINE('proc1');
  END proc1;
  FINAL MEMBER PROCEDURE proc2 IS
  BEGIN
  DBMS_OUTPUT.PUT_LINE('proc2');
  END proc2;
END;
/

Egy objektumtípus megváltoztatása a következő lépésekből áll:

  1. Adjunk ki ALTER TYPE utasítást a típus megváltoztatására. Az ALTER TYPE alapértelmezett működésében – ha nem adunk meg semmilyen opciót – ellenőrzi, hogy létezik-e a típustól függő objektum. Ha létezik ilyen, akkor az utasítás sikertelen lesz. Az opcionális alapszavak segítségével megadhatjuk, hogy a változtatások érvényesítése megtörténjen a típustól függő objektumokon és táblákon.

  2. Használjuk a CREATE OR REPLACE TYPE BODY utasítást, hogy aktualizáljuk a típus törzsét.

  3. Adjunk ki a függő táblákra ALTER TABLE UPGRADE INCLUDING DATA utasítást, ha a táblában szereplő adatokat nem aktualizáltuk az ALTER TYPE utasítással.

  4. Változtassuk meg a függő PL/SQL programegységeinket, hogy azok konzisztensek legyenek a változtatott típussal.

  5. Aktualizáljuk az adatbázison kívüli, a típustól függő alkalmazásainkat.

Objektumtípus törlésére szolgál a következő utasítás:

DROP TYPE típusnév [{FORCE|VALIDATE}];

A FORCE megadása lehetővé teszi szupertípus törlését. Ekkor az összes altípus érvénytelenné válik. A FORCE UNUSED minősítéssel látja el azokat az oszlopokat, amelyek típusnév típusúak, így ezek elérhetetlenné válnak.

VALIDATE esetén az Oracle csak akkor hajtja végre a törlést, ha az adott objektumtípus példányai nincsenek elhelyezve valamely szupertípusának megfelelő típusú oszlopban.

6. példa

DROP TYPE T_Obj;

Egy objektumtípus létrehozása után az a szokásos módon használható deklarációs részben bármely programozási eszköz típusának megadásához. Egy objektum típusú programozási eszköz értéke a konstruktorral történő inicializálás előtt NULL.

7. példa

DECLARE
  x T_Racionalis_szam;
BEGIN
  IF x IS NULL THEN
    DBMS_OUTPUT.PUT_LINE('NULL');
  END IF;
END;
/

/*
NULL

A PL/SQL eljárás sikeresen befejeződött.
*/

Egy nem inicializált objektum attribútumainak az értéke NULL, ha azokra egy kifejezésben hivatkozunk. Az ilyen attribútumnak történő értékadás viszont az ACCESS_INTO_NULL kivételt váltja ki. Ha paraméterként adunk át ilyen objektumot, akkor IN mód esetén attribútumainak értéke NULL lesz, OUT és IN OUT estén pedig az ACCESS_INTO_NULL kivétel következik be, ha megpróbálunk az attribútumoknak értéket adni. Bekapcsolt PL/SQL optimalizáló mellett a kivétel kiváltása elmaradhat.

8. példa

DECLARE
  x T_Teglalap;
BEGIN
  DBMS_OUTPUT.PUT_LINE(x.terulet);
EXCEPTION
  WHEN SELF_IS_NULL THEN
    DBMS_OUTPUT.PUT_LINE('SELF_IS_NULL');
END;
/

/*
SELF_IS_NULL

A PL/SQL eljárás sikeresen befejeződött.
*/

9. példa

DECLARE

  x T_Racionalis_szam;

BEGIN

  DBMS_OUTPUT.PUT_LINE('.' || x.szamlalo || '.');

EXCEPTION

  WHEN ACCESS_INTO_NULL THEN

    DBMS_OUTPUT.PUT_LINE('ACCESS_INTO_NULL');

END;

/

/*

..

A PL/SQL eljárás sikeresen befejeződött.

*/

10. példa

-- Kikapcsoljuk az optimalizálót.
ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL=0;
CREATE OR REPLACE PROCEDURE proc_access_into_null_teszt
IS
  x T_Racionalis_szam := NEW T_Racionalis_szam(5, 6);
  y T_Racionalis_szam;
BEGIN
  x.szamlalo := 10;
  DBMS_OUTPUT.PUT_LINE('.' || x.szamlalo || '.');
  y.szamlalo := 11;
  DBMS_OUTPUT.PUT_LINE('.' || y.szamlalo || '.');
EXCEPTION
  WHEN ACCESS_INTO_NULL THEN
    DBMS_OUTPUT.PUT_LINE('ACCESS_INTO_NULL');
END proc_access_into_null_teszt;
/

EXEC proc_access_into_null_teszt;

/*
.10.
ACCESS_INTO_NULL

A PL/SQL eljárás sikeresen befejeződött.
*/

-- Visszakapcsoljuk az optimalizálót.

ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL=2;

ALTER PROCEDURE proc_access_into_null_teszt COMPILE;

-- Ha így futtatjuk újra, az értékadás megtörténik

-- egy az optimalizáló által bevezetett ideiglenes memóriabeli

-- változóban, mintha y.szamláló egy külön INTEGER változó lenne.

EXEC proc_access_into_null_teszt;

/*

.10.

.11.

A PL/SQL eljárás sikeresen befejeződött.

*/

11. példa

DECLARE
  x T_Racionalis_szam;
  y T_Racionalis_szam;
  PROCEDURE kiir(z T_Racionalis_szam) IS
  BEGIN
  DBMS_OUTPUT.PUT_LINE('.' || z.szamlalo || '.');
  END kiir;
  PROCEDURE init(
  p_Szamlalo INTEGER,
  p_Nevezo INTEGER,
  z OUT T_Racionalis_szam) IS
  BEGIN
  z := NEW T_Racionalis_szam(p_Szamlalo, p_Nevezo);
  z.egyszerusit;
  END init;
  PROCEDURE modosit(
  p_Szamlalo INTEGER,
  p_Nevezo INTEGER,
  z IN OUT T_Racionalis_szam) IS
  BEGIN
  z.szamlalo := p_Szamlalo;
  z.nevezo := p_Nevezo;
  z.egyszerusit;
  END modosit;
BEGIN
  x := NEW T_Racionalis_szam(1, 2);
  kiir(x);
  init(2, 3, x);
  kiir(x);
  kiir(y);
  init(3, 4, y);
  kiir(y);
  modosit(4, 5, x);
  kiir(x);
  y := NULL;
  modosit(5, 6, y);
  kiir(y);
EXCEPTION
  WHEN ACCESS_INTO_NULL THEN
  DBMS_OUTPUT.PUT_LINE('Error: ACCESS_INTO_NULL');
  DBMS_OUTPUT.PUT_LINE(' SQLCODE:' || SQLCODE);
  DBMS_OUTPUT.PUT_LINE(' SQLERRM:' || SQLERRM);
  DBMS_OUTPUT.PUT_LINE('Error backtrace:');
  DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
/

/*
.1.
.2.
..
.3.
.4.
Error: ACCESS_INTO_NULL
SQLCODE:-6530
SQLERRM:ORA-06530: Inicializálatlan összetett objektumra való hivatkozás
Error backtrace:
ORA-06512: a(z) helyen a(z) 24. sornál
ORA-06512: a(z) helyen a(z) 43. sornál

A PL/SQL eljárás sikeresen befejeződött.
*/

Egy objektumtípus attribútumaira az adott típus példányainak nevével történő minősítéssel hivatkozhatunk.

12. példa

DECLARE
  v_Arucikk T_Arucikk;
  v_Kepeslap T_Kepeslap;
  
  v_Terulet NUMBER;
BEGIN
  v_Toll := NEW T_Toll('Golyóstoll', 150, 55, 'kék', 8, 0.95);
  DBMS_OUTPUT.PUT_LINE('Tollak mennyisége: ' || v_Toll.mennyiseg);
  -- Megváltoztatjuk az árat:
  v_Toll.ar := 200;
  v_Kepeslap := NEW T_Kepeslap('Boldog szülinapot lap',
  80, 200, NEW T_Teglalap(10, 15));
  .
  .
  .

Az attribútumnevek láncolata beágyazott objektum esetén megengedett.

13. példa

.
  .
  .
  -- Hivatkozás kollekció attribútumára
  v_Terulet := v_Kepeslap.meret.terulet;
  DBMS_OUTPUT.PUT_LINE('Egy képeslap területe: ' || v_Terulet);
  .
  .
  .

Egy konstruktor hívása kifejezésben megengedett, a konstruktor hívásánál a függvényekre vonatkozó szabályok állnak fönn. Ilyenkor azonban a NEW operátor nem használható.

14. példa (A blokk eredménye)

.
  .
  .
  -- Konstruktor kifejezésben
  DBMS_OUTPUT.PUT_LINE('Egy légből kapott tétel összértékének a fele: '
  || (T_Kepeslap('Üdvözlet Debrecenből',
  120, 50, NEW T_Teglalap(15, 10)).osszertek / 2) );
END;
/

/*
Tollak mennyisége: 55
Egy képeslap területe: 150
Egy légből kapott tétel összértékének a fele: 3000

A PL/SQL eljárás sikeresen befejeződött.
*/