Работа с файлами в PL/SQL: чтение, запись, открытие и закрытие

Работа с файлами в PL/SQL на примерахПакет UTL_FILE позволяет программам PL/SQL как читать, так и записывать данные в любой файл операционной системы, доступный для сервера, на котором работает экземпляр базы данных. Данные из файла можно загрузить непосредственно в таблицу базы данных, в полной мере используя всю мощь и гибкость программирования PL/ SQL.

Также разработчик может генерировать отчеты прямо из PL/SQL, не беспокоясь об ограничениях на максимальный размер буфера DBMS_OUTPUT, существовавших до выхода Oracle Database 10g Release 2.

Идея записи произвольных файлов выглядит довольно рискованно — злонамеренный или беспечный программист теоретически может воспользоваться UTL_FILE для записи файлов баз данных табличного пространства, управляющих файлов и т. д. Oracle позволяет администратору установить ограничения на чтение и запись файлов одним из двух способов:



  •  Чтение и запись файлов средствами UTL_FILE производится в каталогах, заданных параметром UTL_FILE_DIR в инициализационном файле базы данных.
  •  UTL_FILE также читает и записывает файлы в каталогах, определяемых объектами каталогов базы данных.

Сначала вы узнаете, как использовать эти два механизма, а затем мы рассмотрим специфические возможности пакета UTL_FILE. Многие программы UTL_FILE инкапсулируются в удобном пакете, доступном в файле fileIO.pkg на сайте  github.

 

Параметр UTL_FILE_DIR

Хотя использование UTL_FILE_DIR официально не признано устаревшим, этот подход редко используется в новейших версиях базы данных Oracle. Объекты каталогов гораздо удобнее и обладают большей гибкостью. Если у вас есть выбор, не используйте UTL_FILE_DIR; просто пропустите этот раздел и переходите к разделу «Работа с каталогами в Oracle».

Вызывая FOPEN для открытия файла, необходимо указать как местонахождение, так и имя файла. Местонахождение файла проверяется по списку доступных каталогов, которые задаются в файле инициализации базы данных строками вида:

UTL_FILE_DIR = каталог

Включите параметр UTL_FILE_DIR для каждого каталога, который вы хотите сделать доступным для операций UTL_FILE. Например, следующие записи разрешают доступ к четырем разным каталогам в файловых системах семейства Unix/Linux:

UTL_FILE_DIR = /tmp
UTL_FILE_DIR = /ora_apps/hr/time_reporting 
UTL_FILE_DIR = /ora_apps/hr/time_reporting/log 
UTL_FILE_DIR = /users/test_area

Чтобы обойти средства безопасности сервера и разрешить чтение/запись во все каталоги, можно воспользоваться специальным синтаксисом:

UTL_FILE_DIR = *

Никогда не используйте этот механизм в среде реальной эксплуатации. Конечно, в среде разработки этот синтаксис упрощает использование UTL_FILE и тестирование кода. Однако при переводе приложения в рабочую среду доступ следует ограничить несколькими конкретными каталогами.

 

Настройка каталогов

Несколько замечаний по поводу настройки доступных каталогов для UTL_FILE и работы с ними:

  •  Доступ не распространяется на подкаталоги. Допустим, файл инициализации содержит следующие строки:
UTL_FILE_DIR = c:\group\dev1 
UTL_FILE_DIR = c:\group\prod\oe 
UTL_FILE_DIR = c:\group\prod\ar

Вы не сможете открыть файл в подкаталоге c :\group\prod\oe\reports.

  •  Не используйте следующую запись в системах Unix и Linux:
UTL_FILE_DIR = .

Она выполняет чтение/запись в текущем каталоге операционной системы.

  •  Не заключайте имена каталогов в одиночные или двойные кавычки.
  •  В среде Unix/Linux владельцем файла, созданного FOPEN, является теневой процесс, запустивший экземпляр Oracle (как правило, «oracle»). При попытке обратиться к этим файлам или изменить их за пределами UTL_FILE необходимо обладать соответствующими привилегиями (или войти в систему как «oracle»).
  •  Не завершайте имя каталога ограничителем (например, косой чертой в Unix/Linux). Следующая спецификация создаст проблемы при попытке чтения или записи в каталог:
