Search code examples
oracle-databaseplsqlprocedure

How to solved problem with Oracle DBMS_LOB


I am trying to save the information of an XML file in a database table and I am using this procedure:

create or replace PROCEDURE P_FILEUPLOAD_XML (P_CMTT_CODE IN NUMBER DEFAULT 15, P_TEXT IN VARCHAR2, P_TEXT_NAR IN VARCHAR2, P_PATH IN VARCHAR2, P_FILENAME IN VARCHAR2, P_RET_VAL OUT NUMBER) IS

GRUPO       VARCHAR2(20);
l_dir       CONSTANT VARCHAR2(35) := P_PATH;
l_fil       CONSTANT VARCHAR2(30) := P_FILENAME; 
l_loc       BFILE; -- Pointer to the BFILE
l_ret       BOOLEAN := FALSE; -- Return value
l_pos       NUMBER := 1; -- Current position in the file (file begins at position 1)
l_sum       number default 8000; -- Amount of characters have been read
l_buf       VARCHAR2(32767); -- Read Buffer
l_sen       CONSTANT RAW(100) := UTL_RAW.CAST_TO_RAW(CHR(10)); -- Character at the end of the file is NEWLINE (ascii = 10)
l_end       NUMBER; -- End of the current word which will be read
l_counter NUMBER := 0; -- Counter for line sequence
l_line      VARCHAR2(32767); -- Contains the info line by line for insert

BEGIN

SELECT TEXTO INTO GRUPO FROM gzvcatg
         UNPIVOT ((codigo, texto) FOR gzvcatg_external_code IN (
            (gzvcatg_external_code1, gzvcatg_desc1) AS '1')
         WHERE GZVCATG_GROUP = 'TIT_ELEC'
            AND CODIGO = 'PATH';

     -- Mapping the physical file with the pointer to the BFILE
        l_loc := BFILENAME(GRUPO,'ES0000251446027471.xml');

    -- Open the file in READ_ONLY mode
       DBMS_LOB.OPEN(l_loc,DBMS_LOB.LOB_READONLY);
       LOOP
          l_counter := l_counter + 1; -- Counter for sequence
          -- Calculate the end of the current word
          l_end := DBMS_LOB.INSTR(l_loc,l_sen,l_pos,1);

          -- Process end-of-file
          IF (l_end = 0) THEN
            l_end := DBMS_LOB.INSTR(l_loc,l_sen,l_pos,1);
            l_sum := l_end - l_pos - 1;
            EXIT;
          END IF;

          -- Read until end-of-file
          l_sum := l_end - l_pos;
          DBMS_LOB.READ(l_loc,l_sum,l_pos,l_buf);
          l_line := UTL_RAW.CAST_TO_VARCHAR2(l_buf);

        BEGIN 
            INSERT INTO SPRCMNT (
                         SPRCMNT_CMTT_CODE,
                         SPRCMNT_TEXT,
                         SPRCMNT_TEXT_NAR)
                                VALUES(P_CMTT_CODE,
                                       P_TEXT,
                                       P_TEXT_NAR);    
             EXCEPTION WHEN OTHERS THEN 
             ROLLBACK;           
        END;
    END LOOP;    
EXCEPTION
    WHEN OTHERS THEN
        dbms_output.put_line('Error:' || SQLERRM);
        DBMS_LOB.CLOSE(l_loc);
        P_RET_VAL := 3;
        dbms_output.put_line('P_RET_VAL:' || P_RET_VAL);    
END;

But when I execute the procedure I get the following error:

Error:ORA-22285: non-existent directory or file FILEOPEN operation

I do not understand why the path C:\XMLS\ does exist if my XML file exists .

I get the route with the result of the Query.

enter image description here

This result of the Query was assigned to a variable called GRUPO, which is what I declare at the beginning of the procedure, and then I place this variable as a parameter in the function BFILENAME, which originally contains the directory y filename. You can see documentation BFILENAME here

l_loc := BFILENAME(GRUPO,'ES0000251446027471.xml');

In addition to this I have created a directory in Oracle as follows:

CREATE OR REPLACE DIRECTORY DIR_XML as 'C:\XMLS\';

I also gave permissions to the directory

GRANT ALL ON DIRECTORY DIR_XML TO PUBLIC;

I've been wasting a lot of time with this problem and I can't find any solution. Any help will be appreciated.


Solution

  • From the documentation you linked to:

    • 'directory' is a database object that serves as an alias for a full path name on the server file system where the files are actually located.

    so GRUPO should evaluate to 'DIR_XML' rather than 'C:\XMLS'. That is shown in the example in that documentation. (There was an old pre-directory-object mechanism based on a path stored as a database parameter, but that was less secure...)

    If all you have is the path you can look up the directory name:

    select directory_name from all_directories where directory_path = 'C:\XMLS'
    

    bearing in mind that directory paths don't have to be unique, so you may have to deal with duplicates.

    But as @Matthew already explained, and as the documentation says (emphasis added):

    A directory object specifies an alias for a directory on the server file system where ...

    The database can only see files on its own filesystem - local or shared - and not those on client filesystems. If you are running the DB locally as well then there is no difference (though directory and file permissions still matter). If you are accessing a remote DB then it cannot see your client C: drive, and if you give the directory object name you'll still get something like:

    ORA-22288: file or LOB operation FILEOPEN failed
    No such file or directory
    

    You have to put your XML files in a directory on the DB server that the operating system account has access to, and create a directory object that points to that location on the server; and then refer to the directory object name, not the underlying filesystem path.