diff --git a/1_install.sql b/1_install.sql index a55615d..f07bbe8 100644 --- a/1_install.sql +++ b/1_install.sql @@ -1,4 +1,11 @@ prompt Installing PL/SQL Export Utilities -set define off -@PLEX.pks -@PLEX.pkb \ No newline at end of file +SET DEFINE OFF + +BEGIN + EXECUTE IMMEDIATE q'[ alter session set plsql_ccflags='apex_exists:false' ]'; +END; +/ + +@plex.pks + +@plex.pkb \ No newline at end of file diff --git a/PLEX.pkb b/PLEX.pkb index 0d1d6b8..8bad4e7 100755 --- a/PLEX.pkb +++ b/PLEX.pkb @@ -982,6 +982,8 @@ CREATE OR REPLACE PACKAGE BODY plex IS -- MAIN CODE FUNCTION backapp ( + $if $$apex_exists $then + p_app_id IN NUMBER DEFAULT NULL, p_app_date IN BOOLEAN DEFAULT true, p_app_public_reports IN BOOLEAN DEFAULT true, @@ -995,6 +997,7 @@ CREATE OR REPLACE PACKAGE BODY plex IS p_app_supporting_objects IN VARCHAR2 DEFAULT NULL, p_app_include_single_file IN BOOLEAN DEFAULT false, p_app_build_status_run_only IN BOOLEAN DEFAULT false, + $end p_include_object_ddl IN BOOLEAN DEFAULT false, p_object_name_like IN VARCHAR2 DEFAULT NULL, p_object_name_not_like IN VARCHAR2 DEFAULT NULL, @@ -1028,16 +1031,21 @@ CREATE OR REPLACE PACKAGE BODY plex IS BEGIN util_ilog_init( p_module => 'plex.backapp' + $if $$apex_exists $then + || CASE WHEN p_app_id IS NOT NULL THEN '(' || TO_CHAR(p_app_id) || ')' - END, + END + $end, p_include_runtime_log => p_include_runtime_log ); END init; +$if $$apex_exists $then + PROCEDURE check_owner IS CURSOR cur_owner IS SELECT @@ -1078,6 +1086,9 @@ CREATE OR REPLACE PACKAGE BODY plex IS util_ilog_stop; END check_owner; +$end + +$if $$apex_exists $then PROCEDURE process_apex_app IS l_single_file tab_export_files; @@ -1189,16 +1200,17 @@ CREATE OR REPLACE PACKAGE BODY plex IS END IF; END process_apex_app; + $end PROCEDURE replace_query_like_expressions ( p_like_list VARCHAR2, p_not_like_list VARCHAR2, p_column_name VARCHAR2 ) IS - l_expression_table apex_tab_varchar2; + l_expression_table tab_varchar2; BEGIN -- process filter "like" - l_expression_table := apex_string.split( + l_expression_table := util_split( p_like_list, ',' ); @@ -1212,7 +1224,7 @@ CREATE OR REPLACE PACKAGE BODY plex IS l_query, '#LIKE_EXPRESSIONS#', nvl( - apex_string.join( + util_join( l_expression_table, ' or ' ), @@ -1222,7 +1234,7 @@ CREATE OR REPLACE PACKAGE BODY plex IS -- process filter "not like" - l_expression_table := apex_string.split( + l_expression_table := util_split( p_not_like_list, ',' ); @@ -1236,7 +1248,7 @@ CREATE OR REPLACE PACKAGE BODY plex IS l_query, '#NOT_LIKE_EXPRESSIONS#', nvl( - apex_string.join( + util_join( l_expression_table, ' and ' ), @@ -2083,6 +2095,8 @@ if %errorlevel% neq 0 exit /b %errorlevel% c_plex_url, '{{SYSTEMROLE}}', 'DEV', + $if $$apex_exists $then + '{{APP_ID}}', p_app_id, '{{APP_ALIAS}}', @@ -2091,6 +2105,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% l_app_owner, '{{APP_WORKSPACE}}', l_app_workspace, + $end '{{SCRIPTFILE}}', 'export_app_custom_code.sql', '{{LOGFILE}}', @@ -2116,6 +2131,8 @@ if %errorlevel% neq 0 exit /b %errorlevel% c_plex_url, '{{SYSTEMROLE}}', 'TEST', + $if $$apex_exists $then + '{{APP_ID}}', p_app_id, '{{APP_ALIAS}}', @@ -2124,6 +2141,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% l_app_owner, '{{APP_WORKSPACE}}', l_app_workspace, + $end '{{SCRIPTFILE}}', 'install_app_custom_code.sql', '{{LOGFILE}}', @@ -2149,6 +2167,8 @@ if %errorlevel% neq 0 exit /b %errorlevel% c_plex_url, '{{SYSTEMROLE}}', 'PROD', + $if $$apex_exists $then + '{{APP_ID}}', p_app_id, '{{APP_ALIAS}}', @@ -2157,6 +2177,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% l_app_owner, '{{APP_WORKSPACE}}', l_app_workspace, + $end '{{SCRIPTFILE}}', 'install_app_custom_code.sql', '{{LOGFILE}}', @@ -2219,7 +2240,11 @@ DECLARE l_files tab_export_files; BEGIN l_files := plex.backapp ( - -- These are the defaults - align it to your needs: + -- These are the defaults - align it to your needs:^' + ; +$if $$apex_exists $then + + l_file_template := l_file_template || q'^ p_app_id => :app_id, p_app_date => true, p_app_public_reports => true, @@ -2232,8 +2257,10 @@ BEGIN p_app_comments => true, p_app_supporting_objects => null, p_app_include_single_file => false, - p_app_build_status_run_only => false, - + p_app_build_status_run_only => false,^' + ; +$end + l_file_template := l_file_template || q'^ p_include_object_ddl => true, p_object_name_like => null, p_object_name_not_like => null, @@ -2509,13 +2536,19 @@ prompt BEGIN init; + $if $$apex_exists $then + check_owner; -- IF p_app_id IS NOT NULL THEN process_apex_app; ELSE + $end l_export_files := NEW tab_export_files(); + $if $$apex_exists $then + END IF; + $end -- IF p_include_object_ddl THEN diff --git a/PLEX.pks b/PLEX.pks index b1781d9..81f724f 100755 --- a/PLEX.pks +++ b/PLEX.pks @@ -59,6 +59,7 @@ TYPE tab_varchar2 IS TABLE OF varchar2(32767); FUNCTION backapp ( +$if $$apex_exists $then -- App related options: p_app_id IN NUMBER DEFAULT null, -- If null, we simply skip the APEX app export. p_app_date IN BOOLEAN DEFAULT true, -- If true, include export date and time in the result. @@ -73,6 +74,7 @@ FUNCTION backapp ( p_app_supporting_objects IN VARCHAR2 DEFAULT null, -- If 'Y', export supporting objects. If 'I', automatically install on import. If 'N', do not export supporting objects. If null, the application's include in export deployment value is used. p_app_include_single_file IN BOOLEAN DEFAULT false, -- If true, the single sql install file is also included beside the splitted files. p_app_build_status_run_only IN BOOLEAN DEFAULT false, -- If true, the build status of the app will be overwritten to RUN_ONLY. +$end -- Object related options: p_include_object_ddl IN BOOLEAN DEFAULT false, -- If true, include DDL of current user/schema and all its objects. p_object_name_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the objects - example: 'EMP%,DEPT%' will be translated to: where ... and (object_name like 'EMP%' escape '\' or object_name like 'DEPT%' escape '\'). diff --git a/lib/as_zip.sql b/lib/as_zip.sql new file mode 100644 index 0000000..8a7a530 --- /dev/null +++ b/lib/as_zip.sql @@ -0,0 +1,528 @@ +CREATE OR REPLACE package as_zip +is +/********************************************** +** +** Author: Anton Scheffer +** Date: 25-01-2012 +** Website: http://technology.amis.nl/blog +** +** Changelog: +** Date: 04-08-2016 +** fixed endless loop for empty/null zip file +** Date: 28-07-2016 +** added support for defate64 (this only works for zip-files created with 7Zip) +** Date: 31-01-2014 +** file limit increased to 4GB +** Date: 29-04-2012 +** fixed bug for large uncompressed files, thanks Morten Braten +** Date: 21-03-2012 +** Take CRC32, compressed length and uncompressed length from +** Central file header instead of Local file header +** Date: 17-02-2012 +** Added more support for non-ascii filenames +** Date: 25-01-2012 +** Added MIT-license +** Some minor improvements +****************************************************************************** +****************************************************************************** +Copyright (C) 2010,2011 by Anton Scheffer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +****************************************************************************** +******************************************** */ + type file_list is table of clob; +-- + function file2blob + ( p_dir varchar2 + , p_file_name varchar2 + ) + return blob; +-- + function get_file_list + ( p_dir varchar2 + , p_zip_file varchar2 + , p_encoding varchar2 := null + ) + return file_list; +-- + function get_file_list + ( p_zipped_blob blob + , p_encoding varchar2 := null + ) + return file_list; +-- + function get_file + ( p_dir varchar2 + , p_zip_file varchar2 + , p_file_name varchar2 + , p_encoding varchar2 := null + ) + return blob; +-- + function get_file + ( p_zipped_blob blob + , p_file_name varchar2 + , p_encoding varchar2 := null + ) + return blob; +-- + procedure add1file + ( p_zipped_blob in out blob + , p_name varchar2 + , p_content blob + ); +-- + procedure finish_zip( p_zipped_blob in out blob ); +-- + procedure save_zip + ( p_zipped_blob blob + , p_dir varchar2 := 'MY_DIR' + , p_filename varchar2 := 'my.zip' + ); +-- +/* +declare + g_zipped_blob blob; +begin + as_zip.add1file( g_zipped_blob, 'test4.txt', null ); -- a empty file + as_zip.add1file( g_zipped_blob, 'dir1/test1.txt', utl_raw.cast_to_raw( q'' ) ); + as_zip.add1file( g_zipped_blob, 'test1234.txt', utl_raw.cast_to_raw( 'A small file' ) ); + as_zip.add1file( g_zipped_blob, 'dir2/', null ); -- a folder + as_zip.add1file( g_zipped_blob, 'dir3/', null ); -- a folder + as_zip.add1file( g_zipped_blob, 'dir3/test2.txt', utl_raw.cast_to_raw( 'A small filein a previous created folder' ) ); + as_zip.finish_zip( g_zipped_blob ); + as_zip.save_zip( g_zipped_blob, 'MY_DIR', 'my.zip' ); + dbms_lob.freetemporary( g_zipped_blob ); +end; +-- +declare + zip_files as_zip.file_list; +begin + zip_files := as_zip.get_file_list( 'MY_DIR', 'my.zip' ); + for i in zip_files.first() .. zip_files.last + loop + dbms_output.put_line( zip_files( i ) ); + dbms_output.put_line( utl_raw.cast_to_varchar2( as_zip.get_file( 'MY_DIR', 'my.zip', zip_files( i ) ) ) ); + end loop; +end; +*/ +end; +/ + +CREATE OR REPLACE package body as_zip +is +-- + c_LOCAL_FILE_HEADER constant raw(4) := hextoraw( '504B0304' ); -- Local file header signature + c_END_OF_CENTRAL_DIRECTORY constant raw(4) := hextoraw( '504B0506' ); -- End of central directory signature +-- + function blob2num( p_blob blob, p_len integer, p_pos integer ) + return number + is + rv number; + begin + rv := utl_raw.cast_to_binary_integer( dbms_lob.substr( p_blob, p_len, p_pos ), utl_raw.little_endian ); + if rv < 0 + then + rv := rv + 4294967296; + end if; + return rv; + end; +-- + function raw2varchar2( p_raw raw, p_encoding varchar2 ) + return varchar2 + is + begin + return coalesce( utl_i18n.raw_to_char( p_raw, p_encoding ) + , utl_i18n.raw_to_char( p_raw, utl_i18n.map_charset( p_encoding, utl_i18n.GENERIC_CONTEXT, utl_i18n.IANA_TO_ORACLE ) ) + ); + end; +-- + function little_endian( p_big number, p_bytes pls_integer := 4 ) + return raw + is + t_big number := p_big; + begin + if t_big > 2147483647 + then + t_big := t_big - 4294967296; + end if; + return utl_raw.substr( utl_raw.cast_from_binary_integer( t_big, utl_raw.little_endian ), 1, p_bytes ); + end; +-- + function file2blob + ( p_dir varchar2 + , p_file_name varchar2 + ) + return blob + is + file_lob bfile; + file_blob blob; + begin + file_lob := bfilename( p_dir, p_file_name ); + dbms_lob.open( file_lob, dbms_lob.file_readonly ); + dbms_lob.createtemporary( file_blob, true ); + dbms_lob.loadfromfile( file_blob, file_lob, dbms_lob.lobmaxsize ); + dbms_lob.close( file_lob ); + return file_blob; + exception + when others then + if dbms_lob.isopen( file_lob ) = 1 + then + dbms_lob.close( file_lob ); + end if; + if dbms_lob.istemporary( file_blob ) = 1 + then + dbms_lob.freetemporary( file_blob ); + end if; + raise; + end; +-- + function get_file_list + ( p_zipped_blob blob + , p_encoding varchar2 := null + ) + return file_list + is + t_ind integer; + t_hd_ind integer; + t_rv file_list; + t_encoding varchar2(32767); + begin + t_ind := nvl( dbms_lob.getlength( p_zipped_blob ), 0 ) - 21; + loop + exit when t_ind < 1 or dbms_lob.substr( p_zipped_blob, 4, t_ind ) = c_END_OF_CENTRAL_DIRECTORY; + t_ind := t_ind - 1; + end loop; +-- + if t_ind <= 0 + then + return null; + end if; +-- + t_hd_ind := blob2num( p_zipped_blob, 4, t_ind + 16 ) + 1; + t_rv := file_list(); + t_rv.extend( blob2num( p_zipped_blob, 2, t_ind + 10 ) ); + for i in 1 .. blob2num( p_zipped_blob, 2, t_ind + 8 ) + loop + if p_encoding is null + then + if utl_raw.bit_and( dbms_lob.substr( p_zipped_blob, 1, t_hd_ind + 9 ), hextoraw( '08' ) ) = hextoraw( '08' ) + then + t_encoding := 'AL32UTF8'; -- utf8 + else + t_encoding := 'US8PC437'; -- IBM codepage 437 + end if; + else + t_encoding := p_encoding; + end if; + t_rv( i ) := raw2varchar2 + ( dbms_lob.substr( p_zipped_blob + , blob2num( p_zipped_blob, 2, t_hd_ind + 28 ) + , t_hd_ind + 46 + ) + , t_encoding + ); + t_hd_ind := t_hd_ind + 46 + + blob2num( p_zipped_blob, 2, t_hd_ind + 28 ) -- File name length + + blob2num( p_zipped_blob, 2, t_hd_ind + 30 ) -- Extra field length + + blob2num( p_zipped_blob, 2, t_hd_ind + 32 ); -- File comment length + end loop; +-- + return t_rv; + end; +-- + function get_file_list + ( p_dir varchar2 + , p_zip_file varchar2 + , p_encoding varchar2 := null + ) + return file_list + is + begin + return get_file_list( file2blob( p_dir, p_zip_file ), p_encoding ); + end; +-- + function get_file + ( p_zipped_blob blob + , p_file_name varchar2 + , p_encoding varchar2 := null + ) + return blob + is + t_tmp blob; + t_ind integer; + t_hd_ind integer; + t_fl_ind integer; + t_encoding varchar2(32767); + t_len integer; + begin + t_ind := nvl( dbms_lob.getlength( p_zipped_blob ), 0 ) - 21; + loop + exit when t_ind < 1 or dbms_lob.substr( p_zipped_blob, 4, t_ind ) = c_END_OF_CENTRAL_DIRECTORY; + t_ind := t_ind - 1; + end loop; +-- + if t_ind <= 0 + then + return null; + end if; +-- + t_hd_ind := blob2num( p_zipped_blob, 4, t_ind + 16 ) + 1; + for i in 1 .. blob2num( p_zipped_blob, 2, t_ind + 8 ) + loop + if p_encoding is null + then + if utl_raw.bit_and( dbms_lob.substr( p_zipped_blob, 1, t_hd_ind + 9 ), hextoraw( '08' ) ) = hextoraw( '08' ) + then + t_encoding := 'AL32UTF8'; -- utf8 + else + t_encoding := 'US8PC437'; -- IBM codepage 437 + end if; + else + t_encoding := p_encoding; + end if; + if p_file_name = raw2varchar2 + ( dbms_lob.substr( p_zipped_blob + , blob2num( p_zipped_blob, 2, t_hd_ind + 28 ) + , t_hd_ind + 46 + ) + , t_encoding + ) + then + t_len := blob2num( p_zipped_blob, 4, t_hd_ind + 24 ); -- uncompressed length + if t_len = 0 + then + if substr( p_file_name, -1 ) in ( '/', '\' ) + then -- directory/folder + return null; + else -- empty file + return empty_blob(); + end if; + end if; +-- + if dbms_lob.substr( p_zipped_blob, 2, t_hd_ind + 10 ) in ( hextoraw( '0800' ) -- deflate + , hextoraw( '0900' ) -- deflate64 + ) + then + t_fl_ind := blob2num( p_zipped_blob, 4, t_hd_ind + 42 ); + t_tmp := hextoraw( '1F8B0800000000000003' ); -- gzip header + dbms_lob.copy( t_tmp + , p_zipped_blob + , blob2num( p_zipped_blob, 4, t_hd_ind + 20 ) + , 11 + , t_fl_ind + 31 + + blob2num( p_zipped_blob, 2, t_fl_ind + 27 ) -- File name length + + blob2num( p_zipped_blob, 2, t_fl_ind + 29 ) -- Extra field length + ); + dbms_lob.append( t_tmp, utl_raw.concat( dbms_lob.substr( p_zipped_blob, 4, t_hd_ind + 16 ) -- CRC32 + , little_endian( t_len ) -- uncompressed length + ) + ); + return utl_compress.lz_uncompress( t_tmp ); + end if; +-- + if dbms_lob.substr( p_zipped_blob, 2, t_hd_ind + 10 ) = hextoraw( '0000' ) -- The file is stored (no compression) + then + t_fl_ind := blob2num( p_zipped_blob, 4, t_hd_ind + 42 ); + dbms_lob.createtemporary( t_tmp, true ); + dbms_lob.copy( t_tmp + , p_zipped_blob + , t_len + , 1 + , t_fl_ind + 31 + + blob2num( p_zipped_blob, 2, t_fl_ind + 27 ) -- File name length + + blob2num( p_zipped_blob, 2, t_fl_ind + 29 ) -- Extra field length + ); + return t_tmp; + end if; + end if; + t_hd_ind := t_hd_ind + 46 + + blob2num( p_zipped_blob, 2, t_hd_ind + 28 ) -- File name length + + blob2num( p_zipped_blob, 2, t_hd_ind + 30 ) -- Extra field length + + blob2num( p_zipped_blob, 2, t_hd_ind + 32 ); -- File comment length + end loop; +-- + return null; + end; +-- + function get_file + ( p_dir varchar2 + , p_zip_file varchar2 + , p_file_name varchar2 + , p_encoding varchar2 := null + ) + return blob + is + begin + return get_file( file2blob( p_dir, p_zip_file ), p_file_name, p_encoding ); + end; +-- + procedure add1file + ( p_zipped_blob in out blob + , p_name varchar2 + , p_content blob + ) + is + t_now date; + t_blob blob; + t_len integer; + t_clen integer; + t_crc32 raw(4) := hextoraw( '00000000' ); + t_compressed boolean := false; + t_name raw(32767); + begin + t_now := sysdate; + t_len := nvl( dbms_lob.getlength( p_content ), 0 ); + if t_len > 0 + then + t_blob := utl_compress.lz_compress( p_content ); + t_clen := dbms_lob.getlength( t_blob ) - 18; + t_compressed := t_clen < t_len; + t_crc32 := dbms_lob.substr( t_blob, 4, t_clen + 11 ); + end if; + if not t_compressed + then + t_clen := t_len; + t_blob := p_content; + end if; + if p_zipped_blob is null + then + dbms_lob.createtemporary( p_zipped_blob, true ); + end if; + t_name := utl_i18n.string_to_raw( p_name, 'AL32UTF8' ); + dbms_lob.append( p_zipped_blob + , utl_raw.concat( c_LOCAL_FILE_HEADER -- Local file header signature + , hextoraw( '1400' ) -- version 2.0 + , case when t_name = utl_i18n.string_to_raw( p_name, 'US8PC437' ) + then hextoraw( '0000' ) -- no General purpose bits + else hextoraw( '0008' ) -- set Language encoding flag (EFS) + end + , case when t_compressed + then hextoraw( '0800' ) -- deflate + else hextoraw( '0000' ) -- stored + end + , little_endian( to_number( to_char( t_now, 'ss' ) ) / 2 + + to_number( to_char( t_now, 'mi' ) ) * 32 + + to_number( to_char( t_now, 'hh24' ) ) * 2048 + , 2 + ) -- File last modification time + , little_endian( to_number( to_char( t_now, 'dd' ) ) + + to_number( to_char( t_now, 'mm' ) ) * 32 + + ( to_number( to_char( t_now, 'yyyy' ) ) - 1980 ) * 512 + , 2 + ) -- File last modification date + , t_crc32 -- CRC-32 + , little_endian( t_clen ) -- compressed size + , little_endian( t_len ) -- uncompressed size + , little_endian( utl_raw.length( t_name ), 2 ) -- File name length + , hextoraw( '0000' ) -- Extra field length + , t_name -- File name + ) + ); + if t_compressed + then + dbms_lob.copy( p_zipped_blob, t_blob, t_clen, dbms_lob.getlength( p_zipped_blob ) + 1, 11 ); -- compressed content + elsif t_clen > 0 + then + dbms_lob.copy( p_zipped_blob, t_blob, t_clen, dbms_lob.getlength( p_zipped_blob ) + 1, 1 ); -- content + end if; + if dbms_lob.istemporary( t_blob ) = 1 + then + dbms_lob.freetemporary( t_blob ); + end if; + end; +-- + procedure finish_zip( p_zipped_blob in out blob ) + is + t_cnt pls_integer := 0; + t_offs integer; + t_offs_dir_header integer; + t_offs_end_header integer; + t_comment raw(32767) := utl_raw.cast_to_raw( 'Implementation by Anton Scheffer' ); + begin + t_offs_dir_header := dbms_lob.getlength( p_zipped_blob ); + t_offs := 1; + while dbms_lob.substr( p_zipped_blob, utl_raw.length( c_LOCAL_FILE_HEADER ), t_offs ) = c_LOCAL_FILE_HEADER + loop + t_cnt := t_cnt + 1; + dbms_lob.append( p_zipped_blob + , utl_raw.concat( hextoraw( '504B0102' ) -- Central directory file header signature + , hextoraw( '1400' ) -- version 2.0 + , dbms_lob.substr( p_zipped_blob, 26, t_offs + 4 ) + , hextoraw( '0000' ) -- File comment length + , hextoraw( '0000' ) -- Disk number where file starts + , hextoraw( '0000' ) -- Internal file attributes => + -- 0000 binary file + -- 0100 (ascii)text file + , case + when dbms_lob.substr( p_zipped_blob + , 1 + , t_offs + 30 + blob2num( p_zipped_blob, 2, t_offs + 26 ) - 1 + ) in ( hextoraw( '2F' ) -- / + , hextoraw( '5C' ) -- \ + ) + then hextoraw( '10000000' ) -- a directory/folder + else hextoraw( '2000B681' ) -- a file + end -- External file attributes + , little_endian( t_offs - 1 ) -- Relative offset of local file header + , dbms_lob.substr( p_zipped_blob + , blob2num( p_zipped_blob, 2, t_offs + 26 ) + , t_offs + 30 + ) -- File name + ) + ); + t_offs := t_offs + 30 + blob2num( p_zipped_blob, 4, t_offs + 18 ) -- compressed size + + blob2num( p_zipped_blob, 2, t_offs + 26 ) -- File name length + + blob2num( p_zipped_blob, 2, t_offs + 28 ); -- Extra field length + end loop; + t_offs_end_header := dbms_lob.getlength( p_zipped_blob ); + dbms_lob.append( p_zipped_blob + , utl_raw.concat( c_END_OF_CENTRAL_DIRECTORY -- End of central directory signature + , hextoraw( '0000' ) -- Number of this disk + , hextoraw( '0000' ) -- Disk where central directory starts + , little_endian( t_cnt, 2 ) -- Number of central directory records on this disk + , little_endian( t_cnt, 2 ) -- Total number of central directory records + , little_endian( t_offs_end_header - t_offs_dir_header ) -- Size of central directory + , little_endian( t_offs_dir_header ) -- Offset of start of central directory, relative to start of archive + , little_endian( nvl( utl_raw.length( t_comment ), 0 ), 2 ) -- ZIP file comment length + , t_comment + ) + ); + end; +-- + procedure save_zip + ( p_zipped_blob blob + , p_dir varchar2 := 'MY_DIR' + , p_filename varchar2 := 'my.zip' + ) + is + t_fh utl_file.file_type; + t_len pls_integer := 32767; + begin + t_fh := utl_file.fopen( p_dir, p_filename, 'wb' ); + for i in 0 .. trunc( ( dbms_lob.getlength( p_zipped_blob ) - 1 ) / t_len ) + loop + utl_file.put_raw( t_fh, dbms_lob.substr( p_zipped_blob, t_len, i * t_len + 1 ) ); + end loop; + utl_file.fclose( t_fh ); + end; +-- +end; +/ \ No newline at end of file diff --git a/lib/as_zip.txt b/lib/as_zip.txt new file mode 100644 index 0000000..dc443d3 --- /dev/null +++ b/lib/as_zip.txt @@ -0,0 +1 @@ +https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/ \ No newline at end of file