Oracle PLSQL: call static method from member one - oracle

I have two methods of my Oracle type with name myType:
create or replace type myType as object (
dummy varchar2(1),
constructor function myType(something varchar2) return self as result,
member procedure mp(self in myType,force boolean := false),
static procedure sp
);
create or replace type body myType is
constructor function myType(something varchar2) return self as result
is
begin
dummy := something;
return;
end;
member procedure mp(self in myType,force boolean := false) is
begin
if self.dummy is null and force then
myType.sp();
end if;
end;
static procedure sp is
begin
null;
end;
end;
This causes an Oracle error: myType is out of scope (PLS-00225). If I remove "myType." it thinks I am trying to call sm() as a member method and also gives an error (PLS-00587).
I've found out how to solve the issue:
create or replace synonym mySynonym for myType;
and then call instead of myType.sp(); -> mySynonym.sp(); and it works fine.
Still I would prefer to find some solution without a garbage synonym if one exists.
Oracle version: 11.2
Important: the constructor causes the fail and it can't be eliminated in my case

Sorry, can't reproduce this, on Oracle XE 11.2:
SQL> create or replace type myType as object (
2 z char(1),
3 member procedure mp,
4 static procedure sp
5 );
6 /
Type created.
SQL> create or replace type body myType is
2 member procedure mp is
3 begin
4 myType.sp();
5 end;
6
7 static procedure sp is
8 begin
9 dbms_output.put_line('We''re here');
10 end;
11 end;
12 /
Type body created.
SQL> set serveroutput on
SQL> declare
2 x myType;
3 begin
4 x := myType(z => 'X');
5 x.mp();
6 end;
7 /
We're here
PL/SQL procedure successfully completed.
SQL>
How are you defining your type header?
EDIT: Now that you've added the constructor, I can reproduce the error.
One way to fix the problem is to qualify myType with the schema owner when calling the static procedure:
member procedure mp(self in myType,force boolean := false) is
begin
if self.dummy is null and force then
luke.myType.sp();
end if;
end;
This change allowed me to compile the type successfully.
I created your procedure while connected to my 11g XE database as user luke. This username is very likely to be different for your system.
Of course, your project may have different usernames for dev, test and production, so this might not be the ideal approach for you. If so, creating the synonym is probably the best thing to do.

Related

PLSQL Subtype of Nested Table PLS-00355: use of pl/sql table not allowed in this context

