Confused about type coercion in function calls (stack frames) - oracle

Consider the following example:
create or replace function f(n integer) return integer as
begin
return n;
end;
/
begin
dbms_output.put_line(f(3.8));
end;
/
3.8
PL/SQL procedure successfully completed.
This makes no sense to me. Obviously, PL/SQL simply ignores the integer specification, both on entering the function and on exiting it. Is this simply a bug? Is it a design choice, made deliberately by the language developers?
Here is why I find this confusing. Compare to the following example:
declare
x integer;
begin
x := 3.8;
dbms_output.put_line(x);
end;
/
4
PL/SQL procedure successfully completed.
In this example, the data type specification is complied with. PL/SQL doesn't throw an error, but at least it performs an implicit coercion and it does not violate the data type declared for x - the variable stores the value 4, an integer, not 3.8.
So, how does PL/SQL do the function call thing in the first example? As far as I understand (never having been trained formally in computing), whenever the compiler or interpreter finds a function call it creates a stack frame, with variables for the arguments passed to the function and for the return value to come back from the function. Aren't these variables, when the stack frame is created, supposed to be the same data type as specified in the function declaration? If the stack frame has a field of integer data type for the argument 3.8, how come that is not coerced to 4 before it is even stored in the corresponding variable? And the same thing for the return value: if the function returns 3.8 but the caller expects an integer (and therefore the corresponding variable in the stack frame should be integer), how is it able to accept the return value 3.8?
And, most disturbing - why is this behavior different from the behavior when explicitly declared variables are involved (as in my second example)?
Thank you for sharing your thoughts!

The answer is found in the documentation for Oracle Database (to which your question has absolutely no relation whatsoever).
Firstly, INTEGER in the said database is a synonym for NUMBER(38). Upon assignment to a NUMBER(38) variable x, as in your second example, according to the assignment rules the NUMBER (with arbitrary precision) literal 3.8 is rounded.
In your first example though no assignment happens because IN parameters to PL/SQL subprograms are passed by reference and the same reference (to the NUMBER value 3.8) is returned.

Related

PL/SQL - Call new instance of package procedure

