typeof Equivalent for Object Types in PL/SQL - sql

I'm trying to use OOP and TDD inside of Oracle. Yes, I know that sounds crazy. And, I need some advice.
I'm writing a test for the following constructor method (simplified for the purposes of this question):
CONSTRUCTOR FUNCTION PERSON(p_pidm NUMBER, p_throw_exception NUMBER DEFAULT 0, p_program_id NUMBER DEFAULT 0)
RETURN SELF AS RESULT IS
BEGIN
-- Attach constructor parameters to internal attributes
self.throw_exception := p_throw_exception;
self.program_id := p_program_id;
-- TESTING STUDENT INSTANTIATION
self.student := NEW STUDENT(self.a_pidm);
RETURN;
END;
In the corresponding test, I'll need to verify that self.student is set to a valid instance of STUDENT. In other languages, I do this with a typeof method, but I'm not aware of one in PL/SQL.
So, the question is, does anyone know a function/procedure that I can pass a user-defined type into and get back its class/type name?
Thanks.

You probably want to use the IS OF <<type>> syntax.
Something like
IF l_variable IS OF( student )
THEN
<<do something>>
END IF;

Related

Ada - how to initialize a limited tagged type that contains File_Type members?

In the following code Mix_Card_Reader inherits from Mix_IO_Device, the latter being an abstract tagged record.
Previously it contained one Positive and two Stream_Access members. I'd like to alter the code so that it uses File_Type members instead.
The reason for that is that I want each instance of this type to be able to open and close its files as and when required, or not at all if need be.
The problem is that I cannot initialise this inheriting type because File_Type is a limited type. How can I write my Create_Mix_Card_Reader function to allow this?
.ads...
type Mix_IO_Device is abstract tagged limited
record
Block_Size : Positive;
Input_File : File_Type;
Output_File : File_Type;
end record;
type Mix_Card_Reader is new Mix_IO_Device with null record;
.adb...
function Create_Mix_Card_Reader return Mix_IO_Device_Access is
Ret : Mix_IO_Device_Access := new Mix_Card_Reader'(16, null, null);
begin
return Ret;
end Create_Mix_Card_Reader;
GNAT is complaining that I cannot pass null, null into the pair of File_Type members because they are not compatible of course, the nulls are a left-over from when this used to have Stream_Access members. It seems that I have to pass something in here but I don't want to have to prematurely open the files simply to placate the compiler.
What to do?
Edit:
I have a couple of obvious options:
use access File_Type instead (but I still have to maintain the opening/closing of the files elsewhere).
store all the File_Type objects in an array separately and just refer to them using Streams as before but this seems messy.
This should do the trick:
function Create_Mix_Card_Reader return Mix_IO_Device_Access is
Ret : Mix_IO_Device_Access := new Mix_Card_Reader'(
16, Input_Type => <>, Ouptut_Type => <>);
begin
return Ret;
end Create_Mix_Card_Reader;
The box notation is a placeholder for the default value. You need at least Ada 2005 to use it in aggregates and must not use positional notation, details are explained in the Ada 2005 Rationale. You can shorten the two assignments to others => <> if you want.
You don’t really need to initialize the File_Type variables, since they start off initialized (but not opened).
I got the impression that you didn’t start off using an access type? Try this (not an answer to the question as posed, but may still be useful):
with Ada.Text_IO; use Ada.Text_IO;
package Wossname is
type Mix_IO_Device is abstract tagged limited
record
Block_Size : Positive;
Input_File : File_Type;
Output_File : File_Type;
end record;
type Mix_Card_Reader is new Mix_IO_Device with null record;
function Create_Mix_Card_Reader return Mix_IO_Device'Class;
end Wossname;
I’m not 100% sure of the exact legality here, but I think this is "initializing in place":
package body Wossname is
function Create_Mix_Card_Reader return Mix_IO_Device'Class is
begin
return Ret : Mix_Card_Reader do
Ret.Block_Size := 16;
end return;
end Create_Mix_Card_Reader;
end Wossname;
and as you can see it compiles (and runs!) OK.
procedure Wossname.Test is
Reader : Mix_IO_Device'Class := Create_Mix_Card_Reader;
begin
begin
Create (Reader.Output_File, Name => "wossname.out", Mode => Out_File);
exception
when Use_Error =>
Open (Reader.Output_File, Name => "wossname.out", Mode => Out_File);
end;
Put (Reader.Output_File, "hi!");
Close (Reader.Output_File);
end Wossname.Test;

How to handle parameters in SQL PACKAGE?

