Trying to replace dbms_xmlgen.xmlget with sys_xmlagg - sql

I'm working on parameterizing some JDBC queries against Oracle 10gR2.
Most of the queries are of the form:
String value = "somevalue";
String query = "select dbms_xmlgen.xmlget('select c1, c2 from t1 where c1 = ''"
+ somevalue + "'' ') xml from dual;";
I can't parameterize that as is, since the actual select is in a quoted string inside xmlget and parameters are not expanded inside a string. JDBC will see that query as having no parameters.
I've been fairly successful in emulating the behavior of dbms_xmlgen.xmlget with:
String query = "SELECT xmltype.getclobval(sys_xmlagg(xmlelement(\"ROW\","
+ "xmlforest(c1, c2)))) xml from t1 where c1 = ?";
The only issue I have not been able to resolve is the case where the query returns no rows.
With dbms_xmlgen.xmlget, no rows returns an empty CLOB. But, with sys_xmlagg, no rows results in a CLOB consisting of:
<?xml version="1.0"?><ROWSET></ROWSET>
I'm looking for a solution that will give me an empty CLOB instead of an empty document.

I don't have access to an Oracle DB at the moment, so please forgive inaccuracies.
The parameterization of the DBMS_XMLGEN call seems to be the goal. This is accomplished by using a little PL/SQL. The Oracle Docs for the DBMS_XMLGEN package describe a few operations which should help. First, create a context from a SYS_REFCURSOR using this form:
DBMS_XMLGEN.NEWCONTEXT (
queryString IN SYS_REFCURSOR)
RETURN ctxHandle;
Then, use the context in another form of GetXML:
DBMS_XMLGEN.GETXML (
ctx IN ctxHandle,
tmpclob IN OUT NCOPY CLOB,
dtdOrSchema IN number := NONE)
RETURN BOOLEAN;
Using this method also gives the benefit of potentially reusing the CLOB (not making a new temporary one), which may help with performance. There is another form which is more like the one you were using in your example, but loses this property.
One more thing... The return of GETXML in this example should tell you whether there were rows returned or not. This should be more reliable than checking the contents of the CLOB when the operation completes. Alternately, you can use the NumRowsProcessed function on the context to get the count of the rows included in the CLOB.
Roughly, your code would look something like this:
DECLARE
srcRefCursor SYS_REFCURSOR;
ctxHandle ctxHandle;
somevalue VARCHAR2(1000);
myClob CLOB;
hasRows boolean;
BEGIN
OPEN srcRefCursor FOR
SELECT c1, c2
FROM t1
WHERE c1 = somevalue; --Note parameterized value
ctxHandle := DBMS_XMLGEN.NEWCONTEXT(srcRefCursor);
hasRows := DBMS_XMLGEN.GETXML(
ctxHandle,
myClob -- XML stored in myCLOB
);
IF (hasRows) THEN
/* Do work on CLOB here */
END IF;
DBMS_XMLGEN.CLOSECONTEXT(ctxHandle);
END;

Related

Execute a Select inside a loop PL/SQL and return cursor?