I have package that uses global variables.
From one of the procedures i need to call "new instance" od another procedure (of the same package). I need this second procedure to use it's own version of global variables and when it's done i need first procedure to use variables as they were before.
I did a quick and dirty fix, at the beginning of second procedure i create backup variables and put global variables values in it and then setting global variables to NULL and at the end i put backup values back to global variables.
But i hope there is better solution than this one, a way to start new instance of that procedure/package
EDIT
Detailed scenario is like this:
Package has multiple procedures and functions that use variables defined at package level. I call firs procedure of the package and that procedure calls everything that is needed for current action. In some cases from second procedure i need to start new task (action) that has to be executed. In that case i would like to start again first procedure in package, but for it to use its own global variables (not to mess with original ones) because original procedure is not yet finished and still needs it's variables
Some of package global variables are custom type (table of custom_type to be exact) so it's creating additional problem for me.
Hope it's clear now what problem i have and what i'm trying to do
So it's something like this
Package 1
global_var 1
global_var 2
global_var 3
Procedure 1
some code
call to function 1
some code
call to procedure 2
and so on...
Procedure 2
some code
call function 3
some code
call to procedure 1 (start new process)
some code
call procedure 3
and so on....
Procedure 3
Procedure 4
Function 1
Function 2
Function 3
Function 4
And variables are like this
type type1 is record (x NUMBER, y VARCHAR2(1024), z NUMBER);
type type2 is table of type1;
global_var_1 NUMBER;
global_var_2 type2;
...
After reading your entire post (with the additional description), I came to the conclusion that your problem starts with a wrongly conceived view of the required process. In fact (and lending words from other programming languages) you need your package to spawn new threads with their own data context.
I see two possible solutions (in Oracle):
Write your package in JAVA (here I'm assuming that this option does allow the creation of threads, but never checked it),
Do not use GLOBAL VARIABLES.
For the first option I have no much to say except that it may be worth for you to investigate further.
As for the second option, you can (quite easily in fact):
Define a type a package level that will contain all the variables that you are currently using at global level and:
Add to all your functions in the package a parameter of the new type (I would put it as the last one) with default value NULL. Wherever needed, the definition of the new parameter can be set as IN OUT (for the case that one or more of the fields within the structure is updated within a function and the update needs to "bubble-up" to the invoking code).
Now, let's analyze the two scenarios (first invocation of FIRST procedure and second invocation from within the package):
FIRST INVOCATION (from outside the package): The new parameter will be empty (NULL) and the procedure will assign whatever values are needed to cascade throughout additional invocations of other functions/procedures;
SECOND INVOCATION (from within the package): can include a non-NULL value for the new parameter (depending on your needs); the code will then be able to prepare a new set of values (for a new variable of the type described above) which will then be cascaded through the additional invocations of other functions and procedures.
This method achieves exactly what you need while (as far as I can imagine) the required code changes would be minimal.
Hope this addresses the question you posted and the proposed approach serves you well.

Overloading oracle procedure with DATE and VARCHAR2

I have the following code that doesn't work. It compiles, but when called with sysdate as the parm_value parameter it throws PLS-00307: too many declarations of 'P_UPSERT_SDE_DATA' match this call If I comment out the varchar2 entry, the overload works as expected with just date and number datatypes.
What is the best way to go about what I'm trying to do, which is accept parameters differing only in parm_values datatype(specifically date and varchar2)?
PROCEDURE P_Upsert_SDE_Data(parm_table_name GORSDAV.GORSDAV_TABLE_NAME%TYPE,
parm_attr_name GORSDAV.GORSDAV_ATTR_NAME%TYPE,
parm_key GORSDAV.GORSDAV_PK_PARENTTAB%TYPE,
parm_user_id GORSDAV.GORSDAV_USER_ID%TYPE,
parm_value VARCHAR2);
--
PROCEDURE P_Upsert_SDE_Data(parm_table_name GORSDAV.GORSDAV_TABLE_NAME%TYPE,
parm_attr_name GORSDAV.GORSDAV_ATTR_NAME%TYPE,
parm_key GORSDAV.GORSDAV_PK_PARENTTAB%TYPE,
parm_user_id GORSDAV.GORSDAV_USER_ID%TYPE,
parm_value NUMBER);
PROCEDURE P_Upsert_SDE_Data(parm_table_name GORSDAV.GORSDAV_TABLE_NAME%TYPE,
parm_attr_name GORSDAV.GORSDAV_ATTR_NAME%TYPE,
parm_key GORSDAV.GORSDAV_PK_PARENTTAB%TYPE,
parm_user_id GORSDAV.GORSDAV_USER_ID%TYPE,
parm_value DATE);
You can see this if one of the others arguments you pass is being implicitly converted; from the call you posted I suspect var_FRB‌​GRNT_CODE is a different type and is being converted; e.g. that variable is a number and GORSDAV.GORSDAV_PK_PARENTTAB is a string.
From the documentation:
When trying to determine which subprogram was invoked, if the PL/SQL compiler implicitly converts one parameter to a matching type, then the compiler looks for other parameters that it can implicitly convert to matching types. If there is more than one match, then compile-time error PLS-00307 occurs, as in Example 8-34.
Implicit conversion of one of the other arguments makes it look for potential conversions of the others; it's only then that it sees the date and varchar2 versions, which can be implicitly converted to each other. If all of the arguments are of the same type as the table columns used for the formal parameter declarations then it won't look for implicit conversions, and won't be confused by the date/varchar2 versions.
Whenever I get this error, it seems to be that there's a mismatch between the spec declaration and the body declaration somewhere. Make sure each of your spec declarations match your body declarations exactly.

Oracle procedure param defined as .NUMBER, is it ok to pass it in as .INTEGER

I have two procedures in oracle which define a parameter differently, even though the underlying data is the same. In code we represent it as an integer.
procedure GetByNum(..., vRegionID in number, ...)
procedure GetByInt(...., vRegionID in integer, ...)
In java we always define the field as an Integer (and in db too)
public Integer getRegionID() {
return 100;
}
Is there a way to pass this (in java) integer to both procedures using the same OracleTypes.*? We use a custom base wrapper around org.springframework.jdbc.object.StoredProcedure to call both of them. Modifying the stored procedures is not allowed for right now, so all that is left is fixing up the java.
Can you pass getRegionID as OracleTypes.NUMBER to the getByInt proc?
Can you pass it as OracleTypes.INTEGER to getByNum, which expects in number?
Another, magical way?
This might seem simple, but I assume i'm not allowed to mangle parameters like this. I hope thats not true!
Edit:
Using OracleTypes.INTEGER to pass to procedure ... (param in number) worked
Don't know if this is just environment specific though
Hoping for a definitive answer, so I can have them start using the new wrapper without worrying about an unforeseen issue.
INTEGER is a ANSI standard type, which Oracle describes as a subtype of NUMBER as NUMBER(38). Therefore, anywhere that takes an INTEGER as a parameter should also accept a NUMBER so long as there are no fractional parts.

Inline VS Linear String processing performance in Object Oriented Pascal - Delphi

Because of the close ending of one of my projects I wanned set up some discussion ( in case Rob does not provide very detailed answer :D ), I am more focusing of some memory and cycle optimizations in some hungry string processing areas. In my case I am interested in some performace tests, if anyone has made something like that, for particularry performance diff for two cases:
Case 1: I use string processing in in-line way so I have one extra lengthy line, for example,
RichEdit1.SelText := stringfunction1(stringfunction2(stringfunction3(stringfunction4, stringfunction5), stringfunction6, stringfunction7(stringfunction8))))
or
Case 2:
I just split all those functions so each has executed in seperate line and therefore I have to declare the variable that would buffer the return of each function.
P.S. I hope I have not mistaken with the brackets in Case 1.
So, what is your findings / opinion / critics about this question?
Maybe it is not simply worth time to gain some extra nanosecond?
Declaring variables wouldn't make any difference I believe.
When you call functions like this, the compiler needs to generate implicit string variables to keep the result of your functions. The way you are doing it, the main advantage would be that the compiler can decide to reuse a temp variable once it's done using it, but nothing prevent the compiler to do the same with explicit variables.
Actually, every time you call a function with a string result, the compiler need to create a temp variables, because function returning a string are actually implemented as a procedure with an additional var parameter.
For exemple:
function GetTempPath : string;
is really implemented this way
procedure GetTempPath(var S : string);
so, given following procedure:
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Text := GetTempPath;
end;
The compiler first allocate a temporary string variable. Calls GetTempPath with said temp variable in parameter. Once it returns, it takes this variable and set it to Memo1.Lines.Text. Essentially, what it really does is this:
procedure TForm1.Button1Click(Sender: TObject);
var S : string;
begin
GetTempPath(S);
Memo1.Lines.Text := S;
end;
and if you actually declare the function like the following, the compiler is smart enough to not create an additionnal variable.
procedure TForm1.Button1Click(Sender: TObject);
var S : string;
begin
S := GetTempPath;
Memo1.Lines.Text := S;
end;
The code
var
s1, s2: string;
begin
s1 := 'This is a very long string...';
s2 := s1;
end;
does not copy the string s1 into s2 (which could be a performance issue in a tight loop), but it simply instructs s2 to point to the same location in memory as s1. That is, in general, assigning strings to variables isn't a very bad thing.
In fact, I am not sure what method would produce the most efficient assembly code (if they are not identical!). Even in the in-lined case, the intermediate results have to be stored somewhere...
All-in-all, I definitely think you should go for the approach that is the most readble one (to a human programmer). The difference in performance should not even be detectable.
Okay.
I will try to put things together within two sentences. :)
String optimization
basically is premature optimmization
because of fact that it is bottleneck
of performance in VERY VERY VERY rear
situations or usecases.
Inline string usages main advantage is to use compilers features that allow to reuse previous return ( temp ) variables in further paramteric function calls. Still - if we intend to use linear operation(s), we should add GetTempPath() procedure before main string equalization code to make sure we use old temp variables still accessible in memory.