UTL_FILE_DIR = /tmp/orafiles/

 

Определение местонахождения файла при открытии

Местонахождение файла определяется строкой, специфической для операционной системы, которая задает каталог или область для открытия файла. При передаче местонахождения в вызове UTL_FILE. FOPEN спецификация местонахождения указывается в том виде, в каком она задана в файле инициализации базы данных. И помните, что в операционных системах, учитывающих регистр символов, регистр спецификации в файле инициализации должен совпадать с регистром, использованным в вызове UTL_FILE.F0PEN. Несколько примеров:

  •  Windows:
file_id := UTL_FILE.FOPEN ('k:\common\debug', 'trace.lis', 'R');
  •  Unix/Linux:
file_id := UTL_FILE.FOPEN ('/usr/od2000/admin', 'trace.lis', 'W');

Местонахождение должно задаваться явно, как полный путь к файлу. При этом не допускается использование параметров, относящихся к специфике операционной системы (например, переменных окружения в Unix/Linux).

 

Работа с каталогами в Oracle

До выхода версии Oracle9i Release 2 при открытии файла приходилось указывать его местонахождение. Однако жесткое кодирование данных всегда нежелательно — а если данные будут перемещены в другое место? Сколько программ придется изменять, чтобы они искали свои данные в правильном месте? И сколько раз придется вносить такие изменения?

Лучше объявить переменную или константу, которой будет присваиваться значение, определяющее местонахождение данных. Если сделать это в пакете, к константе можно будет обращаться из любой программы в схеме, обладающей привилегией EXECUTE для этого пакета. Пример:

PACKAGE accts_pkg 
IS
   c_data_location
      CONSTANT VARCHAR2(30) := '/accts/data';
   ...
END accts_pkg;

DECLARE
   file_id UTL_FILE.file_type;
BEGIN
   file_id := UTL_FILE.fopen (accts_pkg.c_data_location, 'trans.dat', 'R');
END;

Неплохо, но еще лучше — использовать объект уровня схемы, который может определяться в базе данных. Для создания каталога администратор базы данных должен предоставить вам привилегию CREATE ANY DIRECTORY, после чего вы получаете возможность создания новых каталогов инструкциями следующего вида:

CREATE OR REPLACE DIRECTORY development_dir AS '/dev/source';
CREATE OR REPLACE DIRECTORY test_dir AS '/test/source';

Несколько важных обстоятельств, относящихся к созданию каталогов в UTL_FILE:

  •  Oracle не проверяет заданное вами имя каталога. Строка просто связывается с именованным объектом базы данных.
  •  Имя каталога, указанное, допустим, при вызове UTL_FILE.FOPEN, интерпретируется не как имя объекта Oracle, а как строка с учетом регистра символов. Иначе говоря, если имя не задается как строка с символами в верхнем регистре, операция завершится неудачей. Например, такое решение работает:
handle := UTL_FILE.FOPEN(
             location => 'TEST_DIR', filename => 'myfile.txt', open_mode =>
'r');

... а такое — нет:

handle := UTL_FILE.FOPEN(
         location => test_dir, filename => 'myfile.txt', open_mode => 'r');
  •  После того как каталог будет создан, вы можете предоставить конкретным пользователям разрешения на работу с ним:
GRANT READ ON DIRECTORY development_dir TO senior_developer;
  •  Наконец, информацию о каталогах, доступных для текущей схемы, можно получить из представления ALL_DIRECT0RIES. Данные представления также могут использоваться для создания полезных вспомогательных программ. В следующем примере выводится список всех каталогов, определенных в базе данных:
/* Файл в Сети: fileIO.pkg */
PROCEDURE fileIO.gen_utl_file_dir_entries 
IS
BEGIN
   FOR rec IN (SELECT * FROM all_directories)
   LOOP
      DBMS_OUTPUT.PUT_LINE ('UTL_FILE_DIR = ' || rec.directory_path);
   END LOOP;
