I run the write_test procedure, which works good.
begin
koll_data_pkg.write_test(p_customer_id=>247, p_addr=>'address', p_dir=>'\\SERVER01\Backup\Log\');
end;
But, when I change value of p_dir to another directory p_dir=>\SERVER12\Backup\Log\ it gives following error:
ORA-29283: invalid file operation
ORA-06512: by "SYS.UTL_FILE",
ORA-29283: invalid file operation
ORA-06512: by "DATA_PKG",
ORA-06512: by line
I have tried give permission using following commands, but still same error:
CREATE OR REPLACE DIRECTORY DEVO_INVREC_DIR AS '\\SERVER12\Backup\Log\';
GRANT READ, WRITE ON DIRECTORY DEVO_INVREC_DIR TO USER1;
GRANT READ, WRITE ON DIRECTORY DEVO_INVREC_DIR TO USER1;
GRANT EXECUTE ON UTL_FILE TO USER1;
Procedure:
procedure write_test(p_customer_id in koll_customer_party.customer_id%type,
p_addr in varchar,
p_dir in varchar,
p_filename in varchar2 default null)
is
lt_id id_tt;
lt_bolagsnamn bolagsnamn_tt;
l_file utl_file.file_type;
l_line varchar2(2048);
l_name varchar2(300):= 'DEVO_INVREC_DIR';
l_filename varchar2(100):= 'testfile.txt';
l_sql varchar2(512);
begin
select devo_id, bolagsnamn
bulk collect into lt_id, lt_bolagsnamn
from documents where customer_id=p_customer_id
if lt_id.count > 0 then
l_sql := 'create or replace directory ' || l_name || ' as ''' || p_dir || '''';
execute immediate l_sql;
if p_filename is not null then
l_filename := p_filename;
end if;
l_file := utl_file.fopen(l_name,l_filename,'w');
if utl_file.is_open(l_file) is not null then
for i in lt_id.first .. lt_devo_id.last loop
l_line:= lt_id(i) || ';' || replace(lt_bolagsnamn(i),';','');
utl_file.put_line(l_file, l_line);
end loop;
end if;
utl_file.fclose(l_file);
end if;
end;
Check out this forum response: https://community.oracle.com/thread/4145239?start=0&tstart=0
In summary, Oracle can't access the network shares in its default installed configuration because the Windows SYSTEM user can't access network shares by definition. You either have to reconfigure Oracle to run as a user other than SYSTEM, with permissions on the share, or allow SYSTEM to access network shares (a HUGE security risk). I was going to include a link describing how to change the user to be another service account, but they all seem to be broken or removed. It may depend on your exact version of Oracle and Windows, too, so you're best bet in the absence of other documentation would be to contact Oracle Support. There is no simple PL/SQL programming answer to your problem.
Related
I have a successfully compiled procedure under SYSTEM schema.
create or replace procedure get_file_list as
ns varchar2(1024);
cursor c_my_directory is
select directory_name, directory_path from all_directories where directory_path like '/home/oracle/EDI%';
begin
-- before generating the file list, the temporary table is deleted
delete from edi.temp_EDI_file_list;
for each_directory in c_my_directory loop
-- it reads the contents of my_directory into a table called X$FRBMSFT
sys.dbms_backup_restore.searchfiles (each_directory.directory_path, ns);
for each_file in (select fname_krbmsft as name from X$KRBMSFT) loop
insert into edi.temp_edi_file_list
values (each_directory.directory_name, each_file.name);
end loop;
end loop;
commit;
exception
when others then
raise_application_error (-20001,sqlcode || ' ' || sqlerrm);
end get_file_list;
[.. it was created under SYSTEM schema because I am not allowed to grant select on X$FRBMSFT to user "edi"].
I granted execute privilegies to user "edi" on this procedure.
[.. connected as SYSTEM, role SYSDBA, I executed grant execute on system.get_file_list to EDI;]
When I am trying to execute the procedure (execute system.get_file_list;) with user "edi" it return the error
PLS-00905: object SYSTEM.GET_FILE_LIST is invalid
Can someone, please, give me a hint about what am I doing wrong?
Thank you,
In the end I managed to create the procedure, with some help from the link provided by #APC.
... conected as SYSTEM
create or replace view file_list as select fname_krbmsft from X$KRBMSFT readonly;
create or replace procedure searchfiles (pattern in out nocopy varchar2, ns in out nocopy varchar2) authid definer as
begin
dbms_backup_restore.searchfiles(pattern, ns);
end searchfiles;
GRANT SELECT ON FILE_LIST TO EDI;
GRANT EXECUTE ON SEARCHFILES TO EDI;
... conected as EDI
create or replace procedure get_file_list as
ns varchar2(1024);
cursor c_my_directory is
select directory_name, directory_path from all_directories where directory_path like '/home/oracle/EDI%';
begin
-- before generating the file list, the temporary table is deleted
delete from edi.temp_EDI_file_list;
for each_directory in c_my_directory loop
-- it reads the contents of all directories into a table called X$FRBMSFT via procedure SEARCHFILES
sys.SEARCHFILES (each_directory.directory_path, ns);
-- it interogate the X$FRBMSFT via file_list view
for each_file in (select fname_krbmsft as name from sys.file_list) loop
insert into temp_edi_file_list
values (each_directory.directory_name, each_file.name);
end loop;
end loop;
commit;
exception
when others then
raise_application_error (-20001,sqlcode || ' ' || sqlerrm);
end get_file_list;
The difference was made by the way they were called the objects created with user SYSTEM. They were called with SYS.xxx instead of SYSTEM.xxx
Here is the definition of the stored procedure:
CREATE OR REPLACE PROCEDURE usp_dropTable(schema VARCHAR, tblToDrop VARCHAR) IS
BEGIN
DECLARE v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM all_tables
WHERE owner = schema
AND table_name = tblToDrop;
IF v_cnt > 0 THEN
EXECUTE IMMEDIATE('DROP TABLE someschema.some_table PURGE');
END IF;
END;
END;
Here is the call:
CALL usp_dropTable('SOMESCHEMA', 'SOME_TABLE');
For some reason, I keep getting insufficient privileges error for the EXECUTE IMMEDIATE command. I looked online and found out that the insufficient privileges error usually means the oracle user account does not have privileges for the command used in the query that is passes, which in this case is DROP. However, I have drop privileges. I am really confused and I can't seem to find a solution that works for me.
Thanks to you in advance.
SOLUTION:
As Steve mentioned below, Oracle security model is weird in that it needs to know explicitly somewhere in the procedure what kind of privileges to use. The way to let Oracle know that is to use AUTHID keyword in the CREATE OR REPLACE statement. If you want the same level of privileges as the creator of the procedure, you use AUTHID DEFINER. If you want Oracle to use the privileges of the user currently running the stored procedure, you want to use AUTHID CURRENT_USER. The procedure declaration looks as follows:
CREATE OR REPLACE PROCEDURE usp_dropTable(schema VARCHAR, tblToDrop VARCHAR)
AUTHID CURRENT_USER IS
BEGIN
DECLARE v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM all_tables
WHERE owner = schema
AND table_name = tblToDrop;
IF v_cnt > 0 THEN
EXECUTE IMMEDIATE('DROP TABLE someschema.some_table PURGE');
END IF;
END;
END;
Thank you everyone for responding. This was definitely very annoying problem to get to the solution.
Oracle's security model is such that when executing dynamic SQL using Execute Immediate (inside the context of a PL/SQL block or procedure), the user does not have privileges to objects or commands that are granted via role membership. Your user likely has "DBA" role or something similar. You must explicitly grant "drop table" permissions to this user. The same would apply if you were trying to select from tables in another schema (such as sys or system) - you would need to grant explicit SELECT privileges on that table to this user.
You should use this example with AUTHID CURRENT_USER :
CREATE OR REPLACE PROCEDURE Create_sequence_for_tab (VAR_TAB_NAME IN VARCHAR2)
AUTHID CURRENT_USER
IS
SEQ_NAME VARCHAR2 (100);
FINAL_QUERY VARCHAR2 (100);
COUNT_NUMBER NUMBER := 0;
cur_id NUMBER;
BEGIN
SEQ_NAME := 'SEQ_' || VAR_TAB_NAME;
SELECT COUNT (*)
INTO COUNT_NUMBER
FROM USER_SEQUENCES
WHERE SEQUENCE_NAME = SEQ_NAME;
DBMS_OUTPUT.PUT_LINE (SEQ_NAME || '>' || COUNT_NUMBER);
IF COUNT_NUMBER = 0
THEN
--DBMS_OUTPUT.PUT_LINE('DROP SEQUENCE ' || SEQ_NAME);
-- EXECUTE IMMEDIATE 'DROP SEQUENCE ' || SEQ_NAME;
-- ELSE
SELECT 'CREATE SEQUENCE COMPTABILITE.' || SEQ_NAME || ' START WITH ' || ROUND (DBMS_RANDOM.VALUE (100000000000, 999999999999), 0) || ' INCREMENT BY 1'
INTO FINAL_QUERY
FROM DUAL;
DBMS_OUTPUT.PUT_LINE (FINAL_QUERY);
cur_id := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.parse (cur_id, FINAL_QUERY, DBMS_SQL.v7);
DBMS_SQL.CLOSE_CURSOR (cur_id);
-- EXECUTE IMMEDIATE FINAL_QUERY;
END IF;
COMMIT;
END;
/
you could use "AUTHID CURRENT_USER" in body of your procedure definition for your requirements.
Alternatively you can grant the user DROP_ANY_TABLE privilege if need be and the procedure will run as is without the need for any alteration. Dangerous maybe but depends what you're doing :)
I want to grant the CREATE ANY DIRECTORY permission to a user, with the following restriction: all directories created by this user must be inside of /foo/bar, and any attempt to create a directory outside of this should fail with a permission error. How may I do this on Oracle 11G or 12C?
That depends, if you want to restrict which OS directories Oracle can access from utl_file commands, you can set the utl_file_dir parameter. Unfortunately, this parameter is system wide, so you won't be able to grant/revoke for a specific user using this parameter. Also keep in mind that if you make changes to this parameter, those changes won't go into effect until the Oracle database is restarted:
alter system set utl_file_dir = '/foo/bar' scope=spfile;
shutdown immediate;
startup open;
Consult the 12.1 Oracle Docs for more information regarding utl_file_dir.
That said, if you really want to restrict who can create Oracle Directories to specific OS directories, a procedure would be appropriate for that task since that would allow you to have finer grained control (and limit who has the very powerful create any directory privilege to the owner of the procedure):
sqlplus kjohnston
create or replace procedure mydircreate (p_dir varchar2)
as
ex_custom EXCEPTION;
PRAGMA EXCEPTION_INIT( ex_custom, -20001 );
begin
if lower(p_dir) not like '/foo/bar/%' then
raise_application_error( -20001, 'Not authorized' );
end if;
execute immediate 'create or replace directory mydir as ''' || p_dir || '''';
end mydircreate;
create user testuser identified by <password>;
grant create session to testuser;
grant execute on kjohnston.mydircreate to testuser;
exit;
sqlplus testuser
SQL> exec kjohnston.mydircreate('mydir', '/randomdir');
ORA-20001: Not authorized
SQL> exec kjohnston.mydircreate('mydir', '/foo/bar/baz');
PL/SQL procedure successfully completed.
You can include this restriction in trigger. List of system events and attributes Working with system events
CREATE OR REPLACE TRIGGER trg_before_ddl
BEFORE DDL ON DATABASE
declare
v_sql ORA_NAME_LIST_T;
v_ddl varchar2(4000);
v_cnt BINARY_INTEGER;
is_valid number;
begin
if ora_sysevent in ('CREATE') and ora_dict_obj_type = 'DIRECTORY' then
v_cnt := ora_sql_txt (v_sql);
FOR i IN 1..v_cnt LOOP
v_ddl := v_ddl || RTRIM (v_sql (i), CHR (0));
END LOOP;
v_ddl := regexp_substr(v_ddl,'AS ''(.*)''', 1, 1, 'i', 1 ); -- get path from ddl_statement
-- check valid directory here, path is in v_ddl ;
is_valid := REGEXP_instr(v_ddl,'^/valid_dir/.*$');
if (is_valid = 0) then
raise_application_error(-20000,'Directory is not valid' || v_ddl);
end if;
end if;
END;
/
CREATE DIRECTORY valid_dir AS '/valid_dir/xyz';
CREATE DIRECTORY invalid_dir AS '/invalid_dir/xyz';
I am importing text file through SQL*Loader into an Oracle table but i don't want to give the specific name of the file, I want to import only the .txt file extensions file.
look the below code :
create or replace
PROCEDURE EXT_TABLE
AS
A1 NUMBER ;
L_QUERY VARCHAR2(1000) := NULL;
L_DROP VARCHAR2(10000) := NULL;
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE IMPORT_TEST
( EMP_ID NUMBER (10)
)
ORGANIZATION EXTERNAL
( TYPE ORACLE_LOADER
DEFAULT DIRECTORY IMPORT
ACCESS PARAMETERS
( RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY '',''
MISSING FIELD VALUES ARE NULL
)
LOCATION ('file with .txt extension')
)reject limit unlimited';
L_QUERY:= 'INSERT INTO MPRN SELECT * FROM IMPORT_TEST';
EXECUTE IMMEDIATE L_QUERY;
L_DROP := 'drop table IMPORT_TEST ';
execute immediate L_DROP;
--execute immediate 'DROP IMPORT_TEST';
commit;
END EXT_TABLE;
At the location, LOCATION ('file with .txt extension'), I don't want to give the name of the file as in the directory only one txt file is there. I don't want to use the IN parameter. I want to search from the directory only. The user will run the procedure and it will import the txt file automatically without selecting manually.
For the most part you aren't going to be able to do this in a pure PL/SQL fashion. There is a workaround listed here: Listing files in a specified directory using PL/SQL but considering the requirement for SYS that may not be exactly what you are looking for. After that a Java Stored Procedure would be your best bet.
If you are able to determine the filename, you can redefine the location for your external table on the fly with an execute immediate call. You could put it in a procedure like this and make use of it before querying your external table:
procedure alterExtTableFileName(a_tableName varchar2, a_filename varchar2) is
pragma autonomous_transaction;
begin
dbms_output.put_line('alterExtTableFileName(TableName=' || a_tableName || ' FileName=' || a_filename || ')');
execute immediate 'alter table ' || a_tableName || ' LOCATION (''' || a_filename || ''')';
commit;
exception when others then
rollback;
raise;
end alterExtTableFileName;
I was wondering how to go about exporting a query from PL/SQL to an text file or csv file. The query I have in mind exports a huge amount of data (about 1 gig). So I'd also like the data split across multiple files;
out1.csv
out2.csv
out3.csv
I'd like to be able to decide how many files to split it across.
Anyone have any idea how to do this?
Use UTL_FILE.
A well known ( probably the most complete discussion on this topic ) discussion on this can be found at Ask Tom, Here , note that many of the examples there date back to oracle 8, so there may be better ways to do it in your version of Oracle.
Try this
For creating MYDIR
create or replace directory MYDIR as 'F:/DATA/';
Grant all permission to MYDIR via SYS user
execute this procedure
CREATE OR REPLACE PROCEDURE export_to_csv(refcur out sys_refcursor) IS
v_file UTL_FILE.file_type;
v_string VARCHAR2(4000);
CURSOR c_emp IS
SELECT ROLE_ID, ROLE_DESC FROM role_mst;
BEGIN
open refcur for
SELECT ROLE_ID, ROLE_DESC FROM role_mst;
v_file := UTL_FILE.fopen('MYDIR', 'empdata.csv', 'w', 1000);
-- if you do not want heading then remove below two lines
v_string := 'Emp Code, Emp Name';
UTL_FILE.put_line(v_file, v_string);
FOR cur IN c_emp LOOP
v_string := cur.ROLE_ID || ',' || cur.ROLE_DESC;
UTL_FILE.put_line(v_file, v_string);
END LOOP;
UTL_FILE.fclose(v_file);
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm);
IF UTL_FILE.is_open(v_file) THEN
UTL_FILE.fclose(v_file);
END IF;
END;