Oracle Default Values

I've got a quick question about default values in PL/SQL functions in Oracle. Take this program as an example;
create or replace
FUNCTION testFunction
(
varNumber IN NUMBER DEFAULT 0
)
RETURN NUMBER
AS
BEGIN
dbms_output.put_line(varNumber);
RETURN varNumber;
END;
The idea here being that if no value is specified for varNumber when this function is called, then it will take the value of 0.
Now, my problem is that my functions are getting called from a web services layer that will always pass in NULL as the value for parameters which it doesn't have a value for. Oracle interprets NULL as a value, and so does not initialise varNumber to its default of 0.
I can see why this approach makes sense, but I was wondering if there was a way to override this behaviour, and make it so that if a NULL value is passed, that it causes Oracle to assign the explicit DEFAULT value that is specified in the function header?
I have considered the option of doing a manual check...
IF(varNumber IS NULL) THEN
varNumber := 0;
END IF;
However, there are hundreds of functions where this may be an issue, never mind the large number of parameters per function, and so I'd prefer it if I could find a more general solution to the problem.
Cheers for any insight you can give.
Use NVL to define the value.
NVL( value_in, replace_with )
You can't assign values to an IN parameter, but you could make them IN/OUT and then set them. That raises a big potential for misuse and confusion, though.
So I think you'd do better with a local variable. But you can do it in the declaration. That is,
create or replace
FUNCTION testFunction
(
varNumber IN NUMBER DEFAULT 0
)
RETURN NUMBER
AS
vFix number := nvl(varNumber,0);
BEGIN
dbms_output.put_line(vFix);
RETURN vFix;
END;
Your manual check is the only way to safely do what you want.
You can write that in one line like this though:
varNumber = NVL(varNumber,0);
Good luck!

Resources