END gen_utl_file_dir_entries;

Одно из преимуществ подобных вспомогательных программ заключается в том, что они позволяют легко и нетривиально обрабатывать «ошибки форматирования» (например, если имя каталога не задается в верхнем регистре).

 

Открытие файлов

Прежде чем читать или записывать данные, файл необходимо предварительно открыть. Функция UTL_FILE.F0PEN открывает заданный файл и возвращает дескриптор для выполнения дальнейших операций с файлом. Заголовок функции выглядит так:

FUNCTION UTL_FILE.FOPEN (
     Location IN VARCHAR2 
   , filename IN VARCHAR2 
   , open_mode IN VARCHAR2
   , max_Linesize IN BINARY_INTEGER DEFAULT NULL)
RETURN UTL_FILE.file_type;

Здесь location — путь к файлу (каталог из UTL_FILE_DIR или объект каталога в базе данных); filename — имя файла; openmode — режим открытия (см. ниже); max_linesize — максимальное количество символов в строке с учетом символа новой строки (от 1 до 32 767); UTL_FILE.file_type — запись со всей информацией, необходимой UTL_FILE для работы с файлом.

Файл может открываться в одном из трех режимов:

  •  R— файл открывается только для чтения. В этом режиме для чтения данных из файла используется процедура GET_LINE из пакета UTL_FILE.
  •  W — файл открывается для чтения и записи в режиме замены. Все существующие строки удаляются из файла. В этом режиме для модификации файла могут использоваться любые из следующих программ UTL_FILE: PUT, PUT_LINE, NEW_LINE, PUTF и FFLUSH.
  •  A — файл открывается для чтения и записи в режиме присоединения. Все существующие строки остаются в файле, а новые строки присоединяются за последней строкой. Для модификации файла могут использоваться любые из следующих программ utl_file: put, put_line, new_line, PUTF и FFLUSH.

При открытии файлов необходимо учитывать следующие обстоятельства:

  •  Путь, объединенный с именем файла, должен представлять действительное имя файла в вашей операционной системе.
  •  Путь к файлу должен быть доступным и уже существующим; функция FOPEN не создает каталог или подкаталог для записи нового файла.
  •  Файл, открываемый для чтения, должен уже существовать. Если файл открывается для записи, он либо создается (если файл не существует), либо из него удаляется все содержимое (если файл существует).
  •  Файл, открываемый для присоединения, уже должен существовать. В противном случае UTL_FILE инициирует исключение INVALID_OPERATION.

Следующий пример демонстрирует объявление файлового дескриптора с последующим открытием файла только для чтения:

DECLARE
    config_file UTL_FILE.FILE_TYPE;
BEGIN
    config_file := UTL_FILE.FOPEN ('/maint/admin', 'config.txt', 'R');

Обратите внимание: при открытии файла максимальная длина строки не задается. Этот параметр не является обязательным. Если он не указан, то максимальная длина строки, которая читается или записывается в файл, равна приблизительно 1024. С учетом этого ограничения можно передать аргумент max_linesize, как это сделано в следующем примере:

DECLARE
    config_file UTL_FILE.FILE_TYPE;
BEGIN
    config_file := UTL_FILE.FOPEN (
    '/maint/admin', 'config.txt', 'R', max_linesize => 32767);

Файлы в многобайтовых кодировках следует открывать функцией FOPEN_ NCHAR. В этом случае Oracle рекомендует ограничить max_linesize значением 6400.

 

Проверка открытия файла

Функция IS_0PEN возвращает TRUE, если заданный дескриптор указывает на уже открытый файл. В противном случае возвращается FALSE. Заголовок функции выглядит так:

FUNCTION UTL_FILE.IS_OPEN (file IN UTL_FILE.FILE_TYPE) RETURN BOOLEAN;

Здесь file — проверяемый файл.

Важно понимать, что означает такая проверка в контексте UTL_FILE. Функция IS_0PEN не выполняет никаких проверок на уровне операционной системы, она всего лишь убеждается в том, что поле id записи файлового дескриптора отлично от NULL. Если вы не модифицировали эти записи и их содержимое, поле id имеет отличное от NULL значение только при вызове F0PEN. При вызове FCL0SE в него снова записывается значение NULL.

 

