How to find the tables affected in a PL SQL package? - sql

I have to find out the list of tables into which insertion or updation happens in my existing PL SQL package. I started analyzing the package. The concern is the package code runs in thousands of lines of code and in turn
calls many other packages. Also, the code was not written by me. I cannot run AWR report since it is Development environment.
Is there a way to get the tables into which insertion/updation happens after initiating the transaction?
Could a trigger be written to suit my requirement?

-- plain
select *
from dba_dependencies
where name = 'PACKAGE_NAME' and owner = 'PACKAGE_OWNER'
and type in ('PACKAGE', 'PACKAGE BODY') and referenced_type = 'TABLE';
-- hierarchy
select distinct referenced_owner, referenced_name, referenced_type, referenced_link_name
from dba_dependencies
where referenced_type = 'TABLE'
start with name = 'PACKAGE_NAME' and owner = 'PACKAGE_OWNER'
and type in ('PACKAGE', 'PACKAGE BODY')
connect by nocycle prior referenced_name = name and prior referenced_owner = owner
and replace(prior referenced_type, 'PACKAGE BODY', 'PACKAGE') = replace(type, 'PACKAGE BODY', 'PACKAGE')
and referenced_owner not in ('SYS', 'SYSTEM', 'OUTLN' , 'AUDSYS')
order by 1, 2, 3;

#akk0rd87 has the better answer -- DBA_DEPENDENCIES, with a CONNECT BY to get table usages by called procedures.
The only thing his answer wouldn't find is tables used directly because of dynamic SQLs (e.g., EXECUTE IMMEDIATE). For that, you can use fine-grained auditing.
To me, this approach is just a backup after you follow #akk0rd87's advice.
Since SO in not a free code writing service, I'll just give you the broad strokes.
1) Create a table to serve as your audit trail. Make sure it has the following columns, at least:
SCHEMA_NAME (30 chars)
TABLE_NAME (30 chars)
CALL_STACK (4000 chars)
SQL_STMT (4000 chars)
2) Create a package to serve as an audit handler. I will give you code for this, because it must follow the exact API I give to be usable with fine grained auditing.
CREATE OR REPLACE PACKAGE BODY xxcust_table_access_aud_pkg AS
PROCEDURE audit_access ( schema_name VARCHAR2, table_Name VARCHAR2, policy_name VARCHAR2 ) IS
BEGIN
INSERT INTO your_audit_table ( SCHEMA_NAME, TABLE_NAME, CALL_STACK, SQL_STMT )
VALUES ( schema_name,
table_Name,
substr(DBMS_UTILITY.format_call_stack,1,4000),
substr(SYS_CONTEXT ('userenv', 'CURRENT_SQL'),1,4000)
)
EXCEPTION
WHEN others THEN
null;
END;
END xxcust_table_access_aud_pkg ;
3) Loop through all the tables in your application schema and call DBMS_FGA.ADD_POLICY for each one. E.g.,
FOR r IN ( ... all my tables ... ) LOOP
DBMS_FGA.add_policy(object_schema=> r.owner,
object_name => r.table_name,
policy_Name => -- make up something unique, maybe table_name plus some number,
audit_condition => '1=1',
audit_column => null,
handler_schema => -- your schema,
handler_module => 'XXCUST_TABLE_ACCESS_AUD_PKG', -- the package above
enable => true);
4) Run your package and check the table for results
5) Repeat step 3, but drop the policies instead of adding them.

Not sure if a Trigger can be a solution, Below can be one of the solutions, with some manual effort though:
DO a grep on the main package to find the other package names, note the names of all packages including the main one.
Get the source code of all these packages from " all_source" table, with where clause on "NAME" attribute and specifying the package names identified above.
Spool the source for above packages in a text file and do a grep on insert and update commands.

Related

How to Move a Stored Procedure in Oracle SQL from one Schema to Another?