Edit: as per the answers, this works in 21C but not before. Looks like it is an Oracle bug that was fixed. The workaround in previous versions is provided as the accepted answer.
In PL/SQL, a subtype of a nested table behaves inconsistently compared to a regular nested table. It seems like it is not possible to use a subtype of a nested table. Is this correct?
create or replace package test_subtype_pkg
is
type t_foos is table of varchar2(10);
subtype t_bars is t_foos;
procedure main;
end;
/
The following l_bars := t_bars() fails to compile with PLS-00355: use of pl/sql table not allowed in this context
create or replace package body test_subtype_pkg
is
procedure main
is
l_foos t_foos;
l_bars t_bars;
begin
l_foos := t_foos(); -- compiles correctly
l_bars := t_bars(); -- PLS-00355: use of pl/sql table not allowed in this context
end;
end;
/
However, the following compiles without error. During run time it fails with ORA-06531: Reference to uninitialized collection:
create or replace package body test_subtype_pkg
is
procedure main
is
l_bars t_bars;
begin
l_bars.extend; -- At run time, ORA-06531: Reference to uninitialized collection
l_bars(1) := 'foo';
end;
end;
/
Is it possible to use a subtype of a nested table?
You can use the sub-type but it only appears to work if you call the constructor from the base-type:
create or replace package test_subtype_pkg
is
type t_foos is table of varchar2(10);
subtype t_bars is t_foos;
procedure main;
end;
/
create or replace package body test_subtype_pkg
is
procedure main
is
l_foos t_foos;
l_bars t_bars;
begin
l_foos := new t_foos();
l_bars := new t_foos();
l_bars.extend;
l_bars(1) := 'foo';
end;
end;
/
Note: the NEW keyword is optional.
Compiles without errors and then:
BEGIN
test_subtype_pkg.main();
END;
/
Works without a runtime error.
Note: You get the runtime error in your second example because you do not initialise the collection as you never call a constructor.
Oracle 18 fiddle
I don't have 18c; this is 21cXE and both codes work OK.
SQL> select banner from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 21c Express Edition Release 21.0.0.0.0 - Production
SQL> create or replace package test_subtype_pkg
2 is
3 type t_foos is table of varchar2(10);
4 subtype t_bars is t_foos;
5
6 procedure main;
7 end;
8 /
Package created.
You said that this failed on your database; it works for me:
SQL> create or replace package body test_subtype_pkg
2 is
3 procedure main
4 is
5 l_foos t_foos;
6 l_bars t_bars;
7 begin
8 l_foos := t_foos(); -- compiles correctly
9 l_bars := t_bars(); -- PLS-00355: use of pl/sql table not allowed in this context
10 end;
11 end;
12 /
Package body created.
SQL> exec test_subtype_pkg.main;
PL/SQL procedure successfully completed.
SQL>
Your code that compiles, but doesn't work - it is because you really didn't initialize the collection (see line #5 which shows how to do it):
SQL> create or replace package body test_subtype_pkg
2 is
3 procedure main
4 is
5 l_bars t_bars := t_bars();
6 begin
7 l_bars.extend; -- At run time, ORA-06531: Reference to uninitialized collection
8 l_bars(1) := 'foo';
9 end;
10 end;
11 /
Package body created.
SQL> exec test_subtype_pkg.main;
PL/SQL procedure successfully completed.
SQL>

Oracle Context equivalent for DB2

I am currently migrating from oracle to DB2. I have actively used sys_context in most of the applications to get the userID of logged in user through sessions(IIS and .net framework).
I am looking to convert the following set of scripts from oracle to DB2. So far not able to find any equivalent for oracle context in DB2.
SQL> create context my_ctx
2 using pkg_ctx;
Context created.
SQL> create package pkg_ctx
2 as
3 procedure set_context;
4 end;
5 /
Package created.
SQL> create or replace package body pkg_ctx
2 as
3 procedure set_context
4 as
5 begin
6 dbms_session.set_context( 'MY_CTX', 'USERNAME', 'test' );
7 end;
8 end;
9 /
Package body created.
SQL> exec pkg_ctx.set_context;
PL/SQL procedure successfully completed.
SQL> select sys_context( 'MY_CTX', 'USERNAME' )
2 from dual;
SYS_CONTEXT('MY_CTX','USERNAME')
-------------------------------------------------------------------------------
test
Assuming you work with Db2 for LUW, you can use one of the CURRENT CLIENT_* registry variables to pass session context information from the client application to the server. For example, during the session initialization on the client side you would issue SET CURRENT CLIENT_USERID='stacky', then on the server that variable can be read by a routine or a trigger.
It worked like this, haven't tried it at application level. But from the database tried to register 2 users from 2 different instances of the same database on DB2.
call sysproc.wlm_set_client_info('stacky',null,null,null,null);
select wlm_set_client_info from sysibm.sysdummy1;
returns stacky
from another instance of the same database
call sysproc.wlm_set_client_info('test',null,null,null,null);
select wlm_set_client_info from sysibm.sysdummy1;
returns test.
Will try this from the application level through creating sessions of the users, will monitor how exactly this works with the special registers.
Not sure if this is the exact way to do this, will explore more and keep updated.
For replicating a similar situation in our environment, while doing the migration from oracle to DB2 LUW.
The below procedure sets up the all the context values in user_id (limited to varchar 255) field which we need as as 'key1:value1|key2:value2|'
Procedure to setup the context values
CREATE OR REPLACE PROCEDURE setup_context(pValues IN VARCHAR)
IS
lValues VARCHAR2(255) := pValues;
BEGIN
--save the as is
wlm_set_client_info(lValues, NULL, NULL, NULL, NULL);
END;
Procedure to set the value of the desired key
CREATE OR REPLACE FUNCTION sys_context(prefix IN VARCHAR) RETURN VARCHAR IS
lValues VARCHAR2(255);
lParam VARCHAR2(255);
lKey VARCHAR2(255);
lValue VARCHAR2(255);
lIndex PLS_INTEGER;
BEGIN
--get the data from the current session
SELECT CURRENT CLIENT_USERID INTO lValues FROM dual;
LOOP
EXIT WHEN lValues IS NULL OR LENGTH(lValues) = 0;
lIndex := instr(lValues, '|');
IF lIndex > 0 THEN
lParam := substr(lValues, 1, lIndex-1);
lValues := substr(lValues, lIndex+1);
ELSE
lParam := lValues;
lValues := NULL;
END IF;
lIndex := instr(lParam, ':');
lKey := substr(lParam, 1, lIndex-1);
lValue := substr(lParam, lIndex+1);
--get the matching value
IF(lKey = prefix ) Then
RETURN lValue;
END IF;
END LOOP;
RETURN '';
END;
reference : https://www.ibm.com/support/knowledgecenter/en/SSEPEK_11.0.0/sqlref/src/tpc/db2z_sp_wlmsetclientinfo.html

Oracle PL/SQL Developer: Return %RowType from Package Procedure

i'm kind of new to Oracle Pl\SQL. I was just trying to create a simple Package with a procedure that returns a set of object id's; the code is as follows:
--Package Spec
CREATE OR REPLACE PACKAGE TEST IS
--GET OBJECT ID'S FROM CONTROL TABLE
PROCEDURE get_object_id_control(p_obj_id OUT abc_table%ROWTYPE);
END;
--Package Body
PROCEDURE get_object_id_control(p_obj_id OUT abc_table%ROWTYPE) AS
BEGIN
SELECT object_id
INTO p_obj_id
FROM abc_table
WHERE fec_proc IS NULL;
END;
I get Error: PL/SQL: ORA-00913: too many values. Is this the correct way for returning multiple values of same data type, or is there a better approach. Thanks in advance.
You can create a custom table type and set the out parameter of the procedure to that type.
CREATE TABLE ABC_TABLE(ID varchar2(100));
create or replace type abc_tab is table of varchar2(100);
/
CREATE OR REPLACE PACKAGE TEST IS
PROCEDURE get_object_id_control(p_obj_id OUT abc_tab);
END;
/
CREATE OR REPLACE PACKAGE BODY TEST IS
PROCEDURE get_object_id_control(p_obj_id OUT abc_tab) AS
BEGIN
SELECT id
bulk collect INTO p_obj_id
FROM abc_table;
END;
END;
/
Then you can call it like so:
declare
v abc_tab;
begin
TEST.get_object_id_control(p_obj_id => v);
for i in v.first..v.last loop
dbms_output.put_line(v(i));
end loop;
end;
/
Similar to GurV's answer (since he beat me by like 30 seconds...), you can use a PL/SQL object type as well. You do not need the CREATE TYPE statement if you don't need to reference the type in SQL.
--Package Spec
CREATE OR REPLACE PACKAGE TEST AS
TYPE id_table_type IS TABLE OF NUMBER;
--GET OBJECT ID'S FROM CONTROL TABLE
PROCEDURE get_object_id_control(p_obj_id_list OUT id_table_type);
END;
--Package Body
CREATE OR REPLACE PACKAGE BODY TEST AS
PROCEDURE get_object_id_control(p_obj_id_list OUT id_table_type) AS
BEGIN
SELECT object_id
BULK COLLECT INTO p_obj_id_list
FROM abc_table
WHERE fec_proc IS NULL;
END;
END;
To use it:
DECLARE
l_id_list test.id_table_type;
BEGIN
test.get_object_id_control (p_obj_id_list => l_id_list);
FOR i IN l_id_list.FIRST .. l_id_list.LAST LOOP
DBMS_OUTPUT.put_line (l_id_list (i));
END LOOP;
END;

Passing an array of data as an input parameter to an Oracle procedure

I'm trying to pass an array of (varchar) data into an Oracle procedure. The Oracle procedure would be either called from SQL*Plus or from another PL/SQL procedure like so:
BEGIN
pr_perform_task('1','2','3','4');
END;
pr_perform_task will read each of the input parameters and perform the tasks.
I'm not sure as to how I can achieve this. My first thought was to use an input parameter of type varray but I'm getting Error: PLS-00201: identifier 'VARRAY' must be declared error, when the procedure definiton looks like this:
CREATE OR REPLACE PROCEDURE PR_DELETE_RECORD_VARRAY(P_ID VARRAY) IS
To summarize, how can I pass the data as an array, let the SP loop through each of the parameters and perform the task ?
I'm using Oracle 10gR2 as my database.
This is one way to do it:
SQL> set serveroutput on
SQL> CREATE OR REPLACE TYPE MyType AS VARRAY(200) OF VARCHAR2(50);
2 /
Type created
SQL> CREATE OR REPLACE PROCEDURE testing (t_in MyType) IS
2 BEGIN
3 FOR i IN 1..t_in.count LOOP
4 dbms_output.put_line(t_in(i));
5 END LOOP;
6 END;
7 /
Procedure created
SQL> DECLARE
2 v_t MyType;
3 BEGIN
4 v_t := MyType();
5 v_t.EXTEND(10);
6 v_t(1) := 'this is a test';
7 v_t(2) := 'A second test line';
8 testing(v_t);
9 END;
10 /
this is a test
A second test line
To expand on my comment to #dcp's answer, here's how you could implement the solution proposed there if you wanted to use an associative array:
SQL> CREATE OR REPLACE PACKAGE p IS
2 TYPE p_type IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
3
4 PROCEDURE pp (inp p_type);
5 END p;
6 /
Package created
SQL> CREATE OR REPLACE PACKAGE BODY p IS
2 PROCEDURE pp (inp p_type) IS
3 BEGIN
4 FOR i IN 1..inp.count LOOP
5 dbms_output.put_line(inp(i));
6 END LOOP;
7 END pp;
8 END p;
9 /
Package body created
SQL> DECLARE
2 v_t p.p_type;
3 BEGIN
4 v_t(1) := 'this is a test of p';
5 v_t(2) := 'A second test line for p';
6 p.pp(v_t);
7 END;
8 /
this is a test of p
A second test line for p
PL/SQL procedure successfully completed
SQL>
This trades creating a standalone Oracle TYPE (which cannot be an associative array) with requiring the definition of a package that can be seen by all in order that the TYPE it defines there can be used by all.
If the types of the parameters are all the same (varchar2 for example), you can have a package like this which will do the following:
CREATE OR REPLACE PACKAGE testuser.test_pkg IS
TYPE assoc_array_varchar2_t IS TABLE OF VARCHAR2(4000) INDEX BY BINARY_INTEGER;
PROCEDURE your_proc(p_parm IN assoc_array_varchar2_t);
END test_pkg;
CREATE OR REPLACE PACKAGE BODY testuser.test_pkg IS
PROCEDURE your_proc(p_parm IN assoc_array_varchar2_t) AS
BEGIN
FOR i IN p_parm.first .. p_parm.last
LOOP
dbms_output.put_line(p_parm(i));
END LOOP;
END;
END test_pkg;
Then, to call it you'd need to set up the array and pass it:
DECLARE
l_array testuser.test_pkg.assoc_array_varchar2_t;
BEGIN
l_array(0) := 'hello';
l_array(1) := 'there';
testuser.test_pkg.your_proc(l_array);
END;
/

pl/sql object types "ORA-06530: Reference to uninitialized composite" error

i have a type as follows:
CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
item_id NUMBER,
system_event_cd VARCHAR2 (20),
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
);
CREATE OR REPLACE TYPE BODY tbusiness_inter_item_bag
AS
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
AS
BEGIN
RETURN;
END;
END;
when i execute the following script, i got a "Reference to uninitialized composite" error, which is imho quite suitable.
DECLARE
item tbusiness_inter_item_bag;
BEGIN
item.system_event_cd := 'ABC';
END;
This also raises the same error:
item.item_id := 3;
But if i change my object type into:
CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
item_id NUMBER(1),
system_event_cd VARCHAR2 (20),
CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
);
then the last statement raises no more error (where my "item" is still uninitialized):
item.item_id := 3;
Shouldn't i get the same ORA-06530 error?
ps: Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi
I reproduced the same behaviour in Oracle 11gR1. I would agree with you, this seems like a bug to me too, albeit a trivial one.
SQL> CREATE OR REPLACE TYPE tbusiness_inter_item_bag AS OBJECT (
2 item_id NUMBER(1),
3 system_event_cd VARCHAR2 (20),
4 CONSTRUCTOR FUNCTION tbusiness_inter_item_bag RETURN SELF AS RESULT
5 );
6 /
Type created.
SQL> DECLARE
2 item tbusiness_inter_item_bag;
3 BEGIN
4 item.item_id := 1;
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>
Note that this still fails:
SQL> DECLARE
2 item tbusiness_inter_item_bag;
3 BEGIN
4 item.item_id := 1;
5 item.system_event_cd := 'ABC';
6 END;
7 /
DECLARE
*
ERROR at line 1:
ORA-06530: Reference to uninitialized composite
ORA-06512: at line 5
SQL>
Obviously, the correct practice is always initialize objects before referencing them.
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 BEGIN
4 item.system_event_cd := 'ABC';
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>
You need to call the constructor you defined:
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 /* ^^ call the constructor */
4 BEGIN
5 item.system_event_cd := 'ABC';
6 END;
7 /
PL/SQL procedure successfully completed
I observe the behaviour you described on a 10.2.0.3 database. I wouldn't rely on it though, it looks like a bug.
I dont think its bug. As per documentation, you are supposed to initialize the composite type. This will always work :
SQL> DECLARE
2 item tbusiness_inter_item_bag := tbusiness_inter_item_bag();
3 BEGIN
4 item.system_event_cd := 'ABC';
5 END;
6 /
PL/SQL procedure successfully completed.
SQL>

Resources