Закрытие файла

Для закрытия файла или всех открытых файлов в сеансе следует использовать процедуры UTL_FILE.FCL0SE и UTL_FILE.FCL0SE_ALL соответственно. Заголовок процедуры выглядит так:

PROCEDURE UTL_FILE.FCLOSE (file IN OUT UTL_FILE.FILE_TYPE);

Здесь file — имя закрываемого файла. Обратите внимание на то, что аргумент UTL_FILE. FCL0SE передается в режиме IN 0UT, потому что процедура после закрытия файла записывает в поле id записи значение NULL.

Если при закрытии файла в буфере остались незаписанные данные, UTL_FILE инициирует исключение WRITE_ERR0R.

Процедура FCL0SE_ALL закрывает все открытые файлы. Ее заголовок выглядит так:

PROCEDURE UTL_FILE.FCLOSE_ALL;

Эта процедура удобна тогда, когда программа открывает множество разных файлов, и вы хотите удостовериться в том, что ни один файл не останется открытым при ее завершении.

В программах, открывающих файлы, процедура FCL0SE_ALL может вызываться в обработчиках исключений; этот вызов гарантирует, что файлы будут закрыты и в случае аварийного завершения программы.

EXCEPTI0N
   WHEN 0THERS 
   THEN
      UTL_FILE.FCLOSE_ALL;
      ... другие завершающие действия ...
END;

При закрытии файлов процедурой FCL0SE_ALL их файловые дескрипторы не помечаются как закрытые (иначе говоря, их поле id остается отличным от NULL). В результате любые вызовы IS_0PEN для таких дескрипторов будут возвращать TRUE, хотя вы и не сможете выполнять с этими файлами операции чтения/записи (без повторного открытия).

  

Чтение из файла

Процедура UTL_FILE.GET_LINE читает из открытого файла строку данных в заданный буфер. Заголовок процедуры выглядит так:

PROCEDURE UTL_FILE.GET_LINE 
   (file IN UTL_FILE.FILE_TYPE, 
    buffer OUT VARCHAR2);

Здесь file — дескриптор, полученный при вызове FOPEN, а buffer — буфер, в который читаются данные. Переменная, заданная для параметра buffer, должна быть достаточно большой для хранения всех данных вплоть до следующего символа новой строки или маркера конца файла. В противном случае PL/SQL инициирует исключение VALUE_ERROR. Символ-завершитель строки не включается в строку, записываемую в буфер.

Oracle предоставляет дополнительные версии GET для чтения данных NVARCHAR2 (GET_LINE_NCHAR) и данных RAW (GET_RAW).

Пример использования GET_LINE:

DECLARE
   l_file UTL_FILE.FILE_TYPE; 
   l_line VARCHAR2(32767);
BEGIN
   l_file := UTL_FILE.FOPEN ('TEMP_DIR', 'numlist.txt', 'R', max_linesize =>
32767);
   UTL_FILE.GET_LINE (l_file, l_line);
   DBMS_OUTPUT.PUT_LINE (l_line);
END;

GET_LINE читает данные только в строковую переменную, поэтому если в файле хранятся числа или даты, вам придется самостоятельно выполнить преобразование к локальной переменной соответствующего типа данных.

 

Исключения GET_LINE

Если GET_LINE пытается прочитать данные после конца файла, инициируется исключение NO_DATA_FOUND. Это же исключение инициируется в следующих ситуациях:

  •  при выполнении неявного (SELECT INTO) курсора, не возвращающего строк;
  •  при ссылке на неопределенную строку в коллекции PL/SQL;
  •  при чтении после конца двоичного файла (BFILE) с использованием DBMS_LOB.

Если вы выполняете несколько таких операций в одном блоке PL/SQL, вероятно, вам понадобится дополнительная логика для определения источника ошибки. Пример такого решения приведен в файле who_did_that.sql на сайте github.

 

Удобная инкапсуляция для GET_LINE

