Sanitizing user inputs that are part of dynamic PL/SQL command - plsql

I have a table filled by users and its VARCHAR2 records contain a part of executable PL/SQL code "IN(user_input)". I wonder how I can sanitize these user inputs or maybe revrite it to be more efficient. All my ideas failed so far. For example:
A Bind variable is not accepted in this case
DBMS_ASSERT.enquote_literal always raise an exception etc.
Thank you very much for any help.
/* A "MY_PARAMETER" column is part of SQL: ...WHERE MY_DATA IN(MY_PARAMETER)... */
CREATE TABLE my_parameter_table (
"ID" INTEGER NOT NULL ENABLE,
"MY_PARAMETER" VARCHAR(255) NOT NULL ENABLE
)
INSERT INTO my_parameter_table ("ID","MY_PARAMETER") VALUES (1,'6,7,8');
INSERT INTO my_parameter_table ("ID","MY_PARAMETER") VALUES (2,'''b'',''g'',''k''');
INSERT INTO my_parameter_table ("ID","MY_PARAMETER") VALUES (3,'SELECT dummy FROM dual'); -- return "X"
/* Tested table with data */
CREATE TABLE my_data_table (
"ID" INTEGER NOT NULL ENABLE,
"MY_DATA" VARCHAR(255) NOT NULL ENABLE,
"MY_RESULT" VARCHAR(255) NOT NULL ENABLE
);
INSERT INTO my_data_table ("ID","MY_DATA","MY_RESULT") VALUES (1,'a','NOT');
INSERT INTO my_data_table ("ID","MY_DATA","MY_RESULT") VALUES (2,'b','THIS'); --WILL PASS
INSERT INTO my_data_table ("ID","MY_DATA","MY_RESULT") VALUES (3,'c','NOT');
INSERT INTO my_data_table ("ID","MY_DATA","MY_RESULT") VALUES (4,'X','IS'); --WILL PASS
INSERT INTO my_data_table ("ID","MY_DATA","MY_RESULT") VALUES (5,'Y','NOT');
INSERT INTO my_data_table ("ID","MY_DATA","MY_RESULT") VALUES (6,'Z','CORRECT'); --WILL PASS
/* Result table where results are inserted */
CREATE TABLE my_result_table (
"MESSAGE" VARCHAR(255) NOT NULL ENABLE
);
/* ------------------------------------------- */
DECLARE
where_condition VARCHAR2(1000) := '';
v_query VARCHAR2(1000) := '';
insert_or VARCHAR2(5) := '';
CURSOR test_parameter_cur IS
(
SELECT * FROM my_parameter_table
);
test_parameter_rec test_parameter_cur%ROWTYPE;
BEGIN
/* Read all parameters and build an WHERE condition */
OPEN test_parameter_cur;
LOOP
FETCH test_parameter_cur INTO test_parameter_rec;
EXIT WHEN test_parameter_cur%NOTFOUND;
/* Condition check can be any type. Varchar, number, date or some subselect */
IF test_parameter_rec.ID = 1 THEN where_condition := where_condition || insert_or || 'd.ID IN('|| test_parameter_rec.MY_PARAMETER ||')';
ELSE where_condition := where_condition || insert_or || 'd.MY_DATA IN('|| test_parameter_rec.MY_PARAMETER ||')';
END IF;
insert_or := ' OR '; -- after first run the "OR" operator is inserted in front of each where condition
END LOOP;
CLOSE test_parameter_cur;
v_query := 'INSERT INTO my_result_table(MESSAGE)
(SELECT d.MY_RESULT FROM my_data_table d
WHERE '|| where_condition ||')';
EXECUTE IMMEDIATE v_query;
COMMIT;
END;
/* Now the my_result_table contains 3 records: THIS, IS, CORRECT */
SELECT * FROM my_result_table;
DROP TABLE my_parameter_table;
DROP TABLE my_data_table;

Have a look here : https://blogs.oracle.com/aramamoo/entry/how_to_split_comma_separated_string_and_pass_to_in_clause_of_select_statement
Works like a charme for ',' - separated list. I recommend to TRIM() the result to remove trailing and leading blanks from the results.
You can use the select-statement as a subselect in an in-clause.

Related

Oracle - store large string in CLOB

