Creating a DB link if not created inside a stored procedure - oracle

I'm trying to code a stored procedure in Oracle. I don't have a ton of experience with it and I'm running to an issue. The end goal of the procedure is to take data from one DB and put it into another in a different form. I have most of the procedure working it seems but I'm having issues with something that seems like it should be simple. At the beginning of the code, I would like to check to make sure a DB link is created. If not, then I want to create the db link.
This is what I put inside my procedure:
IF (select count(1) from ALL_DB_LINKS where db_link = 'DB_LINK.NAME.COM') = 0 THEN
CREATE DATABASE LINK LINK_NAME
CONNECT TO username IDENTIFIED BY password
USING 'SID';
END IF;
I know the link works because I've done it outside this and did a lookup with it. The error I get when I try to compile is this:
Encountered the symbol "CREATE" when expecting one of the following:
I've done all the googling I think I can do and I cannot figure out what I am doing wrong. To head off the other question I have, I've also tried to do it by putting in:
DECLARE test_count number;
select count(1) into test_count from ALL_DB_LINKS where db_link = 'DB_LINK.NAME.COM';
BEGIN
IF test_count = 0 THEN
CREATE DATABASE LINK LINK_NAME
CONNECT TO username IDENTIFIED BY password
USING 'SID';
END IF;
END;
But I get the same error. I'm also not sure if having a begin inside a begin will work. Any help would be a great... well, help.

Oracle compiles packages and in order to do that all objects referenced in code need to already exist. This includes objects referenced over a database link. Things will get marked invalid if the referenced objects don't exist. Usually a DBA will create and maintain such an environment using PL/SQL scripts and not stored procedures.
If you really want to anyway:
EXECUTE IMMEDIATE q'[ CREATE DATABASE LINK LINK_NAME
CONNECT TO username IDENTIFIED BY password
USING 'SID']';

If you really need to do this, you can create the link using dynamic SQL as Ed Gibbs suggested and Brian showed, as long as the link already exists at the point the procedure is created:
create database link test_link
connect to scott identified by oracle
using 'ORCL';
Database link created.
create or replace procedure p42 as
l_count number;
l_dummy dual.dummy%type;
begin
select count(*) into l_count
from all_db_links
where db_link = 'TEST_LINK';
if l_count = 0 then
execute immediate q'[
create database link test_link
connect to scott identified by oracle
using 'ORCL'
]';
end if;
select dummy into l_dummy from dual#test_link;
dbms_output.put_line('Value from remote database: ' || l_dummy);
end;
/
Procedure created.
exec p42;
Value from remote database: X
PL/SQL procedure successfully completed.
But the procedure won't compile if the link doesn't already exist:
drop database link test_link;
create or replace procedure p42 as
...
end;
/
Warning: Procedure created with compilation errors.
show errors
Errors for PROCEDURE P42:
LINE/COL ERROR
-------- -----------------------------------------------------------------
16/5 PL/SQL: SQL Statement ignored
16/5 PL/SQL: ORA-00942: table or view does not exist
Curiously it doesn't object if you create the procedure while the link exists, and then drop the link; the procedure stays valid, the dynamic SQL works to recreate the link, and the procedure runs successfully:
drop database link test_link;
Database link dropped.
select object_type, object_name, status from all_objects
where object_type = 'PROCEDURE' and object_name = 'P42';
OBJECT_TYPE OBJECT_NAME STATUS
--------------- --------------- ---------------------
PROCEDURE P42 VALID
exec p42;
Value from remote database: X
PL/SQL procedure successfully completed.
I would have expected dropping the link to invalidate the procedure, but apparently not, despite it appearing in all_dependencies. You can't recompile the procedure though:
drop database link test_link;
Database link dropped.
alter procedure p42 compile;
Warning: Procedure altered with compilation errors.
show errors
Errors for PROCEDURE P42:
LINE/COL ERROR
-------- -----------------------------------------------------------------
16/5 PL/SQL: SQL Statement ignored
16/5 PL/SQL: ORA-00942: table or view does not exist
In order to let you compile (or recompile) while the link does not exist, you'd need to reference the link using dynamic SQL as well:
drop database link test_link;
drop database link test_link
*
ERROR at line 1:
ORA-02024: database link not found
create or replace procedure p42 as
....
execute immediate 'select dummy from dual#test_link' into l_dummy;
dbms_output.put_line('Value from remote database: ' || l_dummy);
end;
/
Procedure created.
exec p42;
Value from remote database: X
PL/SQL procedure successfully completed.
drop database link test_link;
Database link dropped.
exec p42;
Value from remote database: X
PL/SQL procedure successfully completed.
drop database link test_link;
Database link dropped.
alter procedure p42 compile;
Procedure altered.
exec p42;
Value from remote database: X
PL/SQL procedure successfully completed.