Процедура GET_LINE проста и прямолинейна: она получает следующую строку из файла. Если файловый указатель уже находится на последней строке файла, UTL_FILE.GET_LINE не вернет никакие флаги, а инициирует исключение NO_DATA_FOUND. Это ухудшает структуру кода; возможно, вам стоит рассмотреть возможность инкапсуляции GET_LINE для решения этой проблемы, как объясняется в этом разделе.

Следующая программа читает каждую строку из файла и обрабатывает ее:

DECLARE
   l_file UTL_FILE.file_type; 
   l_line VARCHAR2 (32767);
BEGIN
   l_file := UTL_FILE.FOPEN ('TEMP', 'names.txt', 'R');
   LOOP
      UTL_FILE.get_line (l_file, l_line); 
      process_line (l_line);
   END LOOP;
EXCEPTION
   WHEN NO_DATA_FOUND 
   THEN
      UTL_FILE.fclose (l_file);
END;

Обратите внимание: этот простой цикл не содержит явных команд EXIT; он завершается неявно и с инициированием исключения сразу же после того, как UTL_FILE прочитает данные за концом файла. В подобных небольших блоках логика выглядит понятно, но представьте, что программа насчитывает сотни строк гораздо более сложного кода. Также допустим, что чтение содержимого файла — всего лишь один шаг в общем алгоритме. Если исключение завершает блок, остаток бизнес-логики необходимо разместить в разделе исключений (нежелательно) или же упаковать логику чтения файла в анонимном блоке BEGIN-END.

Меня такое решение не устраивает. Я не хочу программировать бесконечные циклы без команды EXIT; условие завершения не структурировано в самом цикле. Более того, ситуация достижения конца файла не является исключением; в конце концов, каждый файл когда-то да кончится. Почему я должен передавать управление в раздел исключений только из-за того, что я хочу полностью прочитать файл?

Полагаю, более удачный способ обработки конца файла заключается в построении программной «обертки» для GET_LINE, которая немедленно проверяет конец файла и возвращает логический признак (TRUE или FALSE). Этот подход продемонстрирован в следующей процедуре get_nextline:

/* Файл в Сети: getnext.sp */
PROCEDURE get_nextline (
   file_in IN UTL_FILE.FILE_TYPE 
 , line_out OUT VARCHAR2 
 , eof_out OUT BOOLEAN)
IS
BEGIN
   UTL_FILE.GET_LINE (file_in, line_out); 
   eof_out := FALSE;
EXCEPTION
   WHEN NO_DATA_FOUND 
   THEN
      line_out := NULL; 
      eof_out := TRUE;
END;

Процедура get_nextline получает уже назначенный файловый дескриптор и возвращает два значения: строку текста (если она имеется) и логический признак (TRUE, если достигнут конец файла; FALSE в противном случае). Используя get_nextline, я могу прочитать содержимое файла в цикле с командой EXIT:

DECLARE
   l_file UTL_FILE.file_type; 
   l_line VARCHAR2 (32767); 
   l_eof BOOLEAN;
BEGIN
   l_file := UTL_FILE.FOPEN ('TEMP', 'names.txt', 'R');

   LOOP
      get_nextline (l_file, l_line, l_eof);
      EXIT WHEN l_eof; 
      process_line (l_line);
   END LOOP;
   UTL_FILE.fclose (l_file);
END;

С процедурой get_nextline достижение конца файла уже не рассматривается как исключительная ситуация. Я читаю строки из файла, пока он не закончится, после чего закрываю файл и выхожу из цикла. На мой взгляд, такая программа получается более понятной и прямолинейной.

 

Запись в файл