I need to save a procedure body into a Clob column with a use of variable. String is longer than 4000 characters, so I can't use VarChar2, but with CLOB variable I receive error "ORA-01422: exact fetch returns more than requested number of rows". Same error appears with Varchar2. My PL/SQL block:
DECLARE
txt_procedure CLOB;
BEGIN
SELECT text INTO txt_procedure
FROM all_source
WHERE name = 'My_procedure'
ORDER BY line;
INSERT INTO TABLE1(ID,DATE,CLOB_COLUMN)
VALUES (my_seq.NEXTVAL,'11.10.2018',txt_procedure);
END;
/
How could I insert procedure body into clob column ?
As you will get multiple rows from your query for every line of your source, the following might help:
DECLARE
txt_procedure CLOB;
BEGIN
FOR source_r IN ( SELECT text
FROM all_source
WHERE name = 'My_procedure'
ORDER BY line
)
LOOP
txt_procedure := txt_procedure || chr(10) || source_r.text;
END LOOP;
INSERT INTO TABLE1(ID,DATE,CLOB_COLUMN)
VALUES (my_seq.NEXTVAL,'11.10.2018',txt_procedure);
END;
/
UPDATE
As an alternative, you might also use the DBMS_METADATA package for this:
DECLARE
txt_procedure CLOB;
BEGIN
txt_procedure := DBMS_METADATA.get_ddl(
object_type => 'PROCEDURE',
name => 'My_procedure',
owner => 'YOUR_SCHEMA'
);
INSERT INTO TABLE1(ID,DATE,CLOB_COLUMN)
VALUES (my_seq.NEXTVAL,'11.10.2018',txt_procedure);
END;
/

PLSQL NULL or Empty collection in table SELECT

I am trying to figure out a clean way of qualifying on a table type that may be null or empty. Below is what I have tried so far but that results in a
ORA-01427: single-row subquery returns more than one row
DECLARE
V_TEMP_IDS V500.T_TEMP_IDS := V500.T_TEMP_IDS(123, 124);
TYPE T_RESULTS IS TABLE OF NUMBER;
V_RESULTS T_RESULTS;
BEGIN
SELECT TEMP_ID BULK COLLECT INTO V_RESULTS FROM PEOPLE
WHERE ACTIVE = 1
AND TEMP_ID IN (COALESCE(NULLIF((SELECT * FROM TABLE(V_TEMP_IDS)), 0), TEMP_ID));
dbms_output.put_line(SQL%ROWCOUNT);
END;
I would like to select all rows if the collection is null or empty but if it has entries, such as in this example, to include them in the qualification. Hope that makes sense!
Thanks in advance!
NULLIF((SELECT * FROM TABLE(V_TEMP_IDS)), 0) -- is not allowed.
If SELECT * FROM TABLE(V_TEMP_IDS) returns multiple rows, nullif won't work.
(SELECT NULLIF(*, 0) FROM TABLE(V_TEMP_IDS)) -- should be what you want to do..
If you want to check whether v_temp_ids is null or empty, use the is null and is empty operators:
declare
v_temp_ids t_temp_ids := t_temp_ids(123, 124);
type t_results is table of number;
v_results t_results;
begin
select temp_id bulk collect into v_results from people
where active = 1
and ( temp_id member of v_temp_ids
or v_temp_ids is empty
or v_temp_ids is null );
dbms_output.put_line(sql%rowcount);
end;
Test a null collection with
v_temp_ids t_temp_ids;
Test an empty collection with
v_temp_ids t_temp_ids := t_temp_ids();

Oracle trigger to prevent specified value wrote into column

