Converting Ada closures to C callbacks (function + void*) - c

Most clean C APIs declare callback as a combination of callback function and a user data. User data is usually void*. WinAPI uses pointer-sized integer (lParam). During making a thick binding a natural desire is to allow Ada 2005 closures to be used in place of C callbacks.
I have a code. It works like a charm on GNAT (GPL 2012, x86-windows is tested at least), but generally there is no guarantee that Run_Closure_Adapter.X variable and Run_Closure.X argument will have the same internal structure.
The question is: is there a proper (standards-compliant) way to do this? Maybe a trick involving tagged types, interfaces or generics. There is at least one way of doing this: running closure executor and closures in different tasks and using rendezvous. But that's too slow.
Closure_Test.adb:
with Closure_Lib; use Closure_Lib;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
procedure Closure_Test is
procedure Closure_Tester is
Local_String : String := "Hello, world!";
procedure Closure is
begin
Put_Line (Local_String);
end Closure;
begin
Run_Closure (Closure'Access);
end Closure_Tester;
procedure Ada_Run_Closure (X : access procedure) is
begin
X.all;
end Ada_Run_Closure;
-- Nested_Closure fills the execution stack with
-- several activation records of Nested_Closure_Tester
-- Having done so (local I = 0) we start a Fibonacci
-- algorithm using Print_Closure access values of
-- different dynamic nesting levels
procedure Nested_Closure_Tester
(I : Integer;
Closure_Runner: access procedure (X : access procedure);
Prev_Closure, Prev_Closure2: access procedure)
is
procedure Print_Closure is
begin
if Prev_Closure /= null and Prev_Closure2 /= null then
Closure_Runner (Prev_Closure);
Closure_Runner (Prev_Closure2);
else
Put (".");
end if;
end Print_Closure;
procedure Nested_Closure is
begin
if I > 0 then
Nested_Closure_Tester (I - 1, Closure_Runner,
Print_Closure'Access, Prev_Closure);
else
Print_Closure;
end if;
end Nested_Closure;
begin
Closure_Runner (Nested_Closure'Access);
end Nested_Closure_Tester;
begin
-- Closure_Tester;
-- I = 6 gives 13 dots
Nested_Closure_Tester(6, Ada_Run_Closure'Access, null, null);
New_Line;
Nested_Closure_Tester(6, Run_Closure'Access, null, null);
end Closure_Test;
Closure_Lib.ads:
with Interfaces.C;
with System;
package Closure_Lib is
procedure Run_Closure (X : access procedure);
private
type Simple_Callback is access procedure(Data : in System.Address);
pragma Convention (C, Simple_Callback);
procedure Run_Callback (X : in Simple_Callback; Data : in System.Address);
pragma Import (C, Run_Callback, "Run_Callback");
procedure Sample_Callback (Data : in System.Address);
pragma Convention (C, Sample_Callback);
end Closure_Lib;
Closure_Lib.adb:
with Interfaces.C;
with System;
with System.Storage_Elements; use System.Storage_Elements;
with Ada.Text_IO; use Ada.Text_IO;
package body Closure_Lib is
procedure Sample_Callback (Data : in System.Address) is
begin
Ada.Text_IO.Put_Line ("Simple_Callback");
end Sample_Callback;
procedure Run_Closure_Adapter (Data : in System.Address);
pragma Convention (C, Run_Closure_Adapter);
procedure Run_Closure_Adapter (Data : in System.Address) is
X : access procedure;
for X'Address use Data;
pragma Import (Ada, X);
X_Size : constant Storage_Count := X'Size / System.Storage_Unit;
begin
-- Put_Line ("Variable access procedure size:" & Storage_Count'Image (X_Size));
X.all;
end Run_Closure_Adapter;
procedure Run_Closure (X : access procedure) is
X_Size : constant Storage_Count := X'Size / System.Storage_Unit;
X_Address : constant System.Address := X'Address;
begin
-- Put_Line ("Anonymous access procedure size:" & Storage_Count'Image (X_Size));
Run_Callback (Run_Closure_Adapter'Access, X_Address);
end Run_Closure;
end Closure_Lib;
closure_executor.c:
typedef void (*Simple_Callback)(void* Data);
void Run_Callback (Simple_Callback X, void* Data) {
(*X)(Data);
}

I think what you're looking for might be met by using a generic (by the way, I don't see how using a task can ensure that data types match?)
Maybe something like
generic
type Client_Data is private;
package Closure_G is
type Closure (<>) is private;
function Create (Proc : access procedure (Parameter : Client_Data);
And_Parameter : Client_Data) return Closure;
procedure Execute (The_Closure : Closure);
private
type Procedure_P is access procedure (Parameter : Client_Data);
type Closure is record
The_Procedure : Procedure_P;
And_Parameter : Client_Data;
end record;
end Closure_G;
When a user calls Execute (A_Closure), the Proc supplied to Create is called with the And_Parameter that was supplied then.
(The type Closure (<>) is private; makes sure tht users can only create a Closure object using the supplied Create.)
The main trouble with this, in your scenario of passing to a C library to be called-back when an event occurs, is that the Closure object is actually maintained by the C library.
Aside from the fact that you don't really need this Ada Closure, there's a potential problem caused by anonymous access-to-subprogram values, which is that the subprogram could be locally declared and have gone out of scope by the time the C library gets round to calling it. This would be Bad News.
In the Ada world, the compiler copes with this problem in two ways. First, you're not allowed to store anonymous access-to-subprogram values (hence the type Procedure_P above). Second, even if you work round this as in
function Create (Proc : access procedure (Parameter : Client_Data);
And_Parameter : Client_Data) return Closure is
begin
return (The_Procedure => Procedure_P'(Proc),
And_Parameter => And_Parameter);
end Create;
the actual 'accessibility levels' are checked at run time; if you get it wrong you'll get a Program_Error.

As an alternative, you might look at how GtkAda handles callbacks from GTK+. As shown in the GtkAda User’s Guide, and discussed in §4.2.2. Connecting via the Gtk.Handlers package,
The Gtk.Marshallers package provides a set of functions that can be used as callbacks directly for GtkAda…A set of To_Marshaller functions is found in every generic package in Gtk.Handlers. They take a single argument, the name of the function you want to call, and return a handler that can be used directly in Connect.
Interaction is an example that instantiates several such handlers and connects the corresponding callback using an access-to-subprogram parameter.

Related

Syntax error 201 when importing to Informix

I exported a database from Informix 11.50 and want to import it to Informix 14.10.
dbexport -ss -c -q iscala
The exported SQL script starts with the following:
{ DATABASE iscala delimiter | }
EXECUTE PROCEDURE ifx_allow_newline ('t');
grant dba to "informix";
grant dba to "waspop";
grant connect to "jzl";
create distinct type 'informix'.vestnik as decimal(4,0);
grant usage on type 'informix'.vestnik to 'public' as 'informix';
drop cast (decimal(4,0) as vestnik);
create explicit cast (decimal(4,0) as vestnik with 'informix'.date_vestnik);
CREATE FUNCTION "informix".date_vestnik(vestnik DATE) RETURNING vestnik WITH(NOT VARIANT);
RETURN ((CASE WHEN MONTH(vestnik)=12 AND DAY(vestnik)>12 THEN DAY(vestnik) ELSE MONTH(vestnik) END)
*100+MOD(YEAR(vestnik),100))::NUMERIC(4,0)::vestnik;
END FUNCTION;
create explicit cast (vestnik as integer with 'informix'.vestnik_int);
CREATE FUNCTION "informix".vestnik_int(vestnik vestnik) RETURNING INT WITH(NOT VARIANT);
RETURN vestnik::NUMERIC(4,0)::INT;
END FUNCTION;
create explicit cast (integer as vestnik with 'informix'.int_vestnik);
CREATE FUNCTION "informix".int_vestnik(vestnik INT) RETURNING vestnik WITH(NOT VARIANT);
RETURN vestnik::NUMERIC(4,0)::vestnik;
END FUNCTION;
create explicit cast (vestnik as decimal(4,0) with 'informix'.vestnik_date);
CREATE FUNCTION "informix".vestnik_date(vestnik vestnik) RETURNING DATE WITH(NOT VARIANT);
DEFINE y, m, d INT;
LET y = MOD(vestnik::NUMERIC(4,0),100);
LET m = TRUNC(vestnik::NUMERIC(4,0)/100);
LET d = 1;
IF m<1 THEN RETURN NULL;
ELIF m>20 THEN RETURN NULL;
ELIF m>12 THEN LET m,d = 12,m;
END IF
IF y<0 THEN RETURN NULL;
ELIF y<50 THEN LET y = 2000+y;
ELSE LET y = 1900+y;
END IF
RETURN MDY(m,d,y);
END FUNCTION;
{ TABLE "informix".pbcattbl row size = 369 number of columns = 25 index size = 31 }
{ unload file name = pbcat00100.unl number of rows = 0 }
...
...
When I run the import with this SQL script I receive the following error
[informix#srvbib ~]$ dbimport -c -d datadbs -i /home/informix/ iscala
{ DATABASE iscala delimiter | }
grant dba to "informix";
grant dba to "waspop";
grant connect to "jzl";
create distinct type 'informix'.vestnik as decimal(4,0);
grant usage on type 'informix'.vestnik to 'public' as 'informix';
drop cast (decimal(4,0) as vestnik);
create explicit cast (decimal(4,0) as vestnik with 'informix'.date_vestnik);
CREATE FUNCTION "informix".date_vestnik(vestnik DATE) RETURNING vestnik WITH(NOT VARIANT);
RETURN ((CASE WHEN MONTH(vestnik)=12 AND DAY(vestnik)>12 THEN DAY(vestnik) ELSE MONTH(vestnik) END)
*100+MOD(YEAR(vestnik),100))::NUMERIC(4,0)::vestnik;
END FUNCTION;
create explicit cast (vestnik as integer with 'informix'.vestnik_int);
CREATE FUNCTION "informix".vestnik_int(vestnik vestnik) RETURNING INT WITH(NOT VARIANT);
RETURN vestnik::NUMERIC(4,0)::INT;
END FUNCTION;
create explicit cast (integer as vestnik with 'informix'.int_vestnik);
CREATE FUNCTION "informix".int_vestnik(vestnik INT) RETURNING vestnik WITH(NOT VARIANT);
RETURN vestnik::NUMERIC(4,0)::vestnik;
END FUNCTION;
create explicit cast (vestnik as decimal(4,0) with 'informix'.vestnik_date);
*** prepare sqlobj
201 - A syntax error has occurred.
*** execute sqlobj
201 - A syntax error has occurred.
When I remove the procedure it works OK. If I try to create the function using the dbaccess, I can create it without issues.
Do you please know or could you please provide some advice or hint on how to solve this issue? Thank you.
Converting comments into an answer.
This looks like a bug. You will need to contact your Informix technical support channel. I've created bug CQ idsdb00111253. You can use that in conversations with your support team.
Analysis
The error message is not very helpful — the statement that is failing to be prepared is a DROP CAST statement that DB-Import creates after analyzing the CREATE CAST. The text of the generated statement is missing a close parenthesis because it isn't expecting DECIMAL(4, 0) — it thinks that the close parenthesis in that type name means it doesn't need to add one whereas, in fact, it does need to add one. Some lazy parsing is going to have to become less lazy.
There are two variants of the CREATE CAST statement according to the syntax diagrams:
CREATE [{ IMPLICIT | EXPLICIT }] CAST (<type1> AS <type2>)
CREATE [{ IMPLICIT | EXPLICIT }] CAST (<type1> AS <type2> WITH <function>)
The problem occurs because your <type2> is DECIMAL(4,0), and the code spots the ) parenthesis and assumes (mistakenly) that the ) is from the first variant rather than the second:
create explicit cast (vestnik as decimal(4,0) with 'informix'.vestnik_date);
Thank you for your investigation. Regarding your observation, I would expect that if I remove the drop case, the error will disappear, but the error is still there even if the drop cast has been removed. Could you please share with me how you debug this behaviour?
For reasons that are not completely clear to me, DB-Import takes the CREATE EXPLICIT CAST statement and manufactures a DROP CAST statement from it — but makes a mistake when doing so. At some point, I will check whether this is a new defect, but my gut feel is that it will be present in older versions too.
You could debug this by setting the SQLIDEBUG environment variable when you run DB-Import and then using sqliprint to show what messages are sent to and from the server. I can only find references to SQLIDEBUG in the context of JDBC, but it is more general than that.
export SQLIDEBUG=2:${TMPDIR:-/tmp}/sqdbg
When you connect to the server with this set, the client code will create a file such as /tmp/sqdbg_a3715. You can then run sqliprint /tmp/sqdbg_a3715.
Workarounds
As to workarounds, the simplest would be to remove the offending CREATE CAST statement(s) from the import file (iscala.exp/iscala.sql) and complete the import without it. Then use DB-Access to execute the omitted statements. It's undoubtedly a nuisance but should allow you to complete the import.

Error while calling function from procedure from same package

I am new to oracle.I have created a package which has spec and body.
It's header is:
CREATE OR REPLACE PACKAGE OT.PK_TEST IS
FUNCTION PRNT_STRNG RETURN VARCHAR2;
PROCEDURE PR_SUPERHERO(F_NAME VARCHAR2,L_NAME VARCHAR2);
END PK_TEST;
/
Its body is:
CREATE OR REPLACE PACKAGE BODY OT.PK_TEST IS
FUNCTION PRNT_STRNG
RETURN VARCHAR2
IS
BEGIN
RETURN 'ASHWIN';
END PRNT_STRNG;
PROCEDURE PR_SUPERHERO(F_NAME VARCHAR2,L_NAME VARCHAR2)
IS
BEGIN
OT.PK_TEST.PRNT_STRNG;
DBMS_OUTPUT.PUT_LINE(F_NAME);
DBMS_OUTPUT.PUT_LINE(L_NAME);
END PR_SUPERHERO;
END PK_TEST;
/
I wanted to test the whether the function can be called from body of procedure or not.So,I added:
OT.PK_TEST.PRNT_STRNG;
to call the function which is returning varchar.But,I got error:
[Warning] ORA-24344: success with compilation error
11/1 PLS-00221: 'PRNT_STRNG' is not a procedure or is undefined
11/1 PL/SQL: Statement ignored
(1: 0): Warning: compiled but with compilation errors
Can i call function from the procedure of same package or not? Also,can i call function of different package?
Calling a function would return an output. You need to assign the function to a variable like:
some_var = OT.PK_TEST.PRNT_STRNG;
You can only call procedures directly
Yes you can call the function/procedure in the same package. You can also call other function from other package or other schema, you just have to reference it. The only thing you are missing is assigning a variable to the return of the function you have called.
You should do it like this:
v_temp VARCHAR2(32 CHAR);
BEGIN
v_temp := OT.PK_TEST.PRNT_STRNG;
DBMS_OUTPUT.PUT_LINE(v_temp );
DBMS_OUTPUT.PUT_LINE(F_NAME);
DBMS_OUTPUT.PUT_LINE(L_NAME);
END PR_SUPERHERO;

Ada: Array of tasks

What is a good way to link an indexed task to a corresponding indexed protected type in SPARK?
For specifics, consider this setup:
subtype Thread_Range is Natural range 1..n;
protected type P is ... end P;
p: array(Thread_Range) of P;
For each p(i) I would like a task t(i) that monitors p(i) and, when it's ready, processes it. I can make this work pretty easily in Ada, but SPARK w/Ravenscar is more demanding. I've tried two approaches that appear to work fine when I run them:
Give T an Integer discriminant, then instantiate a T(i); for each i, but this grows burdensome with not-very-large i.
task type T(which: Integer);
t1: T(1);
t2: T(2);
...
Add an is_not_monitored function and a set_monitor procedure to P. Create an array of tasks without discriminant. When t(i) begins, it assigns itself to monitor the first p(j) it finds that hasn't already been assigned a monitor.
task type T;
task body T is
which: Integer;
available: Boolean;
begin
for i in Thread_Range loop
available := p(i).is_not_monitored;
if available then
p(i).set_monitor;
which := i;
end if;
end loop;
-- what the task does with p(i) follows
end T;
t: array(Thread_Range) of T;
I like the second one better, but not by much. In any case, SPARK "Prove" grumbles about potential data races, and I can see why (though I'm not sure it's actually due to this).
Hence the question.
This doesn’t cause gnatprove to choke.
And I think the main difference from your option 2 is that Claim checks whether the claim is possible and, if so, performs the claim in one protected call.
But I don’t quite see how to prove that the loop Claim in T exits with Ps (J) being claimed. I tried putting an assertion after the loop, but couldn’t get it to prove.
protected type P is
procedure Claim (Succeeded : out Boolean);
private
Claimed : Boolean := False;
end P;
subtype Thread_Range is Integer range 1 .. 2;
Ps : array (Thread_Range) of P;
Ts : array (Thread_Range) of T;
task body T is
Which : Integer;
begin
Claim:
for J in Thread_Range loop
declare
Claimed : Boolean;
begin
Ps (J).Claim (Succeeded => Claimed);
if Claimed then
Which := J;
exit Claim;
end if;
end;
end loop Claim;
loop -- having a loop keeps gnatprove quiet
delay until Ada.Real_Time.Time_Last;
end loop;
end T;
protected body P is
procedure Claim (Succeeded : out Boolean) is
begin
if not Claimed then
Claimed := True;
Succeeded := True;
else
Succeeded := False;
end if;
end Claim;
end P;
After out-of-band discussions with John, we’ve found that this postcondition can be proved:
procedure Claim (Succeeded : out Boolean)
with
Post =>
(Is_Claimed'Old or (Succeeded and Is_Claimed))
or
(not Succeeded and Is_Claimed);
Note that it’s not P’Old.Is_Claimed, mainly because ’Old requires a copy, and P is limited (because it’s a protected type).
We also found several alternative formulations that prove in GPL 2017 but not in CE 2018: for example,
(Is_Claimed
and
(Is_Claimed'Old xor Succeeded)
I'm not an expert in this, but it seems that you cannot show SPARK that there's a one-to-one relation between a task instance and a protected object instance unless you reference that protected object instance explicitly from a task instance. This is in particular to make SPARK prove that only one task will queue on the entry of a protected object; the Wait entry in the code below). Therefore (and while this might not be exactly what you're looking for), I could only solve the problem of connecting tasks and protected objects, and at the same time having a monitor functionality, by using a generic package that can be instantiated multiple times. This proves in GNAT CE 2018:
generic
package Generic_Worker with SPARK_Mode is
task T;
protected P is
entry Wait;
procedure Trigger;
private
Triggered : Boolean := False;
end P;
end Generic_Worker;
with body:
package body Generic_Worker with SPARK_Mode is
task body T is
begin
loop -- Ravenscar: Tasks must not terminate.
P.Wait;
end loop;
end T;
protected body P is
entry Wait when Triggered is
begin
Triggered := False;
-- Do some work.
end Wait;
procedure Trigger is
begin
Triggered := True;
end Trigger;
end P;
end Generic_Worker;
and instantiations:
with Generic_Worker;
pragma Elaborate_All (Generic_Worker);
package Workers with SPARK_Mode is
package Worker_0 is new Generic_Worker;
package Worker_1 is new Generic_Worker;
package Worker_2 is new Generic_Worker;
package Worker_3 is new Generic_Worker;
package Worker_4 is new Generic_Worker;
end Workers;

Ada - try to call entry of element of array of protected type, if busy - try next element

I'm working on a multithreading project and i have one array of tasks, let's call it A and one array of protected, let's call it B.
Now every element of A at some point will want to access entry from one of the elements of B. Is there a way to iterate quickly through B's and find accesible one?
I was browsing some resources avalaible online and found only
select
call entry;
or
delay;
which won't do the job.
Is there a way to do that that i'm not aware of?
Thanks in advance!
EDIT
So i got really excited about that select else statements and tried it out, unfortunately it still doesnt work, if I try it this way - using Mr. Simon Wright's code sample to simulate effect i need, the
else
part of code never gets executed, they (the taskArray elements) all think they can acces Call_Me and get stuck in the queue.
How can i solve this issue? I also tried using 'Count thing, but it somehow always ended up being 0.
with Ada.Text_IO; use Ada.Text_IO;
procedure Array_Of_POs is
protected type PO is
entry Call_Me;
private
Callable : Boolean := True; --changed
end PO;
protected body PO is
entry Call_Me when Callable is
begin
Callable := False; --added
Put_Line("Doin stuff");
delay 1.0;
Callable := True;
end Call_Me;
end PO;
POs : array (1 .. 3) of PO;
Success : Boolean := False;
task type Test; --added
task body Test is
begin
for J in POs'Range loop -- '
select
POs (J).Call_Me;
Success := True;
else
Put_Line("I never get displayed!"); --added
end select;
end loop;
Put_Line ((if Success
then "succeeded, oh dear"
else "didn't succeed, good"));
end Test;
testArray : array (1..3) of Test;
end Array_Of_POs;
You could try finding an entry E whose Count attribute (E’Count, ARM 9.9(5)) is zero; but this is going to result in race conditions, since another task could sneak in between your checking the count and your making the call. And see the note at (7), ibid.
You need a conditional entry call, as in ARM 9.7.3.
It would look something like
with Ada.Text_IO; use Ada.Text_IO;
procedure Array_Of_POs is
protected type PO is
entry Call_Me;
private
Callable : Boolean := False;
end PO;
protected body PO is
entry Call_Me when Callable is
begin
null;
end Call_Me;
end PO;
POs : array (1 .. 3) of PO;
Success : Boolean := False;
begin
for J in POs'Range loop -- '
select
POs (J).Call_Me;
Success := True;
else
null;
end select;
end loop;
Put_Line ((if Success
then "succeeded, oh dear"
else "didn't succeed, good"));
end Array_Of_POs;
In your case, I suspect this construct would be in an outer loop, so you’d need to do something to avoid busy-waiting (on the lines of putting in a delay if not Success, perhaps).
EDIT
This is a response to your edited question.
ARM 9.5.1(8) states that your PO.Call_Me contains a bounded error. In the next paragraph,
If the bounded error is detected, Program_Error is raised. If not detected, the bounded error might result in deadlock or a (nested) protected action on the same target object.
At least GNAT hasn’t done either of those.
The issue is that the body of the entry is executed in the context (thread) of the caller. So what happens is
Test calls POs(1).Call_Me
The call is accepted
Callable is set False
Put_Line and delay are called (still in the context of Test)
Callable is set True
POs(1).Call_Me returns to Test
...
The approach you’ve adopted might work if instead of using an array of POs you used an array of tasks.

Passing Array to Oracle Function

I am passing an array to a PL/SQL package function. I am doing this to use this array in a query inside the function which has IN clause.
My declaration of package looks like :
create or replace
PACKAGE selected_pkg IS
TYPE NUM_ARRAY IS TABLE OF NUMBER;
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN selected_pkg.NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
)
RETURN CLOB;
END selected_pkg;
In my PL/SQL function I am firing a query like following
select a.id, a.geom from Table_FIELD a where a.id in (select * from table (in_feature_ids)) and sdo_filter(A.GEOM,mdsys.sdo_geometry(2003,4326,NULL,mdsys.sdo_elem_info_array(1,1003,3), mdsys.sdo_ordinate_array(0,57,2.8,59)),'querytype= window') ='TRUE'
The same query runs fine if I run it from anonymous block like
CREATE TYPE num_arr1 IS TABLE OF NUMBER;
declare
myarray num_arr1 := num_arr1(23466,13396,14596);
BEGIN
FOR i IN (select a.id, a.geom from Table_FIELD a where a.id in (select * from table (myarray)) and sdo_filter(A.GEOM,mdsys.sdo_geometry(2003,4326,NULL,mdsys.sdo_elem_info_array(1,1003,3), mdsys.sdo_ordinate_array(0,57,2.8,59)),'querytype= window') ='TRUE'
loop
dbms_output.put_line(i.id);
end loop;
end;
If I try to run it by calling function as below
--Running function from passing array for IDs
declare
result CLOB;
myarray selected_pkg.num_array := selected_pkg.num_array(23466,13396,14596);
begin
result:=SELECTED_PKG.get_selected_kml(3, 19, myarray, 0.0,57.0,2.8,59);
end;
I am getting error
ORA-00904: "IN_FEATURE_IDS": invalid identifier
Could someone please help me understand the cause of it?
Thanks,
Alan
You cannot query a type declared in plsql in a sql query, as the sql engine doesn't recognise it.
Your first example works because you have declared the type numarr1 in the database, whereas the type selected_pkg.num_array is declared in a package.
Good summary here
I can't quite recreate the error you're getting; the anonymous block doesn't refer to in_feature_ids, and the package ought to only report that if it doesn't recognise it on compilation rather than at runtime - unless you're using dynamic SQL. Without being able to see the function body I'm not sure how that's happening.
But you can't use a PL/SQL-defined type in an SQL statement. At some point the table(in_feature_ids) will error; I'm getting an ORA-21700 when I tried it, which is a new one for me, I'd expect ORA-22905. Whatever the error, you have to use a type defined at schema level, not within the package, so this will work (skipping the spatial stuff for brevity):
CREATE TYPE num_array IS TABLE OF NUMBER;
/
CREATE OR REPLACE PACKAGE selected_pkg IS
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
) RETURN CLOB;
END selected_pkg;
/
CREATE OR REPLACE PACKAGE BODY selected_pkg IS
FUNCTION get_selected_kml(
in_layer IN NUMBER,
in_id IN NUMBER,
in_feature_ids IN NUM_ARRAY,
in_lx IN NUMBER,
in_ly IN NUMBER,
in_ux IN NUMBER,
in_uy IN NUMBER
) RETURN CLOB IS
BEGIN
FOR i IN (select * from table(in_feature_ids)) LOOP
DBMS_OUTPUT.PUT_LINE(i.column_value);
END LOOP;
RETURN null;
END get_selected_kml;
END selected_pkg;
/
... and calling that also using the schema-level type:
set serveroutput on
declare
result CLOB;
myarray num_array := num_array(23466,13396,14596);
begin
result:=SELECTED_PKG.get_selected_kml(3, 19, myarray, 0.0,57.0,2.8,59);
end;
/
23466
13396
14596
PL/SQL procedure successfully completed.
Also note that you have to use exactly the same type, not just one that looks the same, as discussed in a recent question. You wouldn't be able to call your function with a variable of num_arr1 type, for example; they look the same on the surface but to Oracle they are different and incompatible.

Resources