В отличие от относительно простой процедуры чтения, пакет UTL_FILE содержит несколько разных процедур, предназначенных для записи в файл:

  •  UTL_FILE.PUT — вывод данных в текущую строку открытого файла без присоединения завершителя. Либо используйте процедуру NEW_LINE для завершения текущей строки, либо выводите строку вместе с завершителем при помощи процедуры PUT_LINE.
  •  UTL_FILE.NEW_LINE — вставка одного (по умолчанию) или нескольких символов новой строки в текущей позиции.
  •  UTL_FILE.PUT_LINE — вывод в файл строки, за которой следует символ-завершитель, зависящий от платформы. Именно эта процедура чаще всего используется при работе с пакетом UTL_FILE.
  •  UTL_FILE.PUTF — вывод в файл до пяти строк в формате, определяемом форматной строкой (по аналогии с функцией printf языка C).
  •  UTL_FILE.FFLUSH — операции записи UTL_FILE обычно буферизуются; FFLUSH немедленно записывает содержимое буфера в файловую систему.

Все эти процедуры могут использоваться только в том случае, если файл был открыт в режиме W или A. Если файл был открыт только для чтения, исполняющее ядро инициирует исключение UTL_FILE.INVALID_OPERATION.

Oracle предоставляет дополнительные версии PUT для записи данных NVARCHAR2 (PUT_LINE_NCHAR, PUT_NCHAR, PUTF_NCHAR) и данных RAW (PUT_ RAW).

Рассмотрим пример использования UTL_FILE.PUT_LINE. Эта процедура записывает данные в файл, присоединяя за текстом символ новой строки. Заголовок PUT_LINE выглядит так:

PROCEDURE UTL_FILE.PUT_LINE ( 
   file IN UTL_FILE.FILE_TYPE 
  ,buffer IN VARCHAR2 
  ,autofLush IN BOOLEAN DEFAULT FALSE)

Здесь file — дескриптор, полученный при вызове FOPEN, а buffer — текст, записываемый в файл (длина не более 32 767); autoflushTRUE, если вы хотите, чтобы эта строка немедленно была передана операционной системе.

Файл должен быть предварительно открыт перед вызовом UTL_FILE.PUT_LINE.

Пример использования PUT_LINE для вывода всех имен из таблицы в файл:

PROCEDURE names_to_file 
IS
   fileid UTL_FILE.file_type;
BEGIN
   fileid := UTL_FILE.FOPEN ('TEMP', 'names.dat', 'W');
   FOR emprec IN (SELECT * FROM employee)
   LOOP
      UTL_FILE.put_line (fileid, emprec.first_name || ' ' || emprec.last_name);
   END LOOP;

   UTL_FILE.fclose (fileid);
END names_to_file;

Вызов PUT_LINE эквивалентен вызову PUT, за которым следует вызов NEW_LINE. Также он эквивалентен вызову PUTF с форматной строкой «%s\n» (см. описание PUTF в следующем разделе).

 

Запись отформатированного текста в файл

Как и PUT, процедура PUTF помещает данные в файл, но использует форматные данные (отсюда и суффикс F) для интерпретации разных элементов, помещаемых в файл. При вызове PUTF можно передавать от одного до пяти элементов данных. Заголовок процедуры выглядит так:

PROCEDURE UTL_FILE.putf 
    (file IN FILE_TYPE ,format IN VARCHAR2 
    ,arg1 IN VARCHAR2 DEFAULT NULL 
    ,arg2 IN VARCHAR2 DEFAULT NULL 
    ,arg3 IN VARCHAR2 DEFAULT NULL 
    ,arg4 IN VARCHAR2 DEFAULT NULL 
    ,arg5 IN VARCHAR2 DEFAULT NULL);

Здесь file — дескриптор, полученный при вызове FOPEN, а format — строка, определяющая формат элементов в файле (см. ниже); параметры argN определяют необязательные строковые аргументы (от одного до пяти).

Форматная строка позволяет подставить значения argN прямо в текст, записываемый в файл. Кроме «шаблонного» (то есть литерального) текста форматная строка может содержать следующие служебные комбинации:

  •  %s — приказывает PUTF поместить соответствующий элемент в файл. Форматная строка может содержать до пяти комбинаций %s, потому что PUTF получает до пяти элементов.
  •  \n — приказывает PUTF поместить в файл символ новой строки. Количество вхождений \n в форматную строку не ограничено.