I'm working on Oracle 11g 64bit.
Say I have a table named "MyTable", I'm trying to monitoring a column named "My_Name".
When "My_Name" is going to be changed to '' (before update), I want to stop it and change "My_Name" back to old value. In other word, '' isn't a legal value for the "My_Name" column.
Here's what I did so far, no compilation error, but no effect, I can still write the '' value into "My_Name" column.
CREATE OR REPLACE TRIGGER MyTable_tracking
BEFORE INSERT OR UPDATE ON MyDB.MyTable REFERENCING NEW AS newValue OLD AS oldValue
FOR EACH ROW
DECLARE
v_old VARCHAR(20);
v_new VARCHAR(20);
BEGIN
IF INSERTING THEN
v_new:=:newValue.My_Name; --Trigger checks column 'My_Name' only
ELSIF UPDATING THEN
v_old:=:oldValue.My_Name; --Trigger checks column 'My_Name' only
v_new:=:newValue.My_Name; --Trigger checks column 'My_Name' only
--IF :newValue.My_Name='' THEN
IF LENGTH(TRIM(:newValue.My_Name))=0 THEN
:newValue.My_Name:=:oldValue.My_Name;
END IF;
END IF;
END;
How can I do this?
No need for a trigger. In Oracle an empty string '' and null are the same thing. So just define my_name as NOT NULL and you can't put null or '' in it.
SQL> create table my_table (id integer primary key, my_name varchar(20) not null);
Table created.
SQL> insert into my_table values (1, 'Arthur');
1 row created.
SQL> update my_table set my_name = '' where id = 1;
update my_table set my_name = '' where id = 1
*
ERROR at line 1:
ORA-01407: cannot update ("ARTHUR"."MY_TABLE"."MY_NAME") to NULL
SQL> insert into my_table values (2, '');
insert into my_table values (2, '')
*
ERROR at line 1:
ORA-01400: cannot insert NULL into ("ARTHUR"."MY_TABLE"."MY_NAME")
SQL>
If you can't use the most efficient solution because of external restrictions (which I find highly questionable), you can use something like this:
create or replace trigger slow_not_null_check
before insert or update on my_table
for each row
begin
if inserting and :new.my_name is null then
:new.my_name := 'No NULL allowed';
end if;
if updating and :new.my_name is null then
:new.my_name := :old.my_name;
end if;
end;
/
This will silently convert, '' to 'No NULL allowed' when inserting and will restore the previous value when updating:
insert into my_table values (1, '');
insert into my_table values (2, 'Arthur');
select * from my_table;
ID | MY_NAME
---+----------------
1 | No NULL allowed
2 | Arthur
update my_table
set my_name = ''
where id = 2;
select *
from my_table;
ID | MY_NAME
---+----------------
1 | No NULL allowed
2 | Arthur
I fixed my problem whit following code
IF INSERTING THEN
v_new:=:newValue.My_Name;
ELSIF UPDATING THEN
v_old:=:oldValue.My_Name;
v_new:=:newValue.My_Name;
--IF :newValue.My_Name='some specified value' THEN --not allow some value
--IF :newValue.My_Name='' --not working
IF :newValue.My_Name='' OR :newValue.My_Name IS NULL THEN --not allow '' or null
:newValue.My_Name:=v_old; --works
--:newValue.My_Name:=:oldValue.My_Name; --not working, use variable instead
END IF;
END IF;

How to restrict special characters < and > in input value for a column

I am not that fluent in using regular expressions. I want that user should not provide value for a column with '<' or '>' in it.
Regards,
Sachin
Use group () and or | operators for example to validate user input you sould do:
set serveroutput on;
declare
l_value varchar2(32767) := '<p>test and data</p>';
begin
if regexp_like(l_value,'(<|>)','i') then
dbms_output.put_line('invalid');
else
dbms_output.put_line('valid');
end if;
end;
/
Good luck.
Use [ and ] to define a set of characters to match, e.g. [abc], [a-z], [a-z0-9_]
select string
, case
when regexp_like(string,'[<>]') then 'Invalid'
else 'Valid'
end as test
from
( select '<p>text</p>' as string from dual union all
select 'text' from dual );
STRING TEST
---------------- -------
<p>text</p> Invalid
text Valid
Or in PL/SQL:
declare
teststring varchar2(100) := '<p>test and data</p>';
regex varchar2(100) := '[<>]';
begin
dbms_output.put('"'||teststring||'"');
dbms_output.put(case when regexp_like(teststring,regex) then ' matches ' else ' does not match ' end );
dbms_output.put(regex);
dbms_output.new_line();
end;
/
"<p>test and data</p>" matches [<>]
PL/SQL procedure successfully completed
As a check constraint:
create table mytable
( col varchar2(20)
constraint mytable_ltgt_chk check ( not regexp_like(col,'[<>]') )
);
Test:
insert into mytable (col) values ('kitten > puppy');
rejected with:
ORA-02290: check constraint (MYRIAD_OWNER_82.MYTABLE_LTGT_CHK) violated
If you wanted to exclude square brackets as well, that would be:
constraint mytable_symbol_chk check ( not regexp_like(col,'[][<>]') );
Or without any regex:
constraint mytable_symbol_chk check ( col = translate(col,'[]<>','.') )
https://regex101.com/r/oQJztM/2/tests