This is probably a simple question for who knows PL/SQL.
I have a stored procedure who takes an array of varchar in input:
TYPE MULTI is table of VARCHAR(15) index by BINARY_INTEGER;
PROCEDURE MYPROC(
SINGLE IN MULTI,
P_RESULT OUT MY_PCK.MYCURSOR,
P_SOMETHING OUT VARCHAR2,
);
The cursor works because i have tested it in other cases but this is the first with an array parameter.
I have a problem with the body, how can i assign each value i get from the select to the cursor?
BEGIN
FOR i IN SINGLE.first .. SINGLE.last
LOOP
--BEGIN OPEN P_RESULT FOR this --this on left gives me error
SELECT MT.DESCR INTO P_SOMETHING
FROM MYTABLE1 MT
WHERE MT.IDS = SINGLE(i)
AND and rownum < 2;
--dbms_output.put_line(SINGLE(i)); --if i use this instead of select i get the values i send to this procedure.
END LOOP;
i tried also:
SELECT MT.DESCR INTO P_RESULT but gives error
What i'm doing wrong?
Thanks in advice.
You can't use your PL/SQL collection type in a SQL statement in 11g. You could create a SQL collection type, or find one you already have access to (which has a suitable string length) and use that to at least verify the mechanism.
For instance, this uses a local variable of type SYS.HSBLKNAMLST, which is defined as a table of varchar2(30), more than enough to match your own PL/SQL type's string length. That variable is populated from your passed-in PL/SQL type, and that is then used in a query to open the cursor:
PROCEDURE MYPROC(
SINGLE IN MULTI,
P_RESULT OUT MY_PCK.MYCURSOR
) IS
LOCAL_COLL SYS.HSBLKNAMLST := SYS.HSBLKNAMLST();
BEGIN
FOR i IN SINGLE.first .. SINGLE.last
LOOP
LOCAL_COLL.extend();
LOCAL_COLL(LOCAL_COLL.last) := SINGLE(i);
END LOOP;
OPEN P_RESULT FOR
SELECT MT.DESCR
FROM MYTABLE1 MT
LEFT JOIN TABLE(LOCAL_COLL) LC
ON LC.COLUMN_VALUE = MT.IDS
WHERE LC.COLUMN_VALUE IS NULL;
END;
I'm a bit confused about the query you showed in your loop though; you seem to be attempting to re-open the cursor for each element of the array, though maybe you were trying to append the result of the query for each element to the same cursor - thought the != would mean that all rows would be included at some point. I've guess that you're trying to get all records with IDS values that are not in the array. If you actually want all that are then that would be:
OPEN P_RESULT FOR
SELECT MT.DESCR
FROM TABLE(LOCAL_COLL) LC
JOIN MYTABLE1 MT
ON MT.IDS = LC.COLUMN_VALUE;
You can see which built-in types are available to you by querying the data dictionary, e.g:
select owner, type_name, coll_type, elem_type_name, length
from all_coll_types
where elem_type_name = 'VARCHAR2'
and coll_type = 'TABLE'
and owner = 'SYS'
order by length;
It would be preferable to create your own SQL type if you're able to.

Oracle SQL - SELECT with Variable arguments stored procedure

I'm struggling with a variable argument stored procedure that has to perform a SELECT on a table using every argument passed to it in its WHERE clause.
Basically I have N account numbers as parameter and I want to return a table with the result of selecting three fields for each account number.
This is what I've done so far:
function sp_get_minutes_expiration_default(retval IN OUT char, gc IN OUT GenericCursor,
p_account_num IN CLIENT_ACCOUNTS.ACCOUNT_NUM%TYPE) return number
is
r_cod integer := 0;
begin
open gc for select account_num, concept_def, minutes_expiration_def from CLIENT_ACCOUNTS
where p_account_num = account_num; -- MAYBE A FOR LOOP HERE?
return r_cod;
exception
-- EXCEPTION HANDLING
end sp_get_minutes_expiration_default;
My brute force solution would be to maybe loop over a list of account numbers, select and maybe do a UNION or append to the result table?
If you cast your input parameter as a table, then you can join it to CLIENT_ACCOUNTS
select account_num, concept_def, minutes_expiration_def
from CLIENT_ACCOUNTS ca, table(p_account_num) a
where a.account_num = ca.account_num
But I would recommend you select the output into another collection that is the output of the function (or procedure). I would urge you to not use reference cursors.
ADDENDUM 1
A more complete example follows:
create or replace type id_type_array as table of number;
/
declare
ti id_type_array := id_type_array();
n number;
begin
ti.extend();
ti(1) := 42;
select column_value into n from table(ti) where rownum = 1;
end;
/
In your code, you would need to use the framework's API to:
create an instance of the collection (of type id_type_array)
populate the collection with the list of numbers
Execute the anonymous PL/SQL block, binding in the collection
But you should immediately see that you don't have to put the query into an anonymous PL/SQL block to execute it (even though many experienced Oracle developers advocate it). You can execute the query just like any other query so long as you bind the correct parameter:
select account_num, concept_def, minutes_expiration_def
from CLIENT_ACCOUNTS ca, table(:p_account_num) a
where a.column_value = ca.account_num