Комбинации %s заменяются строками аргументов в указанном порядке. Если при вызове передано недостаточно значений для подстановки на место всех форматных комбинаций, %s просто удаляется из строки перед записью в файл.

Следующий пример демонстрирует использование форматных строк. Допустим, содержимое файла должно выглядеть так:

Employee: Steven Feuerstein 
Soc Sec #: 123-45-5678 
Salary: $1000

Задача решается одним вызовом PUTF:

UTL_FILE.PUTF
   (file_handle, 'Employee: %s\nSoc Sec #: %s\nSalary: %s\n',
    'Steven Feuerstein',
    '123-45-5678',
    TO_CHAR (:employee.salary, '$9999'));

Если вам потребуется вывести более пяти элементов данных, просто вызовите PUTF дважды для завершения работы.

 

Копирование файлов

Процедура UTL_FILE.FCOPY позволяет легко скопировать содержимое одного файла в другой. Например, в следующем фрагменте в архивном каталоге создается

резервная копия файла из каталога разработки:

DECLARE
   file_suffix VARCHAR2 (100)
             := TO_CHAR (SYSDATE, 'YYYYMMDDHH24MISS');
BEGIN
   -- Копирование всего файла ...
   UTL_FILE.FCOPY (
      src_location   => 'DEVELOPMENT_DIR',
      src_filename   => 'archive.zip',
      dest_location  => 'ARCHIVE_DIR',
      dest_filename  =>    'archive'
                        || file_suffix
                        || '.zip'
   );
END;

Процедура FCOPY также может использоваться для копирования части файла. Два дополнительных параметра задают номера начальной и конечной строки блока, копируемого из файла. Допустим, в текстовом файле хранятся имена победителей ежемесячного конкурса знатоков PL/SQL, начавшегося в январе 2008 года. Мы хотим скопировать все имена, относящиеся к 2009 году, в другой файл. В этом нам помогут пятый и шестой аргументы FCOPY:

DECLARE
   c_start_year CONSTANT PLS_INTEGER := 2008; 
   c_year_of_interest CONSTANT PLS_INTEGER := 2009; 
   l_start PLS_INTEGER; 
   l_end PLS_INTEGER;
BEGIN
   l_start := (c_year_of_interest - c_start_year)*12 + 1; 
   l_end := l_start + 11;

   UTL_FILE.FCOPY (
      src_location   =>	'WINNERS_DIR',
      src_filename   =>	'names.txt',
      dest_location  =>	'WINNERS_DIR',
      dest_filename  =>	'names2008.txt',
      start_line     =>	l_start,
      end line       =>	l_end
   );
END;

Удобная инкапсуляция UTL_FILE.FCOPY позволит задавать начальную и конечную строки вместо номеров строк. При желании вы можете реализовать такую программу самостоятельно.

 

Удаление файлов

В Oracle9i Release 2 и последующих версиях для удаления файлов используется процедура UTL_FILE.FREMOVE. Заголовок процедуры выглядит так:

PROCEDURE UTL_FILE.FREMOVE (
   Location IN VARCHAR2, 
   filename IN VARCHAR2);

Например, в следующем фрагменте UTL_FILE.FREMOVE удаляет архивный файл из предыдущего примера:

BEGIN
   UTL_FILE.FREMOVE ('DEVELOPMENT_DIR', 'archive.zip');
END;

Вы задаете путь и имя файла, а UTL_FILE пытается его удалить. А если при удалении возникнут проблемы? Будет инициировано одно из следующих исключений.

Исключение Описание
UTL_FILE.invalid_path Недействительный дескриптор
UTL_FILE.invalid_filename Файл не найден или имя файла равно NULL
UTL_FILE.file_open Файл уже открыт для записи/присоединения
UTL_FILE.access_denied Отказано в доступе к объекту каталога
UTL_FILE.remove_failed Сбой при удалении файла

 

Иначе говоря, UTL_FILE инициирует исключение при попытке удаления несуществующего файла или при отсутствии привилегий, необходимых для удаления файла. Многие программы удаления файлов в других языках (например, File.delete в Java) возвращают код состояния, который информирует вас о результате попытки удаления. Если вы предпочитаете этот способ, используйте (или скопируйте) программу fileIO.FREMOVE из файла fileIO.pkg на сайте githib.

 