How to use a comma-separated list of strings as pl/sql stored function parameter inside a "NOT IN" clause of a select statement

I have a list of comma-separated strings (from a user input) and I'd like to use this list as a parameter in a pl/sql stored function in a nested sql block using a "not in where clause".
I can't find an elegant way to make it work...
That's what I'm thinking of:
CREATE TABLE example ( somevalue VARCHAR(36) NOT NULL);
--
INSERT INTO example VALUES ('value1');
INSERT INTO example VALUES ('value2');
INSERT INTO example VALUES ('value3');
--
SELECT * FROM example;
--
CREATE OR REPLACE
FUNCTION resultmaker(
ignoreList IN VARCHAR2)
RETURN VARCHAR2
IS
result VARCHAR2(4000);
BEGIN
result := 'Here is my calculated result, using ignorelist=' || ignoreList || ':' || CHR(10);
FOR rec IN
(SELECT DISTINCT somevalue
FROM example
WHERE somevalue NOT IN resultmaker.ignoreList -- here's my issue, the NOT IN clause using the parameter value
)
LOOP
result := result || 'not in ignorelist: ' || rec.somevalue || CHR(10);
END LOOP;
result := result || '.' || CHR(10);
--
RETURN result;
END resultmaker;
/
--
-- simulate function call with user input 'value2, value3'
SELECT resultmaker('value2, value3') FROM dual; -- doesn't work
--
DROP TABLE example;
DROP FUNCTION resultmaker;
Just pass the parameter like '"value2","value3"' and have your statement replace the double quote with single quotes like REPLACE(#Param1,'"','''').
Call to function: SELECT * FROM Function1('"value2","value3"')
Inside function: NOT IN REPLACE(#Param1,'"','''')
In every case you should parse that input. As there is no built-in string tokenizer in PL/SQL (at least I couldn't find it) You may want to look into these options,
http://blog.tanelpoder.com/2007/06/20/my-version-of-sql-string-to-table-tokenizer/
Does PL/SQL have an equivalent StringTokenizer to Java's?
After you parsed the string, you may create a new string like:
not_in_statement varchar2(1000);
CURSOR c1 IS select token from tokenized_strings_table;
BEGIN
not_in_statement := '('
FOR rec IN c1 LOOP
not_in_statement := not_in_statement || '''||rec.token||'''||','
END LOOP
not_in_statement := not_in_statement||')'
END
SELECT DISTINCT somevalue
FROM example
WHERE somevalue NOT IN not_in_statement
You may need to make it dynamic SQL, I did not have time to try.
Here's my solution using dynamic sql for my original question above:
CREATE TABLE example ( somevalue VARCHAR(36) NOT NULL);
--
INSERT INTO example VALUES ('value1');
INSERT INTO example VALUES ('value2');
INSERT INTO example VALUES ('value3');
--
SELECT * FROM example;
--
CREATE OR REPLACE
FUNCTION resultmaker(
ignoreList IN VARCHAR2)
RETURN VARCHAR2
IS
result VARCHAR2(4000);
example_cursor sys_refcursor;
rec example.somevalue%type;
BEGIN
result := 'Here is my calculated result, using ignorelist=' || ignoreList || ':' || CHR(10);
OPEN example_cursor FOR ( 'SELECT DISTINCT somevalue FROM example WHERE somevalue NOT IN (' || ignoreList || ')' );
FETCH example_cursor INTO rec;
WHILE example_cursor%found
LOOP
result := result || 'not in ignorelist: ' || rec || CHR(10);
FETCH example_cursor INTO rec;
END LOOP;
CLOSE example_cursor;
result := result || '.' || CHR(10);
--
RETURN result;
END resultmaker;
/
--
-- simulate function call with user input 'value2', 'value3'
SELECT resultmaker('''value2'', ''value3''') FROM dual;
--
DROP TABLE example;
DROP FUNCTION resultmaker;
The classic and probably correct solution would be to use PL/SQL table passing it as prameter...
There are some good solutions at asktom.oracle.com regarding taking a string of values and dynamically creating an IN clause for them:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:210612357425

Resources