I want to read instance name and set some constant to true if the condition is correct.
For example i have already this
--DYNAMICALLY CREATE A PACKAGE TO HOLD CONSTANTS.
BEGIN
EXECUTE IMMEDIATE
'
CREATE OR REPLACE PACKAGE XXX_COMPILATION_CONSTANTS IS
C_MAKE_PUBLIC_XXX_SCHEMA CONSTANT BOOLEAN := '||CASE WHEN USER = 'TEST' THEN 'TRUE' ELSE 'FALSE' END||';
END;
';
END;
How can I read instance name like USER above ?
--DYNAMICALLY CREATE A PACKAGE TO HOLD CONSTANTS.
BEGIN
EXECUTE IMMEDIATE
'
CREATE OR REPLACE PACKAGE XXX_COMPILATION_CONSTANTS IS
C_MAKE_PUBLIC_XXX_SCHEMA CONSTANT BOOLEAN := '||CASE WHEN **INSTANCE**= 'MY_TEST_DB' THEN 'TRUE' ELSE 'FALSE' END||';
END;
';
END;
My guess is that you probably don't want to create your own package. The USERENV context probably already has all the information you want.
sys_context( 'USERENV', 'CURRENT_USER' )
for the current user and
sys_context( 'USERENV', 'INSTANCE_NAME` )
for the name of the instance. In the documentation I linked to, you can see that there are a bunch of other attributes in that context that are already populated with useful bits of information.
Your example works if you use sys_context('userenv','instance_name'):
begin
execute immediate
'create or replace package xxx_compilation_constants as
c_make_public_xxx_schema constant boolean := '||
case
when sys_context('userenv','instance_name') = 'MY_TEST_DB' then 'TRUE'
else 'FALSE'
end||';
end;
';
end;
which creates a package as:
create or replace package xxx_compilation_constants as
c_make_public_xxx_schema constant boolean := FALSE;
end;
However, it would be simpler to define the package statically as:
create or replace package xxx_compilation_constants
as
c_make_public_xxx_schema constant boolean :=
sys_context('userenv','instance_name') = 'MY_TEST_DB';
end;
You might also look at conditional compilation, though I'm not really seeing a use for it in your example.
Related
I made a package that compiles fine but when I try to test it it gives me "invalid data type".
I've tried two different ways, first one like this
select pkg_contabilidad.f_totalizar_Detalle(100) FROM DUAL;
It gives me the ORA-00902 'invalid data type'
Also I've tried this
DECLARE
TYPE r_registro IS RECORD
(rubro_contable CN_RUBROS_CONTABLES.COD_RUBRO%TYPE,
tipo VARCHAR2(1),
monto NUMBER(16));
resultao r_registro;
numero NUMBER :=100;
BEGIN
resultao := pkg_contabilidad.f_totalizar_detalle(numero);
END;
It gives me another error PLS-00382 'expression is of wrong type'
I don't know what am I doing wrong, cause my function receives just one parameter and is of type NUMBER, so I dont know where's my mistake. I'll leave the code of my package just in case
CREATE OR REPLACE PACKAGE pkg_contabilidad AS
TYPE r_registro IS RECORD
(rubro_contable CN_RUBROS_CONTABLES.COD_RUBRO%TYPE,
tipo VARCHAR2(1),
monto NUMBER(16));
TYPE t_detalle IS TABLE OF
r_registro INDEX BY BINARY_INTEGER;
FUNCTION f_totalizar_detalle(p_clave NUMBER)RETURN t_detalle;
END pkg_contabilidad;
/
CREATE OR REPLACE PACKAGE BODY pkg_contabilidad AS
B_detalle t_detalle;
i integer :=1;
FUNCTION f_totalizar_detalle(p_clave NUMBER) RETURN t_detalle IS
v_detalle t_detalle;
CURSOR c_facturado IS
SELECT c.cod_rubro, 'H', CASE WHEN SUM(d.gravada)=0 THEN SUM(d.iva) ELSE SUM(d.gravada) END
FROM fn_documentos_det d JOIN fn_conceptos c ON d.cod_concepto = c.cod_concepto
WHERE d.clave_doc=p_clave
GROUP BY c.cod_rubro;
CURSOR c_datos IS
SELECT SUM(d.total_doc), 'D',r.cod_rubro
FROM fn_documentos d JOIN fn_cajas_ctas r ON d.num_caja_cta = r.num_caja_cta
WHERE d.clave_doc = p_clave
GROUP BY r.cod_rubro;
BEGIN
open c_datos;
LOOP
FETCH c_datos INTO v_detalle(1);
END LOOP;
CLOSE c_datos;
FOR fila IN c_facturado LOOP
i := i + 1;
v_detalle(i) := fila;
END LOOP;
END;
END PKG_CONTABILIDAD;
The function returns a pkg_contabilidad.t_detalle, so the test needs to be:
declare
resultao pkg_contabilidad.t_detalle;
numero number := 100;
begin
resultao := pkg_contabilidad.f_totalizar_detalle(numero);
end;
It doesn't work in SQL because pkg_contabilidad.t_detalle is a PL/SQL type, not a SQL type (create or replace type). The database can perform some automatic conversions, but there are still limitations.
By the way, this loop will never complete because it lacks an exit condition:
open c_datos;
loop
fetch c_datos into v_detalle(1);
end loop;
close c_datos;
Your function returns a PL/SQL table type, with a table of a PL/SQL record type, which is defined in your package, which plain SQL doesn't know about and can't display - hence your invalid datatype error. If you need to call the function and access the data from SQL you can create schema-level object and collection types instead.
In your anonymous block you are a declaring a new record type. That looks the same to you because the structure is the same, but Oracle expects the exact type the function returns. That makes your test code shorter and simpler though. But you are also trying to return the whole collection into a single record.
DECLARE
l_detalle pkg_contabilidad.t_detalle;
l_registro pkg_contabilidad.r_registro;
l_idx pls_integer;
numero NUMBER :=100;
BEGIN
l_detalle := pkg_contabilidad.f_totalizar_detalle(numero);
l_idx := l_detalle.FIRST;
WHILE l_idx is not null LOOP
l_registro := l_detalle(l_idx);
-- do something with this record
dbms_output.put_line(l_registro.tipo);
l_idx := l_detalle.NEXT(l_idx);
END LOOP;
END;
db<>fiddle with dummy cursors.
Your function is a bit strange and probably isn't doing quite what you want; but also has two fatal problems: it isn't returning anything, and it has an infinite loop. I've fixed those for the fiddle but not anything else, as this seems to be an exercise.
I've got code as follows:
CREATE OR REPLACE TYPE cat_array_t is varray(2) of varchar(10);
-- cat_array cat_array_t:=cat_array_t('retired','worker');
/
CREATE OR REPLACE FUNCTION Get_Data_Faster(in_cat IN cat_array, in_kw_crt IN kw_crt_array) RETURN get_data_faster_data PIPELINED AS
TYPE r_cursor IS REF CURSOR;
query_results r_cursor;
results_out get_data_faster_row := get_data_faster_row(NULL, NULL);
query_str VARCHAR2(4000);
cat_value VARCHAR2(10);
kw_crt_value VARCHAR2(10);
BEGIN
FOR i IN 1..cat_array.COUNT
LOOP
cat_value := cat_array(i);
kw_crt_value := kw_crt_array(i);
-- query_str := 'SELECT distinct '||seq_number||' as seq, value, item
-- FROM my_table ai';
--
-- query_str := query_str || '
-- WHERE ai.value = '''||the_value||''' AND ai.item = '''||the_item||'''
-- AND ai.param = ''BOOK''
-- AND ai.prod in (' || list || ');
query_str := 'select owner_id,property_id ' ||
'from owners ' ||
'where substr(PROPERTY_ID,1,4) =' || chr(39) || kw_crt_value || chr(39) ||
' and Owner_category = ' || chr(39) || cat_value || chr(39);
OPEN query_results FOR query_str;
LOOP
FETCH query_results INTO
results_out.owner_id,
results_out.property_id;
EXIT WHEN query_results%NOTFOUND;
PIPE ROW(results_out);
END LOOP;
CLOSE query_results;
END LOOP;
END;
/
The problem is when I run this I get error.
In log there is "Error: PLS-00201: identifier "CAT_ARRAY" should be declared". Don't how to improve this code. How to pass this line with elements of varray to code
-- cat_array cat_array_t:=cat_array_t('retired','worker');
In addition to the other answers, you're referencing your array with (almost) the name of the type, not the name of the array, e.g.:
FOR i IN 1..cat_array.COUNT
should be
FOR i IN 1..in_cat.COUNT
since you have defined the name of the parameter as in_cat here:
CREATE OR REPLACE FUNCTION Get_Data_Faster(in_cat IN cat_array_t, ....
^^^^^^
You must replace all instances of the variable name cat_array with the correct name in_cat.
You have
CREATE OR REPLACE TYPE cat_array_t ...
but then your function has
CREATE OR REPLACE FUNCTION Get_Data_Faster(in_cat IN cat_array, ...
The type name doesn't match; it should be
CREATE OR REPLACE FUNCTION Get_Data_Faster(in_cat IN cat_array_t, ...
and you don't need/want the separate local PL/SQL type declaration, or the local variable; and you need to refer to the passed-in parameter name (as #Boneist beat me too):
FOR i IN 1..in_cat.COUNT
You may have done the same thing with in_kw_crt IN kw_crt_array.
Of course it returns an error.
CREATE OR REPLACE FUNCTION Get_Data_Faster (in_cat IN cat_array)
RETURN NUMBER ^^^^^^^^^^ type is used here
IS
TYPE cat_array_t IS VARRAY (2) OF VARCHAR (10); --> type is declared here
cat_array cat_array_t:=cat_array_t('emeryt','pracownik');
You're first referencing a type which is declared within the function. You can't do that; create type at SQL level, not within the function.
Your type cat_array and cat_array_t must be SQL type defined as an individual as follows:
CREATE [OR REPLACE ] TYPE type_name AS | IS
VARRAY(max_elements) OF element_type [NOT NULL];
Now, this type can be used globally in your PL/SQL code.
I have a scenario that I want to execute dynamically fetched methods from a cursor with different arguments. Those argument values are replaced (using Get_Parameter_Value___(head_rec_.objkey,parameter_); ) with values in a loop as you can see in the following example.
PROCEDURE Dynamic_exe(
keyvalue_ IN VARCHAR2)
IS
param_str_ VARCHAR2(2000);
temp_param_str_ VARCHAR2(2000);
method_stmt_ VARCHAR2(100);
CURSOR get_method IS
SELECT exe_method
FROM method_tab
BEGIN
param_str_ := Substr(rec_.exe_method,Instr(rec_.exe_method,'(')+1,(Instr(rec_.exe_method,')')-1)-Instr(rec_.exe_method,'('));
temp_param_str_ := param_str_;
method_stmt_ := rec_.exe_method;
WHILE temp_param_str_ IS NOT NULL LOOP
IF (Instr(temp_param_str_,',') > 0 )THEN
parameter_ := trim(Substr(temp_param_str_,1,Instr(temp_param_str_,',')-1));
temp_param_str_ := Substr(temp_param_str_,Instr(temp_param_str_,',')+1);
ELSE
parameter_ := trim(temp_param_str_);
temp_param_str_ := NULL;
END IF;
parameter_value_ := Get_Parameter_Value___(head_rec_.objkey,parameter_);
method_stmt_ := REPLACE(method_stmt_,parameter_,''''||parameter_value_||'''');
END LOOP;
FOR rec_ IN get_method LOOP
EXECUTE IMMEDIATE 'BEGIN '||method_stmt_||'; END;';
END LOOP;
END Dynamic_exe;
This is not safe, SQL injection can be done for this, I need a solution, associated with bind variables, Does anyone have a solution for this?
You can eliminate the possibility of SQL injection by using DBMS_ASSERT.SQL_OBJECT_NAME to protect the method name, and use DBMS_SQL and bind variables to protect the arguments.
DBMS_ASSERT.SQL_OBJECT_NAME throws an error if the value is not the same as an existing object. (Although for packages it only checks that the package name exists, not the procedure name. But the procedure name must still be a realistic name.)
For example, if the package name exists, the function will simply return the name:
SQL> select dbms_assert.SQL_OBJECT_NAME('test_package.test_procedure') name from dual;
NAME
--------------------------------------------------------------------------------
test_package.test_procedure
But any SQL injection shenanigans will raise an exception (which you can catch and handle if necessary):
SQL> select dbms_assert.sql_object_name('; delete from employees;') from dual;
select dbms_assert.sql_object_name('; delete from employees;') from dual
*
ERROR at line 1:
ORA-44002: invalid object name
ORA-06512: at "SYS.DBMS_ASSERT", line 401
Instead of building the entire statement as a string, add :bind_variable_n and DBMS_SQL to run it.
So the final string will look something like this (add the bind variable numbers in the loop):
method_stmt_ := 'begin '||method_name||'(:1, :2); end;';
Executing a dynamic number of bind variables requires DBMS_SQL.BIND_VARIABLE. Switching from native dynamic SQL to DBMS_SQL is going to be annoying, but it will let you pass in the bind variables without any injection concerns.
I'm struggling to create a dynamic sql parametrized query. It involves using 'IS NULL' or 'IS NOT NULL'
Here's a simple pl/sql query:
CREATE OR REPLACE PROCEDURE GET_ALL_INFORMATION
(
"PARAM_START_DATE" IN DATE,
"PARAM_END_DATE" IN DATE,
"PARAM_IS_SUBMITTED" IN NUMBER,
"EXTRACT_SUBMITTED_CONTACTS" OUT sys_refcursor
) IS
sql_stmt VARCHAR2(3000);
PARAM_CONDITION VARCHAR2(20);
BEGIN
IF PARAM_IS_SUBMITTED = 1 THEN
PARAM_CONDITION := 'NOT NULL';
ELSE
PARAM_CONDITION := 'NULL';
END IF;
sql_stmt := ' SELECT
REGISTRATION_NUMBER,
NAME PROVIDER_TYPE,
ORGANIZATION
FROM TABLE_A
WHERE
P.DATE_FINALIZED IS :A;
OPEN EXTRACT_SUBMITTED_CONTACTS FOR sql_stmt USING PARAM_CONDITION;
Whereas the parameter (:A) in (USING PARAM_CONDITION) should have 'NULL' or 'NOT NULL'. It does not seem to work the way I envisioned.
Am I missing something?
As explained by GriffeyDog in a comment above, bind parameters could only be used as place holder for values. Not to replace keywords or identifiers.
However, this is not really an issue here, as you are using dynamic SQL. The key idea ifs that you build your query as a string -- and it will be parsed at run-time by the PL/SQL engine when you invoke EXECUTE or OPEN .. FOR.
Simply said, you need a concatenation -- not a bound parameter:
...
sql_stmt := ' SELECT
REGISTRATION_NUMBER,
NAME PROVIDER_TYPE,
ORGANIZATION
FROM TABLE_A
WHERE
P.DATE_FINALIZED IS ' || PARAM_CONDITION;
-- ^^
OPEN EXTRACT_SUBMITTED_CONTACTS FOR sql_stmt;
I need to call a function dynamically (say handler() ) in PL/SQL function which returns a Nested Table.
Code:
BEGIN
...
v_function := 'handler'; //shown like this of simplicity.
EXECUTE IMMEDIATE 'BEGIN :result := ' || v_function || '(...); END;'
USING OUT v_error_msg;
... //process v_error_msg
END;
and the handler() specification:
TYPE t_error_msgs IS TABLE OF VARCHAR2(2000);
FUNCTION handle (...)
RETURN t_error_msgs;
Issue is I get PL-00457:expressions have to be of SQL types while compiling as execute immediate wont allow non-sql types to be binded.
Is there any way around ?
It depends what you mean by 'workaround' The type will have to be declared at SQL level, not within a PL/SQL block (presumably a package in this case). This would work, for example:
CREATE OR REPLACE TYPE t_error_msgs AS TABLE OF VARCHAR2(2000)
/
CREATE OR REPLACE PACKAGE p42 AS
FUNCTION handler RETURN t_error_msgs;
END p42;
/
CREATE OR REPLACE PACKAGE BODY p42 AS
FUNCTION handler RETURN t_error_msgs IS
BEGIN
RETURN null; -- put real data here, obviously...
END handler;
END p42;
/
DECLARE
v_error_msg t_error_msgs;
v_function varchar2(30);
BEGIN
v_function := 'p42.handler';
EXECUTE IMMEDIATE 'BEGIN :result := ' || v_function || '; END;'
USING OUT v_error_msg;
END;
/
Alternatively you can reconsider whether you really need this to be dynamic. Presumably you're passing or somehow determining the function to call on the fly and populating v_function. If there's a relatively short list of possible values it might be simpler to have a case with individual static function calls.