Переименование и перемещение файлов

Операции копирования и удаления объединяются в вызове процедуры UTL_FILE.RENAME. Эта удобная утилита позволяет либо переименовать файл в том же каталоге, либо изменить как имя, так и местонахождение (то есть фактически переместить файл). Заголовок FRENAME выглядит так:

PROCEDURE UTL_FILE.frename (
   src_location   IN VARCHAR2,
   src_filename   IN VARCHAR2,
   dest_location  IN VARCHAR2,
   dest_filename  IN VARCHAR2,
   overwrite      IN BOOLEAN DEFAULT FALSE);

Программа может инициировать одно из следующих исключений.

 

Исключение Описание
UTL_FILE.invalid_path Недействительный дескриптор
UTL_FILE.invalid_filename Файл не найден или имя файла равно NULL
UTL_FILE.access_denied Отказано в доступе к объекту каталога
UTL_FILE.remove_failed Сбой при удалении файла

 

Получение атрибутов файла

Иногда требуется получить информацию о конкретном файле. Сколько места занимает файл? Существует ли он? Какой размер блока используется для хранения файла? Ответы уже не являются секретами, которые могут быть разрешены только с помощью команды операционной системы (или в случае длины файла — пакета DBMS_LOB), как в ранних версиях Oracle. Всю эту информацию можно получить за один вызов процедуры UTL_FILE.FGETATTR.

Заголовок FGETATTR выглядит так:

PROCEDURE UTL_FILE.FGETATTR (
   Location IN VARCHAR2, 
   filename IN VARCHAR2, 
   fexists OUT BOOLEAN, 
   fiLe_Length OUT NUMBER, 
   bLock_size OUT BINARY_INTEGER);

Таким образом, чтобы использовать эту программу, вы должны объявить три переменные для хранения логического признака (существует ли файл?), длины файла и размера блока. Пример использования:

DECLARE
   L_fexists       BOOLEAN;
   L_fiLe_Length   PLS_INTEGER;
   L_bLock_size    PLS_INTEGER;
BEGIN
   UTL_FILE.FGETATTR (
      Location     => 'DEVELOPMENT_DIR',
      fiLename     => 'bigpkg.pkg', 
      fexists      => l_fexists, 
      fiLe_Length  => l_file_length, 
      bLock_size   => l_block_size
   );
   ...
END;

Такой интерфейс весьма неудобен. Даже если вас интересует только длина файла, все равно придется объявлять все переменные, получать длину, а затем работать с полученным значением. Возможно, для работы с FGETATTR стоит создать собственные функции- «обертки», которые позволяют получить отдельное значение:

FUNCTION fileIO.flength (
   location_in	IN	VARCHAR2,
   file_in	IN	VARCHAR2
   )
   RETURN PLS_INTEGER;

или :

FUNCTION fileIO.fexists ( 
   location_in IN VARCHAR2, 
   file_in IN VARCHAR2 
   )
      RETURN BOOLEAN;

В этом случае вам не приходится объявлять ненужные переменные, а программный код становится проще и лаконичнее.

 

Вас заинтересует / Intresting for you:

Встроенные методы коллекций PL...
Встроенные методы коллекций PL... 2307 просмотров sepia Tue, 29 Oct 2019, 09:54:01
Управление приложениями PL/SQL...
Управление приложениями PL/SQL... 1511 просмотров Rasen Fasenger Mon, 22 Oct 2018, 04:44:08
Работа с числами в PL/SQL на п...
Работа с числами в PL/SQL на п... 4890 просмотров Antoniy Mon, 28 May 2018, 16:45:11
Основы языка PL/SQL: использов...
Основы языка PL/SQL: использов... 1600 просмотров Ирина Светлова Tue, 06 Feb 2018, 14:04:03

Comments on Работа с файлами в PL/SQL: чтение, запись, открытие и закрытие

Будьте первым прокомментировавшим
Пожалуйста, авторизуйтесь, для комментирования