I have a table which has columns id, name and password.
I made also a SQL PACKAGE for that which looks like (I omitted Package header, since to emphasis my point more clear):
create or replace PACKAGE BODY MEMBER
is
FUNCTION createWith(v_id, v_name, v_password)
return Number
is
BEGIN
Insert into tbl_member(id, name, password)
Values(v_id, v_name, v_password);
return SQL%ROWCOUNT ;
END createWith;
However, since name column is not necessary column(has no 'not null' option), I sometimes I pass only two parameters(id and password). As far as I know, if the numbers of parameter I sending and numbers of declared parameters in function do not match, it should be 'wrong type or number exception', but It works well.
The thing I wonder is how it can automatically handle empty parameter.
Is anyone who knows useful link or its logic, explain this.
//=============================================================
This is first addition
First of all, I guess my question was not clear enough.
So I going to try again.
step 0, make a table with no constrains and options.(also no default values)
step 1, this is the function header.
function creatWith(v_id in varchar2, v_name in varchar2, v_password in varchar2) return number;
step 2, I call this Package in java(it is web application based on Springframework. However, my company call this a 'solution', so I can not open source code).
However, the method which mapped with this Function has only two parameters. like
public void insert(String id, String pw);
step 3, This is the point of question. It occurs an error or not? In my case, it works. But I do not know My company solution handle or Oracle DB automatically does this.
If Oracle does have, can I get some documents for this policy?
It seems that Oracle tries to use "NULL" values for empty parameters. Change the field to NOT NULL and it will start to return an error.
Try declaring a DEFAULT attribute, like:
FUNCTION createWith(
v_id IN NUMBER,
v_name IN VARCHAR2 DEFAULT 'empty',
v_password IN VARCHAR2)
With this, it will write 'empty' for the Name field when the parameter is empty.
For Oracle Database, declare DEFAULT value in the function definition.
FUNCTION createWith(v_id IN NUMBER,
v_name IN VARCHAR2 DEFAULT 'N.A.',
v_password IN VARCHAR2);

Why is the type of an object a pointer to tagged typed, and not a tagged type?

I have 3 files: other.ads, other.adb, and test.adb.
other.ads:
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
procedure Say (T : in Thing);
end Other;
other.adb: not shown for brevity, and not necessary for the example.
test.adb:
with Other;
procedure Test is
T : Other.Thing := new Other.Thing;
begin
T.Stuff := "test";
T.Say;
end Test;
I get this error:
test.adb:4:23: expected type "Thing" defined at other.ads:2
test.adb:4:23: found type access to "Thing" defined at line 4
If I have these files instead:
other.ads:
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
type Ref is access all Thing;
procedure Say (T : in Thing);
end Other;
test.adb:
with Other;
procedure Test is
T : Other.Ref := new Other.Thing;
begin
T.Stuff := "test";
T.Say;
end Test;
Then it compiles and runs fine.
Why can't I specify new Other.Thing to be of type Other.Thing?
If you declare a variable in Java, the way you set it up depends on whether the variable’s type is primitive. int foo reserves space for an integer. Thing foo, on the other hand, reserves space for a reference (pointer) to a Thing, and you use new to reserve space for the Thing itself; Thing foo = new Thing.
Ada isn’t like that (nor is C or C++, for that matter); when you say Foo : Thing, the compiler reserves space for the Thing right there (probably on the stack). So your first example can just read
T : Other.Thing;
begin
T.Stuff := “test”;
The time when you use the new keyword in Ada is when you need an access value for some reason, as you have forced in your second example; you’ve declared T as Ref, which is declared as access all Thing.
Note that in your second example, when you say
T.Stuff := “test”;
this is actually shorthand for
T.all.Stuff := “test”;
and some people like to put the .all in explicitly.
The type of T is an access type that points to an object of type Other.Thing because you specified a allocator using new. You can also simply declare an object of type Other.Thing an initialize it using an aggregate.
with Ada.Text_IO;
procedure Test is
package Other is
type Thing is tagged record
Stuff : String(1..4);
end record;
end Other;
S : Other.Thing := (Stuff => "test");
T : access Other.Thing := new Other.Thing;
begin
Ada.Text_IO.Put_Line(S.Stuff);
T.Stuff := "test";
Ada.Text_IO.Put_Line(T.Stuff);
end Test;

Upcasting accesses