SQL injection with function call

Following query gets executed in my program, where 'a' is the parameter value which I am taking as input & passing it in query.
select * from emp where name LIKE LOWER('%a%')
Can anybody tell me whether I can do SQL injection attack on above query or is it safe?
I have seen SQL injection with where clause & like operator but can we do it with function call as well. What can I pass as the instead of 'a' for SQL injection.
I am using PL/SQL editor & Oracle DB.
Risk of SQL-injection appears when your application interacts with environment (other program or user) and assembles SQL query from parts using string concatenation. For example, you can write PL/SQL procedure:
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
execute immediate 'select count(*) from mytable where mycolumn = ' || a into sql_result;
end;
This procedure is vulnerable. You can pass there a string ''abc''' or 1 = 1 or anything like this and it distorts result (or made something worse).
Or you can write it like this:
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
execute immediate 'select count(*) from mytable where mycolumn = :A' using a into sql_result;
end;
And this procedure is not vulnerable.
Also you can write
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
select count(*)
into sql_result
from mytable
where mycolumn = a;
end;
Here is no problems at all, it is the most secure way (it is "static SQL"), but sometimes we need dynamic SQL (like in first two examples).
Why first way is bad and second is good? It is because SQL engine compiles queries almost like other compilers compile their code, like C++ for example. SQL engine compiles query as a "program" and defines possible "variables" in this "program". "Variable" in second procedure is the parameter :A. If query contains "variables", engine asks their values (USING clause) and passes them into compiled query. In first case engine gets concatenated string:
select count(*) from mytable where mycolumn = 'abc' or 1 = 1
considers it like a whole "program" and executes it "as is". In second case engine gets string
select count(*) from mytable where mycolumn = :A
compiles it, defines 1 "variable" A and asks it value, and then passes it to a "program", and that "program" just searches value 'abc' or 1 = 1 in column mycolumn. This works not only with dynamic SQL in PL/SQL code. It works the same way in any language, and all popular frameworks (for java, c#, delphi, etc.) and all popular DBMS provide instruments for safe work as in second example.
Of course, it was simplified example, sometimes consequences could be much more worse.
a can be
1') or 1 = 1 or LIKE LOWER('1
You should make sure the values passed as a are clean maybe by stripping the slashes in a.
You can exploit SQL Injection almost anywhere.
Checkout my white paper, Google "SQL Injection Anywhere" that demonstrates injections in weird places - including function calls.

Postgres run SQL statement from string

I would like to execute a SQL statement based on a string. I have created the SQL statement through a function which returns a string. Can anybody explain how I can execute the statement that is returned? I know that you can't do it in plain SQL, so I was thinking about putting it in a function. The only issue is that the columns in the statement aren't always the same, so I don't know which data types to use for the columns. I'm using Postgres 9.1.0.
For example, suppose the SQL string returned from my function the is:
Select open, closed, discarded from abc
But, it can also be:
Select open from abc
Or
Select open, closed from abc
How can I execute any of these strings, so that the results would be returned as a table with only the columns listed in the statement?
Edit: the function is written in PL/pgSQL. And the results will be used for reporting where they don't want to see columns that have no values. So the function that I wrote returns the names of all columns that have values and then add it to the SQL statement.
Thanks for your help!
I don't think you can return the rows directly from a function, because its return type would be unknown. Even if you specified the return type as RECORD, you'd have to list the returned columns at call time. Based on Wayne Conrad's idea, you could do this:
CREATE FUNCTION my_create(cmd TEXT) RETURNS VOID AS $$
BEGIN
EXECUTE 'CREATE TEMPORARY TABLE temp_result AS ' || cmd;
END;
$$ VOLATILE LANGUAGE plpgsql;
Then use the function like this:
BEGIN;
SELECT my_create(...);
SELECT * FROM temp_result;
ROLLBACK; -- or COMMIT

using comma separated values inside IN clause for NUMBER column

I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/