Related

PL/SQL: ORA-01031: insufficient privileges 6/1 PL/SQL: SQL Statement ignored GRANT

Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production
I'm getting the above message when trying to create a package.
I can simulate with a very simple procedure and select statement.
My understanding is that have SQL select access for a user does not translate to PL/SQL (package) access for the same user, and that an option is to use roles. This has not worked for me.
This is all done with the same user (not apex_180100).
Showing the issue:
This SQL works. It doesn't make sense, but proves that I can select from the tables.
SELECT 1
FROM apex_180100.wwv_flow_activity_log l,
apex_180100.wwv_flow_worksheet_rpts r
WHERE l.ir_report_id IS NOT NULL
AND l.flow_id = 100
AND l.worksheet_id = r.worksheet_id
AND l.ir_report_id = r.id
AND l.flow_id = r.flow_id
AND l.step_id = r.page_id;
I granted select to a role
GRANT SELECT ON apex_180100.wwv_flow_worksheet_rpts TO PRIV_FULL_TABLE;
GRANT SELECT ON apex_180100.wwv_flow_activity_log TO PRIV_FULL_TABLE;
I grant my role to my procedure (ultimately I will grant to my package)
GRANT PRIV_FULL_TABLE TO PROCEDURE p_test;
I get an error when creating this simple sample procedure.
create OR REPLACE procedure p_test is
V_TEST NUMBER;
begin
SELECT 1
INTO V_TEST
FROM apex_180100.wwv_flow_activity_log l,
apex_180100.wwv_flow_worksheet_rpts r
WHERE l.ir_report_id IS NOT NULL
AND l.flow_id = 100
AND l.worksheet_id = r.worksheet_id
AND l.ir_report_id = r.id
AND l.flow_id = r.flow_id
AND l.step_id = r.page_id;
end;
PL/SQL: ORA-01031: insufficient privileges compilation error
Hm, there's something strange in what you are saying. Usually we grant privileges to users, not procedures.
SQL> create procedure p_test as begin
2 null;
3 end;
4 /
Procedure created.
SQL> create role priv_full_table;
Role created.
SQL> grant priv_full_table to procedure p_test;
grant priv_full_table to procedure p_test
*
ERROR at line 1:
ORA-00991: only MAC privileges may be granted to procedures
SQL>
Apart from that, if I understood you correctly, issue is exactly what you thought that solves it: privileges granted to roles won't work in named stored procedures. p_test is a named procedure:
create OR REPLACE procedure p_test is ...
which means that you'll have to grant those privileges directly to user which will be using them.
Thanks to #Littlefoot
I used a workaround. My procedure is relatively simple, I wanted to insert into a custom table from my "problem" tables. I wanted this to be called by an hourly DB job.
As I can select and insert in SQL, but not PL/SQL procedures, I used a SQL script instead of datbase procedure.
i.e I converted my package into a series of SQL statements. I stored this sql script on the server and ran as a DBA job executable.
Not ideal.
PS 'execute immediate' doesn't work either.

In Oracle DB how to avoid decompiling of objects dependent on a DBLink?

