Composition of the PL / SQL Object: Object Instantiation / Constructor Issues

advertisements

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.


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;
/