I am trying to understand what steps I need to undertake in order to move a stored procedure from one schema and into another. The schema that this is currently sitting in is going to be made redundant and I have been asked to move all tables and procedures. I have no trouble with tables but never done anything with procedures hence want to make sure I don't miss anything out.
What I have currently done is look through the procedure and made a list of what its actually doing i.e. dropping/creating and inserting data into tables.
After this I wasn't sure if it was just a case of copying the procedure code and then creating a new procedure on the new schema with the same code and then compiling it.
I would really appreciate it if somebody could advise if I am missing anything in the steps that I am undertaking just to ensure I don't mess things up.
There is no way to "move" an object from one schema to another.
The only practible way I see here is copying the source code and then executing it in the new schema. As #pmdba wrote as comment, you should watch out for schema names like "MYSCHEMA"."TABLENAME" and other references.
If you got too much to copy you may consider writing a block where you automatically read the data of the old schema and create it automatically in the new one.
You can get the data of (nearly) everything with, i.e. procedures:
select * from all_source where owner = 'OLDSCHEMANAME' and type = 'PROCEDURE';
and use it like this:
begin
....
select listagg(text, '') within group (order by line) into proc_code
from all_source
where owner = 'OLDSCHEMANAME'
and type = 'PROCEDURE'
group by name;
execute immediate 'create or replace ' || proc_code; -- perhaps you need to remove the last ';' here
...
end;
Please note that this code is only meant as hint and doesn't need to be taken exactly that way. Also, you may still get errors due to non existing objects, wrong schema references etc..
To get the ddl of a table one may use select dbms_metadata.get_ddl('TABLE','Table_name','Schema_Name') from dual;.
By googling dbms_metadata.get_ddl you might get more info on the DBMS_METADATA-package and how to use it correctly.
As already stated, there is no mechanism to copy one object ( procedure , function or package, etc ) to another schema. One alternative is using all_source, but I prefer DBMS_METADATA because allows you to transfer all dependencies, like for example privileges. Imagine I need to copy a procedure but I need to keep the privileges, with this package I can get everything.
Example
SQL> create procedure myschema1.my_procedure ( p1 number )
2 as
3 var1 number := p1;
4 begin
5 select 1 into var1 from dual;
6 end;
7 /
Procedure created.
SQL> grant execute on myschema1.my_procedure to myuser ;
Grant succeeded.
Now, let's imagine we want to copy the procedure and its privileges to another schema
SQL> set long 99999999 set lines 200 pages 400
SQL> select dbms_metadata.get_ddl('PROCEDURE','MY_PROCEDURE','MYSCHEMA1') from dual ;
DBMS_METADATA.GET_DDL('PROCEDURE','MY_PROCEDURE','MYSCHEMA1')
--------------------------------------------------------------------------------
CREATE OR REPLACE EDITIONABLE PROCEDURE "MYSCHEMA1"."MY_PROCEDURE" ( p1 number )
as
var1 number := p1;
begin
select 1 into var1 from dual;
end;
But, imagine you don't want quotation and neither the editionable argument
SQL> select
replace(dbms_metadata.get_ddl('PROCEDURE','MY_PROCEDURE','MYSCHEMA1','11.2.0'),'"','') as ddl from dual ;
DDL
--------------------------------------------------------------------------------
CREATE OR REPLACE PROCEDURE MYSCHEMA1.MY_PROCEDURE ( p1 number )
as
var1 number := p1;
begin
select 1 into var1 from dual;
end;
Then to get the final command with the new schema owner, we use regexp_replace to replace the first occurrence
SQL> select regexp_replace(replace(dbms_metadata.get_ddl('PROCEDURE','MY_PROCEDURE','MYSCHEMA1','11.2.0'),'"',''),'MYSCHEMA1','MYSCHEMA2',1,1)
2 as ddl from dual ;
DDL
--------------------------------------------------------------------------------
CREATE OR REPLACE PROCEDURE MYSCHEMA2.MY_PROCEDURE ( p1 number )
as
var1 number := p1;
begin
select 1 into var1 from dual;
end;
Finally, we can get all privileges by
SQL> select dbms_metadata.get_dependent_ddl( 'OBJECT_GRANT' , 'MY_PROCEDURE' , 'MYSCHEMA1' ) from dual ;
DBMS_METADATA.GET_DEPENDENT_DDL('OBJECT_GRANT','MY_PROCEDURE','MYSCHEMA1')
--------------------------------------------------------------------------------
GRANT EXECUTE ON "MYSCHEMA1"."MY_PROCEDURE" TO "MYUSER"
Remember to apply at session level before to start some settings to enhance dbms_metadata output:
begin
DBMS_METADATA.set_transform_param (DBMS_METADATA.session_transform, 'SQLTERMINATOR', true);
DBMS_METADATA.set_transform_param (DBMS_METADATA.session_transform, 'PRETTY', true);
end;

