Tải bản đầy đủ
PLVfile: Reading and Writing Operating System Files

PLVfile: Reading and Writing Operating System Files

Tải bản đầy đủ

[Appendix A] Appendix: PL/SQL Exercises
UTL_FILE returns a handle or pointer to that file. You will then use this handle in all future manipulations of
the file. A file handle has a special datatype of UTL_FILE.FILE_TYPE. FILE_TYPE is actually a PL/SQL
record whose fields contain all the information about the file needed by UTL_FILE. (Currently, the record
consists of a single column, named "id".)
You will reference the file handle, but not any of the individual fields of the handle. A handle is declared as
follows:
DECLARE
file_handle UTL_FILE.FILE_TYPE;
BEGIN

You could display the file handle which is generated by a call to UTL_FILE.FOPEN or the corresponding
PLVfile.fopen functions as follows:
DECLARE
file_handle UTL_FILE.FILE_TYPE;
BEGIN
file_handle := PLVfile.fopen ('login.sql', PLVfile.c_read);
p.l (file_handle.id);
END;
/

The p.l procedure is also overloaded in the PL/SQL 2.3 version so you can pass it the file handle directly
and it will display the id field, as shown here:
p.l (file_handle);

Many PLVfile programs give you the option of providing either the file name or the file handle. In some
cases, such as when you read from a file, you must use the file handle. In other situations, you can choose
your method of specifying the file you want.

13.1.3 File Location, Name, and Mode
When you open a file with the UTL_FILE.FOPEN function, you must provide three arguments, as shown in
the header below:
FUNCTION FOPEN
(location_in IN VARCHAR2, file_name_in IN VARCHAR2,
file_mode_in IN VARCHAR2)
RETURN UTL_FILE.FILE_TYPE;

The first argument is the location of the file (the directory); the second is the name of the file (name and
extension); and the third is the file mode: "R" for read−only, "W" for write−only, and "A" for append.
While UTL_FILE needs all of this information, you should not necessarily have to provide it all every time
you want to perform a file−related action. To make it easier for developers to work with files, PLVfile offers
several options for opening and referencing files. You can provide separate locations and names in the
UTL_FILE format. You can also provide a single string that which contains both the location and name and
let PLVfile parse that string into its separate components.
See Section 13.2, "Specifying the File in PLVfile" for more information on the approach taken by PL/Vision.

13.1.4 Handling UTL_FILE Errors
The UTL_FILE package provides a set of package−based exceptions and also makes use of two, more generic
exceptions to inform you of problems it encounters. These exceptions are shown in Table 13.1.
13.1.3 File Location, Name, and Mode

380

[Appendix A] Appendix: PL/SQL Exercises
It is great that the UTL_FILE package offers some predefined exceptions. By providing specific names for
different exception conditions, I can trap for and handle those conditions. The downside of this approach is
that I need to include explicit exception handlers by name, as shown below:
EXCEPTION
WHEN UTL_FILE.INVALID_PATH
THEN
p.l ('Invalid path');

If I try to use a WHEN OTHERS clause instead (as you can see, there are many UTL_FILE−specific
exceptions), the SQLCODE function simply and uniformly returns the number 1 −− indicating a
user−defined exception. I cannot, in other words, determine which of the UTL_FILE exceptions occurred.

Table 13.1: Exceptions Related to the UTL_FILE Package
Exception Name
NO_DATA_FOUND

Description
The GET_LINE procedure tried to read past the end of the file.
Remember that this same exception is also raised by implicit cursors and
references to PL/SQL tables.

UTL_FILE.INTERNAL_ERRORAn

internal error occurred. The requested operation was not completed.

UTL_FILE.INVALID_FILE_HANDLE
The specified

file handle does not identify a valid, open file. This
exception may be raised by calls to FCLOSE and FFLUSH.

UTL_FILE.INVALID_MODE