Let's say I have a Planet:
type Planet is tagged null record;
type Planet_Ref is access Planet'class;
Now I subclass it:
type Habitable_Planet is new Planet with null record;
type Habitable_Planet_Ref is access Habitable_Planet'class;
Now I define some variables:
p: Planet_Ref := Make_Planet;
hp: Habitable_Planet_Ref := Make_Habitable_Planet;
I would naively expect that assigning p := hp would work, because a Habitable_Planet is a subclass of Planet. But of course that won't work because every type defined with type is distinct and doesn't interoperate with any other type.
So I'd expect to have to declare Habitable_Planet_Ref to be a subtype of Planet_Ref to make this work. But the syntax doesn't seem to allow for this.
How do I make this work?
(Yes, I know I can use an explicit view conversion to cast a Habitable_Planet_Ref to a Planet_Ref, but that's really ugly and I'd like to avoid it.)
Ada recognizes types by name, so indeed you would need a view conversion here.
But if you are using Ada 2005, you can use anonymous access types instead. For instance:
hp: access Habitable_Planet'Class := Make_Habitable_Planet;
p: access Planet'Class := hp; -- valid with anonymous access types
One the drawbacks of using anonymous access types is that the code is more
verbose (although in general you would not use them for local variables, but
as parameters to subprograms or as fields in a (tagged) record.
They also can't be used with Unchecked_Deallocation. In fact, I personally often
use them exactly because of that: when I have a field in a record which is of an
anonymous access type, I know that the record does not "own" the accessed data,
and therefore it should not free it (in fact, I would have to write some convoluted
code to free them).
And of course as per your request the result for type matching are slightly more
relax, which is nice too.
ajb is correct in his comment. Ada is too strict for many practices you might be used to in other languages. An alternative would be to just not use objects and instead just simple records or discriminate records. I understand this may not be what you are looking for, but from my experience more can be done with less lines of code and the solution tends to me easier to understand.
Simple record
--...
type Rec_Planet is record
--.. stuff
end record;
--...
type Rec_Habitable_Planet is record
Planet : Rec_Planet := (others => <>);
--.. stuff
end record;
Discriminate record
type Enum_Planet is (Normal_Planet, Habitable_Planet);
type Rec_Planet(Kind : Enum_Planet := Normal_Planet) is record
-- rec_Planet stuff..
case Kind is
when Habitable_Planet => -- Rec_Habitable_Planet stuff
when others => null;
end case;
end record;
So #manuBriot gave me the answer I needed, but there were some other things I was doing wrong in my question which I should clarify because they'll confuse anyone else reading this question.
I was confusing the issue by using accesses. From Ada's point of view all accesses defined with type are distinct, so it never gets as far as looking at what the access is pointing at; it just disallows the assignment.
However, Ada does support implicit upcasting of class-wide types (and also discrete types, where an instance of a subtype will get implicitly cast to its parent type --- implement ALL the class hierarchies! But that's not really relevant here.) Example here:
With Ada.Text_IO; Use Ada.Text_IO;
With Ada.Integer_Text_IO; Use Ada.Integer_Text_IO;
procedure Prog is
package Superclass is
type Class is tagged record
null;
end record;
procedure Announce(self: in out Class);
subtype Var is Class'class;
end;
package body Superclass is
procedure Announce(self: in out Class)
is
begin
Put_Line("I am the superclass");
end;
end;
package Subclass is
type Class is new Superclass.Class with null record;
procedure Announce(self: in out Class);
end;
package body Subclass is
procedure Announce(self: in out Class)
is
begin
Put_Line("I am the subclass");
end;
end;
osuper: Superclass.Class;
osub: Subclass.Class;
vsuper: Superclass.Var := osuper;
vsub: Superclass.Var := osub; -- implicit upclass here
begin
vsuper.Announce;
vsub.Announce;
end;
(It's in ideone here: http://ideone.com/M79l0a Interesting sidenote. If you define subtype Var is Superclass.Var in the Prog package, and then use Var in the definitions of vsuper and vsub, ideone's Ada compiler crashes.)
Of course, like all indefinite types, once the variable has been initialised then its type cannot be changed. So I can assign any Subclass.Object to vsub, but I can't assign a Superclass.Object to it. And of course I'm physically copying the object rather than referring to an object elsewhere.
Implicitly upcasting accesses to class-wide types should be safe. Because assigning to a class-wide type does a runtime instance check to make sure that the physical type of the objects are compatible, it ought not to be possible to accidentally corrupt objects like you can in C++ --- see Overwriting an instance of a subclass with an instance of a superclass, for example. Therefore assigning to a dereferenced access shouldn't be a problem. However, it's 2100 at night and my brain has turned to sludge, so it's entirely possible that I'm missing something here. (Although given that when using anonymous accesses there aren't any problems, I suspect not.) Elucidation welcome...

PL/SQL private object method

I'm a bit new to Oracle's PL/SQL (using 10g), I was wondering if there's a way to make a private method in an object type, as is often done for private helper methods in other languages (Java, C++, C#, etc...). I know it is possible to make private methods in packages, but I can't seem to find a way to do this for object types. I keep getting compiler errors telling me:
Error: PLS-00539: subprogram 'FOO' is declared in an object type body
and must be defined in the object type specification.
Ok, here's a potential solution that I tested very briefly, and it seems to work so far:
Create a parent object type that will marked as NOT FINAL and NOT INSTANTIABLE and then put all the private code in there. The private methods won't be truly private, but putting them in a type that is not final and not instantiable prevents them from being called. In the instantiable subtype, reference the "private" methods in the supertype through SELF.
Example:
create or replace type PrivateFoo under SuperFoo
(
member procedure setUpCommonFoo
) NOT INSTANTIABLE NOT FINAL;
create or replace type body PrivateFoo is
-- Member procedures and functions
member procedure setUpCommonFoo is
begin
SELF.someAttrib:='Some Common Default Value';
end;
end;
create or replace type Foo under PrivateFoo
(
CONSTRUCTOR FUNCTION Foo RETURN SELF AS RESULT,
CONSTRUCTOR FUNCTION Foo(fkey FooKey) RETURN SELF AS RESULT -- assume fkey is defined in SuperFoo, and FooKey type is defined somewhere else ;)
)
create or replace type body Foo is
--no-arg Constructor For basic Foo set up.
CONSTRUCTOR FUNCTION PartyConvertor RETURN SELF AS RESULT AS
BEGIN
self.setUpCommonFoo;
RETURN;
END;
--alt constructor for other situations...
CONSTRUCTOR FUNCTION PartyConvertor(fkey FooKey) RETURN SELF AS RESULT AS
BEGIN
self.setUpCommonFoo;
SELF.rarelyUsedAttrib:='Special Value!'; --just assume that someAttrib and rarelyUsedAttrib actually exist ;)
self.fkey := fkey;
RETURN;
END;
--Other Members go here...
end;
Now I have to admit, I don't really like this pattern. It seems awkward and kludgy. I'm probably going to just avoid Object Types as much as I can and stick to packages (or very simlpe object types). A package-as-fatory only helps me solve the private common code problem for constructors, not for other types of common code refactoring.
...unless there's a better way to work with Object Types.... anyone? anyone?
If you just need to use the subprogram (function/procedure) from one subprogram PL/SQL does allow you to nest a subprogram within another in the declaration block.
It's not as ideal as having private methods or functions but it might be worth a try before you go creating an inheritance hierarchy.
create or replace type body some_t
as
member function foo
return varchar2
as
function some_private_foo
return varchar2
as
begin
return 'Foo!';
end some_private_foo;
begin
return some_private_foo();
end foo;
end;
If you're on Oracle 12 you're in luck. You can create a package that only your type can code against using the ACCESSIBLE BY clause. In the example below the PL/SQL compiler will only allow code from FOO_T to reference FOO_PRIVATE_PKG.
CREATE OR REPLACE package foo_private_pkg
accessible by ( foo_t )
as
function some_private_foo ( object_in in out nocopy foo_t )
return varchar2;
end;
You can't have private methods in pl/sql objects, you can have polymorphism and inheritance but no encapsulation.
When you want encapsulation (private methods) you can use pl/sql packages.
All three at once isn't possible.
The answer to this is somewhat difficult, but I'll explain it as best as I can. First, it's not completely Oracle's fault; ANSI SQL defines a thing called Abstract Data Types (ADT's) which can be used to extend SQL. Oracle follows their specifications pretty well I think. Part of the lack of encapsulation comes with the difficulty of referencing and storing objects in SQL. I won't get into the details here however since I don't fully understand it myself.
ADT's are useful to give structure to your data, either in code or in tables, but they can't be very complex. For example, you cannot have Object A that has Object B which again has Object A. This cannot be stored in a SQL table. You can get around this by using REFs in Oracle (not sure how other vendors go about this) but that becomes another problem to solve in code.
I've found that ADT's are good for very simple structures and very simple member methods. Anything more elaborate requires packages. Often, I'll write a package that implements member methods for a given Object Type, since you can't call private methods inside an object.
It is a pain...