How to get the name of stored procedure within a package and a schema accessing a particular table in Oracle?

I have used following query to get the procedure list but I am getting just the schema name and package name. If the TYPE Column returns PACKAGE BODY, then how to get the procedure name within the package accessing the table EXCEPTIONAL_INFO, please help.
SELECT * FROM All_DEPENDENCIES WHERE REFERENCED_NAME = 'EXCEPTIONAL_INFO';
You can scan the source texts of the packages. I've used the view DBA_SOURCE because I believe ALL_SOURCE shows only the code of procedures I can execute.
CREATE TABLE exceptional_info (i INT);
CREATE OR REPLACE PACKAGE p IS
PROCEDURE a;
PROCEDURE b(table_name VARCHAR2);
END p;
/
CREATE OR REPLACE PACKAGE BODY p IS
PROCEDURE a AS
n NUMBER;
BEGIN
SELECT count(*) INTO n FROM exceptional_info;
END a;
PROCEDURE b(table_name VARCHAR2) AS
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE '||table_name;
END b;
END;
/
You can find the packages with DBA_DEPENDENCIES and scan the source text of those dependencies:
SELECT owner, type, name, s.line, s.text
FROM dba_dependencies d
LEFT JOIN dba_source s USING (owner, name, type)
WHERE d.referenced_name = 'EXCEPTIONAL_INFO'
AND upper(s.text) like '%EXCEPTIONAL_INFO%';
OWNER TYPE NAME LINE TEXT
SO PACKAGE BODY P 10 SELECT count(*) INTO n FROM exceptional_info;
You need to go up from line 10 to find the name of the procedure in the source text. I'm too lazy to code that now.
However, this method will find only static text. It cannot find PROCEDURE b in the example, which also can modify table exceptional_info, without having the name hard coded. Do you need to catch those usages, too?
you can try this query:
select owner, object_name, procedure_name
from all_procedures
where object_name =
(
SELECT name FROM All_DEPENDENCIES WHERE REFERENCED_NAME = 'EXCEPTIONAL_INFO'
and type='PACKAGE BODY' and rownum=1
)
;

How to prevent or avoid running update delete statements without where clauses in oracle

How to prevent or avoid running update delete statements without where clauses in oracle? Pls help with this
This seems like the wrong thing to do. It would be better to have some combination of:
(A) revoking access from people who cannot be trusted
(B) giving access through a PL/SQL layer that will limit users to pre-defined operations
(C) sizing your database with enough UNDO to flashback if there is some kind of catastrophic mistake.
However, you can sort of do what you want using fine-grained auditing. Basically, make an audit handler that errors out if the current SQL has no filters. It would be easily fooled though.
Here is an example:
-- Create a table we want to protect
drop table matt1;
create table matt1 ( a number );
-- Put some data into it
insert into matt1
select level from dual connect by rownum <= 100;
commit;
-- Create an audit handler that will protect our table from wide-open updates
-- or deletes
CREATE OR REPLACE PACKAGE matt_table_protector_pkg AS
PROCEDURE table_protector ( schema_name VARCHAR2, table_Name VARCHAR2, policy_name VARCHAR2 );
END matt_table_protector_pkg;
/
CREATE OR REPLACE PACKAGE BODY matt_table_protector_pkg AS
PROCEDURE table_protector ( schema_name VARCHAR2, table_Name VARCHAR2, policy_name VARCHAR2 ) IS
l_filter_count NUMBER;
BEGIN
EXECUTE IMMEDIATE 'EXPLAIN PLAN FOR ' || SYS_CONTEXT('USERENV','CURRENT_SQL');
select count(*)
into l_filter_count
from table(dbms_xplan.display(format=>'PREDICATE'))
where plan_table_output like '% - filter(%'
and plan_table_output not like '%SYS_AUDIT(%';
IF l_filter_count = 0 THEN
raise_application_error(-20001, 'Unrestricted DML is not allowed on this table.');
END IF;
END;
END matt_table_protector_pkg;
/
Next, we Create a fine-grained audit policy to make sure our package is called whenever there is an update or delete on our table.
--EXEC DBMS_FGA.drop_policy (object_schema => user, object_name => 'MATT1', policy_name => 'PROTECT_MATT1');
EXEC DBMS_FGA.add_policy (object_schema => user, object_name => 'MATT1', policy_name => 'PROTECT_MATT1', audit_condition => null, audit_column => NULL, handler_schema => user, handler_module => 'MATT_TABLE_PROTECTOR_PKG.TABLE_PROTECTOR', enable => TRUE, statement_types => 'UPDATE, DELETE');
That's it. You're mostly protected now.
select * from matt1;
100 rows selected
delete from matt1 where a = 7;
1 row deleted
delete from matt1;
ORA-28144: Failed to execute fine-grained audit handler
ORA-20001: Unrestricted DML is not allowed on this table.
ORA-06512: at "APPS.MATT_TABLE_PROTECTOR_PKG", line 15
ORA-06512: at line 1
Because Oracle is smart enough to optimize out a lot of tautologies, you will also be protected against statements like:
delete from matt1 where 1=1;
or
delete from matt1 where 1<2;
But it is still easy to fool. For example, this statement will work.
delete from matt1 where sysdate is not null

