I wanted to unzip a file using APEX_ZIP today, and found that the old method was deprecated and now there is a completely different method. Lets unpack it all with examples aplenty.
Preparation
For this demonstration, I’ve decided to zip up the apex\utilities
folder, from the APEX download site which contains these files:
Giving a ZIP file looking like this:
I’ve uploaded the zip it to this table
CREATE TABLE "MY_FILES"
( "ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
"MY_FILE" BLOB,
"MY_FILE_FILENAME" VARCHAR2(255 CHAR),
"MY_FILE_MIMETYPE" VARCHAR2(255 CHAR),
"MY_FILE_CHARSET" VARCHAR2(255 CHAR),
"MY_FILE_LASTUPD" DATE,
CONSTRAINT "MY_FILES_ID_PK" PRIMARY KEY ("ID")
USING INDEX ENABLE
) ;
Great. We’re all set.
Documentation
Notice that the APEX 23.2 Documentation shows this:
However the APEX 24.1 Documentation shows this:
What these pictures are telling me is that the usual way of getting the list of files (GET_FILES
) AND the method of getting a particular file (GET_FILE_CONTENT
signature 1) are both deprecated!
No we are not doomed. There is a new method in town.
Unzipping files
Browsing the zip?
If you are browsing the zip, perhaps you are looking for particular files matching a regex or similar, then consider this code:
DECLARE
l_zip_file blob;
l_unzipped_file blob;
l_dir apex_zip.t_dir_entries;
l_file_path varchar2(32767);
l_current_entry apex_zip.t_dir_entry;
BEGIN
-- Retrieve the zip file from the database
SELECT my_file
INTO l_zip_file
FROM my_files
WHERE my_file_filename = 'utilities.zip';
-- Get all directory entries from the zip file
l_dir := apex_zip.get_dir_entries (
p_zipped_blob => l_zip_file,
p_only_files => FALSE );
-- Print header row for the output table
DBMS_OUTPUT.PUT_LINE(RPAD('Dir', 5) || ' ' ||
RPAD('Size', 5) || ' ' ||
RPAD('Filename', 35) || ' ' ||
RPAD('Index', 35) || ' ' ||
'Uncompressed-Size');
-- Print separator line
DBMS_OUTPUT.PUT_LINE(RPAD('-', 101, '-'));
-- Start with the first entry in the directory
l_file_path := l_dir.first;
-- Loop through all entries in the zip file
WHILE l_file_path IS NOT NULL LOOP
-- Initialize unzipped file as empty BLOB
l_unzipped_file := EMPTY_BLOB();
-- Get current directory entry
l_current_entry := l_dir(l_file_path);
-- If the entry is not a directory, extract its content
IF l_current_entry.is_directory IS NULL THEN
l_unzipped_file := apex_zip.get_file_content (
p_zipped_blob => l_zip_file,
p_dir_entry => l_current_entry );
END IF;
-- Print information about the current entry
DBMS_OUTPUT.PUT_LINE(
apex_string.format( '%0 %1 %2 %3 %4' ,
RPAD(apex_debug.tochar(l_current_entry.is_directory), 5),
LPAD(l_current_entry.uncompressed_length, 5),
RPAD(l_current_entry.file_name, 35),
RPAD(l_file_path, 35),
LPAD(LENGTH(l_unzipped_file), 5)
));
-- Move to the next entry in the directory
l_file_path := l_dir.next(l_file_path);
END LOOP;
END;
this produces:
Dir Size Filename Index Uncompressed-Size
-----------------------------------------------------------------------------------------------------
null 28046 apxpart.sql apxpart.sql 28046
null 1815 apxrekey.sql apxrekey.sql 1815
null 4601 check_lovs_for_errors.sql check_lovs_for_errors.sql 4601
true 0 debug/ debug/ 0
null 12615 debug/activity.sql debug/activity.sql 12615
null 2724 debug/d0.sql debug/d0.sql 2724
null 2870 debug/d1.sql debug/d1.sql 2870
null 3112 debug/d2.sql debug/d2.sql 3112
null 2797 debug/ds.sql debug/ds.sql 2797
null 4683 enable_sso.sql enable_sso.sql 4683
null 15746 report_instance_configuration.sql report_instance_configuration.sql 15746
null 1782 reset_image_prefix.sql reset_image_prefix.sql 1782
null 2772 reset_image_prefix_con.sql reset_image_prefix_con.sql 2772
null 2642 reset_image_prefix_core.sql reset_image_prefix_core.sql 2642
true 0 support/ support/ 0
null 33979 support/apex_verify.sql support/apex_verify.sql 33979
We can observe that:
The presence of the
Uncompressed-Size
means that its been unzipped & the fact that its identical to thesize
means its been correctly unzippedapex_zip.get_dir_entries
returns an object of typet_dir_entries
which is atable of t_dir_entry index by VARCHAR2(32767)
. What is the index? - its the filename! This is why I can grab the filename by either of these two:l_file_path
(index) ← shorter & easier!l_current_entry.file_name
(attribute)
If the index was a
BINARY_INTEGER
we could do aFOR..LOOP
around it. However its aVARCHAR2
, therefore we need to LOOP around it using aWHILE l_file_path IS NOT NULL
.If I want to grab the file, I have to set it the whole record type which I fetch on each loop. This is stored as
l_dir(l_file_path)
and is used as an in parameter toapex_zip.get_file_content
l_unzipped_file := apex_zip.get_file_content ( p_zipped_blob => l_zip_file, p_dir_entry => l_current_entry );
l_current_entry.is_directory
is true whenapex_zip.get_dir_entries
is called withp_only_files => FALSE
. This means that I had to useIF l_current_entry.is_directory IS NULL THEN
to detect files. Alternatively you can omit thep_only_files
as this defaults to TRUE; the issue with this is thatl_current_entry.is_directory
will only ever benull
. It cannot be FALSE - source.
Unzipping a particular file?
If you are looking for a particular file it becomes way easier
DECLARE
l_zip_file blob;
l_unzipped_file blob;
l_dir apex_zip.t_dir_entries;
l_file_path varchar2(32767);
l_current_entry apex_zip.t_dir_entry;
BEGIN
-- Retrieve the zip file from the database
SELECT my_file
INTO l_zip_file
FROM my_files
WHERE my_file_filename = 'utilities.zip';
-- Get all directory entries from the zip file
l_dir := apex_zip.get_dir_entries (
p_zipped_blob => l_zip_file );
-- Print header row for the output table
DBMS_OUTPUT.PUT_LINE(RPAD('Dir', 5) || ' ' ||
RPAD('Size', 5) || ' ' ||
RPAD('Filename', 35) || ' ' ||
RPAD('Index', 35) || ' ' ||
'Uncompressed-Size');
-- Print separator line
DBMS_OUTPUT.PUT_LINE(RPAD('-', 101, '-'));
l_file_path := 'support/apex_verify.sql';
l_current_entry := l_dir( l_file_path );
l_unzipped_file := apex_zip.get_file_content (
p_zipped_blob => l_zip_file,
p_dir_entry => l_current_entry );
-- Print information about the current entry
DBMS_OUTPUT.PUT_LINE(
apex_string.format( '%0 %1 %2 %3 %4' ,
RPAD(apex_debug.tochar(l_current_entry.is_directory), 5),
LPAD(l_current_entry.uncompressed_length, 5),
RPAD(l_current_entry.file_name, 35),
RPAD(l_file_path, 35),
LPAD(LENGTH(l_unzipped_file), 5)
));
END;
this produces
Dir Size Filename Index Uncompressed-Size
-----------------------------------------------------------------------------------------------------
null 33979 support/apex_verify.sql support/apex_verify.sql 33979
We can observe that:
No looping! We still need to call
apex_zip.get_dir_entries
which gives us our collection. We can then identify which record in the collection by the filename and then pass it intoapex_zip.get_file_content
i.e.l_file_path := 'support/apex_verify.sql'; l_current_entry := l_dir( l_file_path ); l_unzipped_file := apex_zip.get_file_content ( p_zipped_blob => l_zip_file, p_dir_entry => l_current_entry );
ENJOY
What’s the picture? It’s Cross James St. Harrogate. Visit Yorkshire!
Credits
Oracle APEX ZIP API - an Upgrade which is not officially documented - Zoran Tica
apex_zip.get_dir_entries - Oracle Documentation