There is a DBLink to a remote DB and a package MY_PACKAGE (in the local schema) indirectly depending on it through MY_VIEW.
When DBLink is not valid (for whatever reason, i.e. connection is lost or credentials are changed on the remote) then the package becomes invalid. But it would be great to have it working, because this package has other responsibilities aside from negotiating through this DBLink. And overall, I do not think it is a desirable situation to have the schema object validity depending on an unstable parts like network connection.
I found this situation in a legacy code (see MY_PACKAGE.doSomething()) and I am not sure even whether this is a correct design. Could you share your opinion on this?
What is the proper approach to keep the package in a VALID state even when some DBLinks are not available?
For example, would it be correct to use a dynamic cursor referencing MY_VIEW, which fails to open ONLY when DBLink is not OK (i.e. MY_PACKAGE.doSomethingAgain()). Of course maybe there is a more natural approach to this problem. I will be glad to hear all options that could be appropriate for safe usage of DBLinks in Oracle.
Code:
-- DBLink --------------------------------------------------------------
CREATE DATABASE LINK "MY_LINK"
CONNECT TO "MY_USER" IDENTIFIED BY VALUES ':1' USING 'WHATEVER';
------------------------------------------------------------------------
-- Synonym -------------------------------------------------------------
CREATE OR REPLACE EDITIONABLE SYNONYM "MY_SYNONYM"
FOR "MY_REMOTE_SCHEMA"."MY_REMOTE_VIEW"#"MY_LINK";
------------------------------------------------------------------------
-- View ----------------------------------------------------------------
CREATE OR REPLACE VIEW "MY_VIEW" AS
SELECT a,b,c FROM "MY_SYNONYM";
> Compiler Error
> ORA-02063: preceding 2 lines from MY_LINK
> ORA-01017: invalid username/password; logon denied
> [Oracle][ODBC SQL Server Wire Protocol driver][SQL Server]
> Login failed for user 'MY_USER'. {28000,NativeErr = 18456}
------------------------------------------------------------------------
.
-- Package body --------------------------------------------------------
CREATE OR REPLACE PACKAGE BODY MY_PACKAGE AS
PROCEDURE doSomething()
IS
i NUMBER := 0;
BEGIN
FOR rec IN (SELECT * FROM "MY_VIEW")
LOOP
i := i + 1;
END LOOP;
END;
PROCEDURE doSomethingAgain()
IS
i NUMBER := 0;
my_cur SYS_REFCURSOR;
BEGIN
OPEN my_cur FOR 'SELECT * FROM MY_VIEW';
LOOP
FETCH my_cur -- ...
-- etc ...
i := i + 1;
END LOOP;
END;
END MY_PACKAGE;
> Compiler Error
> 7/4 PL/SQL: SQL Statement ignored
> 7/35 PL/SQL: ORA-04063: view "MY_VIEW" has errors
------------------------------------------------------------------------
The best option you have is to keep database links alive and kicking, I presume.
Another option is to move procedures - that use those suspicious database links - out of that package and make them either standalone stored procedures so that if they become invalid, only they are invalid (and not the whole package), or - possibly - put those procedures (that share the same destiny) into a separate package.
Yet another option is to use dynamic SQL because Oracle doesn't care whether any objects involved are valid or not until you actually try to use them. Something like this:
SQL> create database link my_link
2 connect to whoever
3 identified by whatever
4 using 'database_that_does_not_exist';
Database link created.
SQL> create or replace view my_view as
2 select * from some_table#my_link;
select * from some_table#my_link
*
ERROR at line 2:
ORA-12154: TNS:could not resolve the connect identifier specified
SQL> create or replace procedure my_proc as
2 l_id number;
3 begin
4 select id into l_id
5 from my_view;
6 end;
7 /
Warning: Procedure created with compilation errors.
SQL> show err
Errors for PROCEDURE MY_PROC:
LINE/COL ERROR
-------- -----------------------------------------------------------------
4/3 PL/SQL: SQL Statement ignored
5/8 PL/SQL: ORA-00942: table or view does not exist
But, with dynamic SQL:
SQL> create or replace procedure my_proc as
2 l_id number;
3 begin
4 execute immediate 'select id from my_view' into l_id;
5 end;
6 /
Procedure created.
SQL>
Looks OK, but not for long:
SQL> exec my_proc;
BEGIN my_proc; END;
*
ERROR at line 1:
ORA-00942: table or view does not exist
ORA-06512: at "SCOTT.MY_PROC", line 4
ORA-06512: at line 1
SQL>
Yes, I know - not all code is simple enough to put it into dynamic SQL. Bigger it is, worse problems you have to maintain it, so that's just possibility I wouldn't really want to use.