The mode supplied to FOPEN is not valid. Valid modes are: `a', `r', or
`w' (upper or lower case is acceptable).

UTL_FILE.INVALID_OPERATION
In FOPEN,

this exception is raised when the file cannot be opened as
requested. To open a file in read or append mode, the file must exist
already. To open in write mode, the file must be writeable/ createable.
In GET_LINE, FFLUSH, NEW_LINE, PUT, PUTF, and PUT_LINE,
this exception is raised when you try to perform an operation which is
incompatible with the mode under which the file was opened. For
example, you tried to write to a read−only file.

UTL_FILE.INVALID_PATH

The path name supplied in a call to FOPEN is not valid. This error occurs
when the location is not accessible or the path name is improperly
constructed.

UTL_FILE.READ_ERROR

An operating system−specific error occurred when you tried to read from
the file. For example, there might be a disk error.

UTL_FILE.WRITE_ERROR

An operating system−specific error occurred when you tried to write to
the file. For example, the disk might be full.

VALUE_ERROR

The text read by GET_LINE is too long to fit in the specified buffer.
To help you deal with this situation, PLVfile offers the exc_section procedure, which predefines all these
handlers (see Section 13.9, "Handling File Errors with PLVfile").

12.9 Cleaning Up Source
and Target

13.1.3 File Location, Name, and Mode

13.2 Specifying the File in
PLVfile

381

[Appendix A] Appendix: PL/SQL Exercises
Copyright (c) 2000 O'Reilly Associates. All rights reserved.

13.1.3 File Location, Name, and Mode

382

Chapter 13
PLVfile: Reading and
Writing Operating System
Files

13.2 Specifying the File in PLVfile
Now that you are aware of the way that UTL_FILE works, let's look at how PLVfile makes it easier to use the
builtin package.
First of all, rather than insist that you separate out the file location from the file name to open and manipulate
files, PLVfile provides a set of programs to make it easier to specify files. These programs are discussed
below.

13.2.1 Setting the Operating System Delimiter
Each operating system has a delimiter that it uses to separate out directories and subdirectories, as well as
separating directories from file names. Since PLVfile allows you to specify a file name as a single string
(directory and file name combined), it needs to know about the operating system delimiter.
Use the set_delim to set the operating system delimiter. Its header is:
PROCEDURE set_delim (delim_in IN VARCHAR2);

You can find out the current operating system delimiter by calling the delim function:
FUNCTION delim RETURN VARCHAR2;

The PLVfile package offers two predefined delimiters for UNIX and DOS as shown:

c_unixdelim CONSTANT VARCHAR2(1) := '/';
c_dosdelim CONSTANT VARCHAR2(1) := '\';

The default, initial setting for the OS delimiter is the UNIX delimiter: "/".

13.2.2 Setting the Default Directory
PLVfile maintains a current directory so that you do not have to continually specify a directory if you are
always working in the same area on disk. To set the current directory, call the set_dir procedure. To
determine the current setting for the directory, call the dir function. The headers for these programs are:
PROCEDURE set_dir (dir_in IN VARCHAR2);
FUNCTION dir RETURN VARCHAR2;

The following call to set_dir sets the default directory to a path in DOS:
SQL> exec PLVfile.set_dir ('c:\orawin\oe_app');

383

[Appendix A] Appendix: PL/SQL Exercises
NOTE: If you do not call PLVfile.set_dir before passing in file names for reading and
writing, there is a very good chance that your efforts to use PLVfile will be very frustrating.
You will get errors that are difficult to understand, since you know your file exists. One way
to minimize the frustration is to place a call to PLVfile.set_dir in your login.sql
script.
Notice that I do not include a terminating backslash in the string. That "final" delimiter is needed when
attaching the directory to the file name, but is neither needed nor legitimate for specifying a directory. In fact,
if you include a final delimiter, PLVfile will strip it from the string, as shown below:
PROCEDURE set_dir (dir_in IN VARCHAR2)
IS
BEGIN
v_dir := RTRIM (dir_in, v_delim);
END;

13.2.3 Parsing the File Name
PLVfile allows you to provide the file name as a single string. When you do this, PLVfile calls parse_name
to parse the string into its separate components. The header for parse_name is:
PROCEDURE parse_name
(file_in IN VARCHAR2, loc_out IN OUT VARCHAR2,
name_out IN OUT VARCHAR2);

where file_in is the full file specification (location, name, and extension). The loc_out argument
receives just the directory, while the name_out argument receives the name and extension. It relies on the
operating system delimiter you assigned with a call to set_dir in order to find the start of the file name.
If the string you pass to parse_name does not have a directory prefixed on the file name, PLVfile will
return the default directory as the location.
The following table shows how parse_name parses and returns values:
parse_name

Default Directory

/usr/app/names.lis NULL

File Location Returned File Name Returned
/usr/app

names.lis

/usr/app/names.lis /oracle/prod/defdir /usr/app

names.lis

names.lis

names.lis

NULL

NULL

names.lis
/oracle/prod/defdir /oracle/prod/defdir
names.lis
This procedure is used extensively inside PLVfile (see Section 13.4, "Opening and Closing Files" for an
example of how parse_name is used to overload several different versions of fopen). You can, however,
also call parse_name directly in your own application. Just make sure that you have set the OS delimiter
before you use parse_name.

13.1 A Review of
UTL_FILE

13.3 Creating and
Checking Existence of
Files

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

13.2.3 Parsing the File Name

384

Chapter 13
PLVfile: Reading and
Writing Operating System
Files

13.3 Creating and Checking Existence of Files
PLVfile provides a program (four overloaded versions, actually) to create a file. The headers for fcreate
are the following:
FUNCTION fcreate
(loc_in IN VARCHAR2, file_in IN VARCHAR2, line_in IN VARCHAR2)
RETURN UTL_FILE.FILE_TYPE;
FUNCTION fcreate (file_in IN VARCHAR2, line_in IN VARCHAR2 := NULL)
RETURN UTL_FILE.FILE_TYPE;
PROCEDURE fcreate
(loc_in IN VARCHAR2, file_in IN VARCHAR2, line_in IN VARCHAR2);
PROCEDURE fcreate (file_in IN VARCHAR2, line_in IN VARCHAR2 := NULL);

In versions of fcreate with three arguments, you provide the location, name, and single line to be deposited
in the file. Notice that all three values are required. In versions of fcreate with two arguments, you provide
the file specification (location and name combined, or just the name, in which case the default directory will
be applied).
Notice that the overloading is not only among different parameter lists, but even different program types. I
will explain this approach to overloading in PLVfile in this section; you will see it repeatedly throughout the
package.
The overloading of fcreate achieves two objectives:
1.
It allows the developer to either obtain the handle to the newly created file (the function versions) or
ignore that file handle entirely (the procedure versions). You'll want to retrieve the handle if you plan
to perform other actions on that file. If you only want to create the file and then move on to other
business, it will be easier and more intuitive to use the procedure versions.
2.
It allows the developer to provide the location and name separately (UTL_FILE style) or specify a
single, combined string. The three−argument version requires all entries. The two−argument version
allows you provide just a name; if you do not specify a line, it places the following default
substitution line in the file:
v_subst_line VARCHAR2(200) :=
'I make my disk light blink, therefore I am.';

When you call fcreate, it "initializes" a file to the line you provide (or the default line value) and then it
closes the file if you have called the procedure version of fcreate. On the other hand, if you have called the
fcreate function, PLVfile returns the handle to the file and then keeps the file open.
385

[Appendix A] Appendix: PL/SQL Exercises

13.3.1 Checking a File's Existence
Perhaps you only want to create a file if it already exists. PLVfile offers the fexists function to provide
you with this information. The headers for this overloaded function are:
FUNCTION fexists (loc_in IN VARCHAR2, file_in IN VARCHAR2)
RETURN BOOLEAN;
FUNCTION fexists (file_in IN VARCHAR2)
RETURN BOOLEAN;

You can provide separate locations and file names, or simply pass in the single string with combined
information. The function returns TRUE if the file can be opened for read−only access successfully. If the file
is already open, this function will return FALSE −− so use fexists with care.

13.2 Specifying the File in
PLVfile

13.4 Opening and Closing
Files

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

13.3.1 Checking a File's Existence

386

Chapter 13
PLVfile: Reading and
Writing Operating System
Files

13.4 Opening and Closing Files
PLVfile offers its own fopen and fclose programs to open and close operating system files (UTL_FILE
has programs of the same names). The fopen module is overloaded as shown below:
PROCEDURE fopen
(loc_in IN VARCHAR2, file_in IN VARCHAR2, mode_in IN VARCHAR2);
PROCEDURE fopen
(file_in IN VARCHAR2, mode_in IN VARCHAR2 := c_all);
FUNCTION fopen
(file_in IN VARCHAR2, mode_in IN VARCHAR2 := c_all)
RETURN UTL_FILE.FILE_TYPE;

Use the function version of fopen when you want to retrieve and use the file handle. Otherwise, you can
simply call either of the procedure versions to open the file and not worry about declaring a file handle.
The fclose and fclose_all procedures may be used to close one or all files. Their headers are:
PROCEDURE fclose (file_in IN UTL_FILE.FILE_TYPE);
PROCEDURE fclose_all;

Notice that there is no overloading of the fclose program. Until a reader or the author enhances PLVfile to
maintain a PL/SQL table of opened file names and their handles, there is no way to close a file by name. The
fclose_all procedure is a passthrough to UTL_FILE.FCLOSE_ALL, which shuts down any files that
have been opened with the UTL_FILE.FOPEN program.
You may find it useful to include a call to fclose or, more likely, fclose_all, in the exception sections
of programs that work with PLVfile. That way if your program fails during file manipulation, it will not leave
a file open. If the file is left open, that could cause another, different error, the next time you try to run it.

13.3 Creating and
Checking Existence of
Files

13.5 Reading From a File

Copyright (c) 2000 O'Reilly Associates. All rights reserved.

387

Chapter 13
PLVfile: Reading and
Writing Operating System
Files

13.5 Reading From a File
PLVfile offers several different ways to read information from an operating system file. The get_line
procedure gets the next line from the file. The line function returns the nth line from a file. The overloaded
infile functions returns the line in which a string is found. These programs are explored below.

13.5.1 Reading the Next Line
Use the get_line procedure to read the next line from a file. The header for get_line is:
PROCEDURE get_line
(file_in IN UTL_FILE.FILE_TYPE, line_out OUT VARCHAR2,
eof_out OUT BOOLEAN);

You must provide a file handle (file_in); you cannot get the next line from a file by name. This means that
you must already have opened the file using one of the fopen functions. The second argument of
get_line (line_out) receives the string which is found on the next line. The eof_out argument is a
flag which is set to TRUE if you have read past the end of the file.
When eof_out returns TRUE, line_out is set to NULL. You should not, however, test the value of
line_out to determine if you are at the end of the file. The line_out argument could be set to NULL if
the next line in a file is blank.
The following script (stored in the file dispfile.sql) uses get_line to read all the lines from a file and
then display those lines.
DECLARE
fileid UTL_FILE.FILETYPE;
line PLVfile.max_line%TYPE;
eof BOOLEAN;
BEGIN
fileid := PLVfile.fopen ('&1');
LOOP
PLVfile.get_line (fileid, line, eof);
EXIT WHEN eof;
p.l (line);
END LOOP;
PLVfile.fclose (fileid);
END;
/

I use the max_line variable of PLVfile to declare the line datatype. This gives me a way to avoid having to
hard−code the length of a line. Then I open the file (provided through a SQL*Plus substitution parameter) in
the simplest possible way: location and name combined, assuming read−only access. My simple loop reads
the next line and exits when the end−of−file condition is reached. If I did retrieve a line, I display it. When
done, I close the file.

388

[Appendix A] Appendix: PL/SQL Exercises

13.5.2 Reading the nth Line
Use the line function to retrieve the specified line from a file. The header for line is:
FUNCTION line (file_in IN VARCHAR2, line_num_in IN INTEGER)
RETURN VARCHAR2;

Notice that in this function you supply a file name and not a file handle (in fact, you don't even have the
option of providing the location and name separately). The second argument is the line number you want
retrieved.
The line function opens (in read−only mode), scans, and closes your file. You do not have to −− and
should not −− perform any of these steps. If the line number specified is 0 or is greater than the number of
lines in the file, the function will return a NULL value.
This function is handy when the lines in your file have a predefined or predictable structure. For example, you
might have an .ini or initialization file for a program in which the first line is the name of the program, the
second line the date and time of last use, and the third line the user who last accessed account information.
You could then use PLVfile.line to retrieve precisely the information you needed. The following call to
the line function extracts just the date and time of last use. It assumes that you have also made use of the
standard PL/Vision date mask when writing this information to the file.
v_lastuse := TO_DATE (PLVfile.line ('oe.ini', 2), PLV.datemask);

13.5.3 The INSTR of PLVFile
PLVfile provides a function which operates within a file in much the same way that the builtin INSTR
function operates on a string. INSTR returns the position in which the n th occurrence of a substring is found.
PLVfile.infile returns the line number in which the nth occurrence of a string occurs. The header of the
infile function, again overloaded to allow specification of the file in two different ways, is shown below:
FUNCTION infile
(loc_in IN VARCHAR2,
file_in IN VARCHAR2,
text_in IN VARCHAR2,
nth_in IN INTEGER := 1,
start_line_in IN INTEGER := 1,
end_line_in IN INTEGER := NULL,
ignore_case_in IN BOOLEAN := TRUE)
RETURN INTEGER;
FUNCTION infile
(file_in IN VARCHAR2,
text_in IN VARCHAR2,
nth_in IN INTEGER := 1,
start_line_in IN INTEGER := 1,
end_line_in IN INTEGER := NULL,
ignore_case_in IN BOOLEAN := TRUE)
RETURN INTEGER;

The arguments to the infile function are described below:
Parameter Name

Description

file_in The name of the file to be opened. The function is overloaded to allow both the location
loc_in, file_in
name and combined name specification for the file. All other arguments are common

among the two.

13.5.2 Reading the nth Line

389

[Appendix A] Appendix: PL/SQL Exercises
text_in

The chunk of text to be searched for in each line of the file.

nth_in

The number of times the text should be found in distinct lines in the file before the
function returns the line number. Default is 1, which means the first match. This value
must be at least 1.

start_line_in
The first

line in the file from which the function should start its search. This value must
be greater than 0.

end_line_in
The

last line in the file to which the function should continue its search. If NULL (the
default), then search through end of the file. This value must be greater than or equal to
start_line_in.

ignore_case_in
Indicates

whether the case of the file contents and text_in should be ignored when
checking for its presence in the line.
The infile function opens (in read−only mode), scans, and closes your file. You do not have to −− and
should not −− perform any of these steps.
The only required parameters are file_in and text_in. I can, as a result, call infile with this
minimum number of arguments:
first_find := PLVfile.infile ('names.vp', 'Hanubi';

I can, however, also do so much more, as shown in the examples below.
1.
Confirm that the role assigned to this user is SUPERVISOR.
IF PLVfile.line ('config.usr', 'ROLE=SUPERVISOR') > 0
THEN
update_schedule;
END IF;

2.
Find the second occurrence of `DELETE' starting with the fifth line.
v_line := PLVfile.line ('commands.dat', 'delete', 2, 5);

3.
Verify that the third line contains a terminal type specification. I ask for an exact match on the case of
the text in the file, since the setup file has a specific structure.
v_line := PLVfile.line
('setup.cfg', 'termtype=', 1, 3, ignore_case_in => FALSE);

The infile function differs from INSTR in at least one way: it does not support negative values for the
starting line number of the search. INSTR does recognize this kind of argument, causing it to scan backwards
through the string. You cannot scan backwards through the contents of a file.
13.5.3.1 Building utilities around infile
Suppose I receive profit−and−loss statements electronically from each of my regional offices every month.
The number of items in the statement can change, but the file must contain a monthly total line in the format:
month_total=NNNNNN

where NNNNNN is the dollar amount.

13.5.3 The INSTR of PLVFile

390