Pregunta

This is a follow-up question to Is it possible to use object composition in PL/SQL?

That question addressed how to create object types in PL/SQL which are mutually dependent (i.e. one attribute of each object is a reference to the other object).

The next problem that I've encountered has to do with the object constructors. Here's the code (logically, student exists inside of person). Also, just to preclude this in the comments, using inheritance is not an option for me.

Person Object

CREATE OR REPLACE TYPE PERSON FORCE AUTHID DEFINER UNDER MYSCHEMA.BASE_OBJECT (
    student MYSCHEMA.student,
    additional attributes...

    CONSTRUCTOR FUNCTION PERSON
    RETURN SELF AS RESULT

) NOT FINAL; 

CREATE OR REPLACE TYPE BODY PERSON
AS
    CONSTRUCTOR FUNCTION PERSON
    RETURN SELF AS RESULT IS

    BEGIN
        self.student := NEW MYSCHEMA.STUDENT(self);
        RETURN;
    END;
END;

Student Object

CREATE OR REPLACE TYPE STUDENT FORCE AUTHID DEFINER UNDER MYSCHEMA.BASE_OBJECT (
    person REF MYSCHEMA.PERSON,

    CONSTRUCTOR FUNCTION STUDENT(p_person REF MYSCHEMA.PERSON)
    RETURN SELF AS RESULT

) NOT FINAL; 

CREATE OR REPLACE TYPE BODY STUDENT
AS

    CONSTRUCTOR FUNCTION STUDENT(p_person REF MYSCHEMA.PERSON)
    RETURN SELF AS RESULT IS

    BEGIN
        self.person := p_person;
        RETURN;
    END;
END;

This code will compile without any errors except for the following line within the PERSON constructor which instantiates a STUDENT object inside of PERSON:

self.student := NEW MYSCHEMA.STUDENT(self);

Which throws the following error:

Error(22,29): PLS-00306: wrong number or types of arguments in call to 'STUDENT'

And so, dear friends, I seek your help again. I'm guessing that there is an additional parameter that is being passed implicitly into the STUDENT constructor, but that's just a guess.

Thanks.

¿Fue útil?

Solución

A REF must refer to a row. You cannot pass SELF, since it is not a reference.

One way to make this work is to create a hidden table:

create table shadow_person of person;

And every instance secrently creates a row in that table:

CONSTRUCTOR FUNCTION PERSON
RETURN SELF AS RESULT IS
    v_ref_person ref person;
BEGIN
    insert into shadow_person values(self)
    returning make_ref(shadow_person, object_id) into v_ref_person;

    self.a_student := new student(v_ref_person);

    RETURN;
END;

This seems to work, but probably has really horrible side-affects that nobody would want in a real production environment.

Here's the full script:

drop type base_object force;
drop type student force;
drop type person force;
drop table shadow_person;


create or replace type base_object is object (a varchar2(10)) not final;
/

CREATE OR REPLACE TYPE PERSON FORCE AUTHID DEFINER UNDER BASE_OBJECT (
    b varchar2(10),
    CONSTRUCTOR FUNCTION PERSON RETURN SELF AS RESULT
) NOT FINAL; 
/

CREATE OR REPLACE TYPE STUDENT FORCE AUTHID DEFINER UNDER BASE_OBJECT (
    c varchar2(10),
    a_person REF PERSON,
    CONSTRUCTOR FUNCTION STUDENT(p_person ref PERSON) RETURN SELF AS RESULT
) NOT FINAL; 
/

alter type person add attribute a_student student cascade;

create table shadow_person of person;

CREATE OR REPLACE TYPE BODY PERSON
AS
    CONSTRUCTOR FUNCTION PERSON
    RETURN SELF AS RESULT IS
        v_ref_person ref person;
    BEGIN
        insert into shadow_person values(self)
        returning make_ref(shadow_person, object_id) into v_ref_person;

        self.a_student := new student(v_ref_person);

        RETURN;
    END;
END;
/

CREATE OR REPLACE TYPE BODY STUDENT
AS
    CONSTRUCTOR FUNCTION STUDENT(p_person REF PERSON)
    RETURN SELF AS RESULT IS
    BEGIN
        self.a_person := p_person;
        RETURN;
    END;
END;
/


--Example of how to use it:
declare
    v_person person := person;
begin
    v_person.a := 'person a';
    v_person.b := 'b';
    v_person.a_student.a := 'student a';
    v_person.a_student.c := 'c';

    dbms_output.put_line(v_person.a_student.c);
end;
/

Otros consejos

Ok! Since I'm dealing with this problem myself, I want to share how we solved it!!!

First: There is no direct way to solve In-Memory circular-dependencies in Oracle - But we can cheat :-)

Just for the sake of comments - REF only works with stored objects and is not easily accessed by PL/SQL so is not an option ins most cases!

But you can get around the issue by using inheritance and casting!

Just define a base type with almost no attributes and create your types UNDER that base type. Now all your types can have attributes of the base type, since the base type doesn't have a reference to any other type - so it isn't cyclic :-)

When accessing the attributes of a subtype, you just have to cast them to the appropriate type to access their attributes and methods!

In your case just define all your attributes of Type BASE_OBJECT and you're good to go!

*I'm a little proud I have figured this out, since I didn't find anything anywhere about this!!!

CREATE OR REPLACE TYPE test_base FORCE IS OBJECT(id NUMBER) NOT FINAL;
/

CREATE OR REPLACE TYPE test_person FORCE UNDER test_base
  ( student test_base
   ,name        varchar2(100)
  );
/

CREATE OR REPLACE TYPE test_student FORCE UNDER test_base
  ( person test_base
   ,university  VARCHAR2(100)
  );
/

DECLARE
  pers test_person;
  stud test_student;
BEGIN
  pers := test_person(1, NULL, 'Mike Ross');
  stud := test_student(2, pers, 'Havard');
  pers.student := stud;

  DBMS_OUTPUT.PUT_LINE('Name: '||pers.NAME);

  DBMS_OUTPUT.PUT_LINE('University:'||TREAT(stud AS test_student).university);

END;
/
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top