If I pass a where clause as a parameter will that prevent SQL Injection?

I created an Oracle proc where I create a dynamic sql statement based on the parameters supplied to the proc.
I've done some testing and it appears that I can't perform sql injection.
Is there anything additional I should be safe guarding against?
SELECT 'UPDATE ' || p_table || ' SET MY_FIELD = ''' || p_Value || ''' ' || p_Where
INTO query_string
FROM DUAL;
EDIT:
Scenarios that I've tried.
1. WHERE SOME_VAL IN ('AAA','BBB') - This works
2. WHERE SOME_VAL IN ('AAA','BBB') OR SOME_VAL2 = '123' - This works.
3. WHERE SOME_VAL IN ('AAA','BBB'); DROP TABLE TEST_TABLE; - This errors out.
4. WHERE SOME_VAL IN ('AAA','BBB') OR (DELETE FROM TEST_TABLE) - This errors out.
It depends on how and by whom your procedure is being invoked. Usually you need to worry about SQL injection for something that is open to large number of users in production. And that should not be the case for any database procedure. If your database procedure is accessible by large number of users, then you have potential for malicious use by someone.
In your case, you can mitigate this risk by creating mapping of parameters to hide actual schema object names and some validation.
For example change parameter p_table to table_name as input parameter. Then using case statement map to actual table name. I am giving you example of table name here because you should really restrict who can access which table from db.
CREATE OR REPLACE PROCEDURE test_proc(table_name IN VARCHAR)
IS
p_table varchar2(100);
BEGIN
CASE table_name
WHEN 'A' THEN p_table:='db_table_a';
WHEN 'B' THEN p_table:='db_table_b';
ELSE RAISE 'Invalid table name parameter';
END CASE;
SELECT 'UPDATE ' || p_table || ' SET MY_FIELD = ''' || p_Value || ''' '
|| p_Where
INTO query_string
FROM DUAL;
END;
You should do similar mapping and validation for other parameters too.
SQL injection always opens Pandora's box.
You should always assume a user can break out of a dynamic SQL statement. With full SQL access you should then assume a user can find a way to escalate privileges and own your database. (Depending on how paranoid you are, it might be safe to assume privilege escalation is impossible as long as your database and schemas are constantly patched and thoroughly hardened. In practice the vast majority of Oracle databases are not sufficiently patched and hardened.)
Below are a few simple examples that should scare you. And you should also assume that there are many hackers who are more clever than I am and have better attacks.
Sample Schema
First let's create a simple table with some data for a realistic test.
drop table test1;
create table test1(my_field varchar2(100), some_val varchar2(100));
insert into test1 values('A', 'AAA');
commit;
Obviously Dangerous Function
Are all of the existing functions safe?
create or replace function dangerous_function return number is
pragma autonomous_transaction;
begin
delete from test1;
commit;
return 1;
end;
/
If not, what is stopping the user from calling it like this?
--Safe static part:
update test1
set my_field = 'b'
--Dangerous dynamic part:
where some_val IN ('AAA')
and 1 = (select dangerous_function from dual)
Luckily creating an autonomous function is unusual and you can probably check the code. But can you guarantee the application will not create one in the future?
Custom Function in SQL
Even if there are no objects a clever user can turn your UPDATE into other DML:
--Safe static part:
update --+ WITH_PLSQL
test1
set my_field = 'b'
--Dangerous dynamic part:
where some_val IN ('AAA')
and 1 = (
with function dangerous_function return number is
pragma autonomous_transaction;
begin
delete from test1;
commit;
return 1;
end;
select dangerous_function from dual
);
I did cheat a little, the above code only works for me with the --+ WITH_PLSQL hint. Without that hint the code throws the error ORA-32034: unsupported use of WITH clause. But that's only a version limitation that might be lifted in the future. Or there might be some clever way to work around it, sometimes hints can break out of their part of the query and reference other sections.
Why Risk It?
Maybe there is a safe way to do it. But why risk it? Everybody in the IT world understands SQL injection bugs now. If you mess up and cause an exploit there will be no sympathy for you.

How to debug ORA-01775: looping chain of synonyms?

I'm familiar with the issue behind ORA-01775: looping chain of synonyms, but is there any trick to debugging it, or do I just have to "create or replace" my way out of it?
Is there a way to query the schema or whatever to find out what the current definition of a public synonym is?
Even more awesome would be a graphical tool, but at this point, anything would be helpful.
As it turns out, the problem wasn't actually a looping chain of synonyms, but the fact that the synonym was pointing to a view that did not exist.
Oracle apparently errors out as a looping chain in this condition.
If you are using TOAD, go to View>Toad Options>Oracle>General and remove TOAD_PLAN_TABLE from EXPLAIN PLAN section and put PLAN_TABLE
The data dictionary table DBA_SYNONYMS has information about all the synonyms in a database. So you can run the query
SELECT table_owner, table_name, db_link
FROM dba_synonyms
WHERE owner = 'PUBLIC'
AND synonym_name = <<synonym name>>
to see what the public synonym currently points at.
The less intuitive solution to this error code seems to be problems with the objects that the synonym is pointing to.
Here is my SQL for finding synonyms that point to erroneous objects.
SELECT S.OWNER as SYN_OWNER, S.SYNONYM_NAME as SYN_NAME,
S.TABLE_OWNER as OBJ_OWNER, S.TABLE_NAME as OBJ_NAME,
CASE WHEN O.OWNER is null THEN 'MISSING' ELSE O.STATUS END as OBJ_STATUS
FROM DBA_SYNONYMS S
LEFT JOIN DBA_OBJECTS O ON S.TABLE_OWNER = O.OWNER AND S.TABLE_NAME = O.OBJECT_NAME
WHERE O.OWNER is null
OR O.STATUS != 'VALID';
Try this select to find the problematic synonyms, it lists all synonyms that are pointing to an object that does not exist (tables,views,sequences,packages, procedures, functions)
SELECT *
FROM dba_synonyms
WHERE table_owner = 'USER'
AND (
NOT EXISTS (
SELECT *
FROM dba_tables
WHERE dba_synonyms.table_name = dba_tables.TABLE_NAME
)
AND NOT EXISTS (
SELECT *
FROM dba_views
WHERE dba_synonyms.table_name = dba_views.VIEW_NAME
)
AND NOT EXISTS (
SELECT *
FROM dba_sequences
WHERE dba_synonyms.table_name = dba_sequences.sequence_NAME
)
AND NOT EXISTS (
SELECT *
FROM dba_dependencies
WHERE type IN (
'PACKAGE'
,'PROCEDURE'
,'FUNCTION'
)
AND dba_synonyms.table_name = dba_dependencies.NAME
)
)
Today I got this error, and after debugging I figured out that the actual tables were misssing, which I was referring using synonyms. So I suggest - first check that whether the tables exists!! :-))
Step 1) See what Objects exist with the name:
select * from all_objects where object_name = upper('&object_name');
It could be that a Synonym exists but no Table?
Step 2) If that's not the problem, investigate the Synonym:
select * from all_synonyms where synonym_name = upper('&synonym_name');
It could be that an underlying Table or View to that Synonym is missing?
A developer accidentally wrote code that generated and ran the following SQL statement CREATE OR REPLACE PUBLIC SYNONYM "DUAL" FOR "DUAL"; which caused select * from dba_synonyms where table_name = 'DUAL';
to return PUBLIC DUAL SOME_USER DUAL rather than PUBLIC DUAL SYS DUAL.
We were able to fix it (thanks to How to recreate public synonym "DUAL"?) by running
ALTER SYSTEM SET "_SYSTEM_TRIG_ENABLED"=FALSE SCOPE=MEMORY;
CREATE OR REPLACE PUBLIC SYNONYM DUAL FOR SYS.DUAL;
ALTER SYSTEM SET "_SYSTEM_TRIG_ENABLED"=true SCOPE=MEMORY;
While Jarrod's answer is a good idea, and catches a broader range of related problems, I found this query found in Oracle forums to more directly address the (originally stated) issue:
select owner, synonym_name, connect_by_iscycle CYCLE
from dba_synonyms
where connect_by_iscycle > 0
connect by nocycle prior table_name = synonym_name
and prior table_owner = owner
union
select 'PUBLIC', synonym_name, 1
from dba_synonyms
where owner = 'PUBLIC'
and table_name = synonym_name
and (table_name, table_owner) not in (select object_name, owner from dba_objects
where object_type != 'SYNONYM')
https://community.oracle.com/message/4176300#4176300
You will not have to wade through other kinds of invalid objects. Just those that are actually in endless loops.
I had a similar problem, which turned out to be caused by missing double quotes off the table and schema name.
We had the same ORA-01775 error but in our case, the schema user was missing some 'grant select' on a couple of the public synonyms.
We encountered this error today.
This is how we debugged and fixed it.
Package went to invalid state due to this error ORA-01775.
With the error line number , We went thru the package body code and found the code which was trying to insert data into a table.
We ran below queries to check if the above table and synonym exists.
SELECT * FROM DBA_TABLES WHERE TABLE_NAME = '&TABLE_NAME'; -- No rows returned
SELECT * FROM DBA_SYNONYMS WHERE SYNONYM_NAME = '&SYNONYM_NAME'; -- 1 row returned
With this we concluded that the table needs to be re- created. As the synonym was pointing to a table that did not exist.
DBA team re-created the table and this fixed the issue.
ORA-01775: looping chain of synonyms
I faced the above error while I was trying to compile a Package which was using an object for which synonym was created however underlying object was not available.
I'm using the following sql to find entries in all_synonyms where there is no corresponding object for the object_name (in user_objects):
select *
from all_synonyms
where table_owner = 'SCOTT'
and synonym_name not like '%/%'
and table_name not in (
select object_name from user_objects
where object_type in (
'TABLE', 'VIEW', 'PACKAGE', 'SEQUENCE',
'PROCEDURE', 'FUNCTION', 'TYPE'
)
);
http://ora-01775.ora-code.com/ suggests:
ORA-01775: looping chain of synonyms
Cause: Through a series of CREATE synonym statements, a synonym was defined that referred to itself. For example, the following definitions are circular:
CREATE SYNONYM s1 for s2 CREATE SYNONYM s2 for s3 CREATE SYNONYM s3 for s1
Action: Change one synonym definition so that it applies to a base table or view and retry the operation.
If you are compiling a PROCEDURE, possibly this is referring to a table or view that does not exist as it is created in the same PROCEDURE. In this case the solution is to make the query declared as String eg v_query: = 'insert into table select * from table2 and then execute immediate on v_query;
This is because the compiler does not yet recognize the object and therefore does not find the reference. Greetings.
I had a function defined in the wrong schema and without a public synonym. I.e. my proc was in schema "Dogs" and the function was in schema "Cats". The function didn't have a public synonym on it to allow Dogs to access the cats' function.
For me, the table name and the synonym both existed but under different owner names. I re-created the tables under the owner name that matched the owner name in synonyms.
I used the queries posted by #Mahi_0707