How to call a PL/SQL Procedure/Package over a Loopback Database Link?

I'm able to call a PL/SQL procedure and package over a real database link fine, but cannot seem to do so over a loopback database link (a database link referring to a schema in the same database, used for testing purposes), in two different databases.
Is there a trick that is required in order to execute remote plsql packages/procedures/functions over a loopback database link?
Local database (foo):
CREATE DATABASE LINK MATTHEW#INST1 CONNECT TO bar IDENTIFIED BY password USING 'MATTHEW';
-- this works fine, as well as selecting from other tables
SELECT * FROM dual#MATTHEW#INST1;
Remote database (bar schema):
create package test_pkg
is
PROCEDURE test_proc;
end;
/
create package body test_pkg
is
procedure test_proc
is
begin
dbms_output.put_line('hello');
end;
end;
/
create procedure test_proc
is
begin
dbms_output.put_line('hello');
end;
/
create or replace function ff return number
is
begin
return 55;
end;
/
Local database (foo)
BEGIN
test_proc#MATTHEW#INST1;
END;
/
Error report:
ORA-06550: line 2, column 5:
PLS-00201: identifier 'TEST_PROC#MATTHEW#INST1' must be declared
BEGIN
test_pkg.test_proc#MATTHEW#INST1;
END;
/
Error report:
ORA-06550: line 2, column 5:
PLS-00201: identifier 'TEST_PKG.TEST_PROC#MATTHEW#INST1' must be declared
select ff#MATTHEW#INST1 from dual;
ORA-00904: "FF": invalid identifier
00904. 00000 - "%s: invalid identifier"
*Cause:
*Action:
Apparently, packages/procedures/functions created over a loopback database link need to be granted to the caller.
-- As the Bar schema
GRANT EXECUTE on test_proc TO foo;
GRANT EXECUTE on test_pkg TO foo;
GRANT EXECUTE on ff to foo;
Of course, this isn't necessary at all on a true database link across two databases, so why is it necessary here?
Looking at the documenation (Scroll down to "Global Name as a Loopback Database Link"):
You can use the global name of a database as a loopback database link without explicitly creating a database link. When the database link in a SQL statement matches the global name of the current database, the database link is effectively ignored.
For example, assume the global name of a database is db1.example.com. You can run the following SQL statement on this database:
SELECT * FROM hr.employees#db1.example.com;
In this case, the #db1.example.com portion of the SQL statement is effectively ignored.
So it appears that Oracle doesn't even use the loopback, thus explaining why grants are required.
Can we see a full top to bottom script? I can't reproduce that error on my db
SQL> create user foo identified by foo;
User created.
SQL> create user bar identified by bar;
User created.
SQL> grant create session, create procedure, create database link to foo;
Grant succeeded.
SQL> grant create session, create procedure, create database link to bar;
Grant succeeded.
SQL>
SQL> conn foo/foo
Connected.
SQL> create database link matthew#inst1 connect to bar identified by bar using 'db122';
Database link created.
SQL> select * from dual#matthew#inst1;
D
-
X
1 row selected.
SQL>
SQL> conn bar/bar
Connected.
SQL> create or replace
2 procedure my_proc is
3 begin
4 null;
5 end;
6 /
Procedure created.
SQL> conn foo/foo
Connected.
SQL> BEGIN
2 my_proc#MATTHEW#INST1;
3 END;
4 /
PL/SQL procedure successfully completed.
SQL>

PLSQL - Create DBLink inside function

I would like to create database link inside of script, and want to receive all table names from the linked database. If I am correct, I need to create database link in order to use, but Oracle does not allow me to create such thing neither inside of my_fn or DECLARE section. Any suggestion?
DECLARE
TYPE tp_col_array IS TABLE OF varchar2(1000);
FUNCTION my_fn(
p_in_dblink_name IN VARCHAR2,
p_in_schema_name IN VARCHAR2)
RETURN varchar2 AS
vr_coll_table tp_col_array;
vr_coll_owner tp_col_array;
BEGIN
create database link "database1"
connect to my_name
identified by "my_password"
using 'database1';
SELECT owner, table_name
bulk collect into vr_coll_owner, vr_coll_table
FROM all_tables#database1
WHERE OWNER NOT IN ('SYS');
RETURN TO_CHAR(vr_coll_owner(1)); //just for temporary
END my_fn;
BEGIN
DBMS_OUTPUT.PUT_LINE(my_fn('link1','schema1'));
END;
EDIT
I also tried the following, but no luck :(
Execute immediate q'[create database link "database1"
connect to my_name
identified by "my_password"
using 'database1']';
If you create a database link dynamically in a PL/SQL block, every reference to that database link would also need to use dynamic SQL otherwise your block won't compile. Your SELECT statement would need to use EXECUTE IMMEDIATE as well. Stepping back, creating database links at runtime is generally a poor practice-- I'd seriously question why you're going down that path.
According to Justin Cave's comment
Make sure the definer-schema is granted the "create database link" privilege.
This one is working:
me#XE> execute execute immediate 'create database link superlink connect to a identified by b using ''TNSALIAS''';
PL/SQL procedure successfully completed.
me#XE> #mylinks
DB_LINK USERNAME PASSWORD HOST CREATED
--------------- --------------- --------------- ------------------------- --------------------
SUPERLINK A TNSALIAS 22.10.2014 22:42:19

truncate and insert within procedure don't work together

I need to do truncate table and then insert data in that table using procedure.
However, one does like dinamic sql but the other one doesn't:
create or replace
procedure RECREATE_AGGREGATE
AUTHID DEFINER
AS
BEGIN
TRUNCATE TABLE AGGREGATE;
INSERT INTO AGGREGATE SELECT * FROM OLD_AGGREGATE;
END;
Error(6,14): PLS-00103: Encountered the symbol "TABLE" when expecting one of the following: := . ( # % ; The symbol ":= was inserted before "TABLE" to continue.
If I add execute immediate around TRUNCATE statement, it works fine, but insert is erroring out.
If I remove it, TRUNCATE TABLE complains...
create or replace
procedure RECREATE_AGGREGATE
AUTHID DEFINER
AS
BEGIN
execute immediate 'TRUNCATE TABLE AGGREGATE';
INSERT INTO AGGREGATE SELECT * FROM OLD_AGGREGATE;
END;
Error(7,5): PL/SQL: SQL Statement ignored
Error(7,84): PL/SQL: ORA-00942: table or view does not exist
Can some shed some light here?
create or replace
procedure RECREATE_AGGREGATE
AUTHID DEFINER
AS
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE AGGREGATE';
INSERT INTO AGGREGATE SELECT * FROM OLD_AGGREGATE;
END;
will work assuming that you have appropriate privileges.
Based on your edit (and echoing #OracleUser's comment), you're likely getting an error running your INSERT statement because old_aggregate is owned by another user and you only have SELECT privileges on that table via a role. If you want to do something in a definer's rights stored procedure, you'll need to have those privileges granted directly to you (or be using 12c which lets you grant privileges to blocks of code rather than to users).
Assuming you want to use a definer's rights stored procedure, you'd need the owner of old_aggregate (or the DBA) to
GRANT SELECT
ON old_user.old_aggregate
TO new_user;
You can verify that you only have the privilege via a role by disabling roles for the session. I'll wager that if you do
SQL> set role none;
SQL> SELECT * FROM old_aggregate
that you'll get an ORA-00942 error as well. This is a good way of simulating what privileges will be available to the user inside a definer's rights stored procedure.
"TRUNCATE is DDL (data definition language). You cannot perform DDL from within PL/SQL. "
http://www.orafaq.com/forum/t/119427

Resources