diff --git a/README.md b/README.md index 4dfacac..458308c 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ SIGNATURE ```sql PACKAGE PLEX AUTHID current_user IS c_plex_name CONSTANT VARCHAR2(30 CHAR) := 'PLEX - PL/SQL Export Utilities'; -c_plex_version CONSTANT VARCHAR2(10 CHAR) := '2.1.0'; +c_plex_version CONSTANT VARCHAR2(10 CHAR) := '2.1.0.1'; c_plex_url CONSTANT VARCHAR2(40 CHAR) := 'https://github.com/ogobrecht/plex'; c_plex_license CONSTANT VARCHAR2(10 CHAR) := 'MIT'; c_plex_license_url CONSTANT VARCHAR2(60 CHAR) := 'https://github.com/ogobrecht/plex/blob/master/LICENSE.txt'; @@ -161,9 +161,9 @@ SELECT backapp FROM dual; EXAMPLE ZIP FILE SQL*Plus ```sql --- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB --- for spooling the content to the client disk. You need to decode the base64 --- encoded file before you are able to unzip the content. Also see this blog +-- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB +-- for spooling the content to the client disk. You need to decode the base64 +-- encoded file before you are able to unzip the content. Also see this blog -- post how to do this on the different operating systems: -- https://www.igorkromin.net/index.php/2017/04/26/base64-encode-or-decode-on-the-command-line-without-installing-extra-tools-on-linux-windows-or-macos/ -- Example Windows: certutil -decode app_100.zip.base64 app_100.zip @@ -184,7 +184,7 @@ END; / spool "app_100.zip.base64" print contents -spool off +spool off ``` SIGNATURE @@ -338,9 +338,9 @@ SELECT queries_to_csv_zip FROM dual; EXAMPLE ZIP FILE SQL*Plus ```sql --- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB --- for spooling the content to the client disk. You need to decode the base64 --- encoded file before you are able to unzip the content. Also see this blog +-- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB +-- for spooling the content to the client disk. You need to decode the base64 +-- encoded file before you are able to unzip the content. Also see this blog -- post how to do this on the different operating systems: -- https://www.igorkromin.net/index.php/2017/04/26/base64-encode-or-decode-on-the-command-line-without-installing-extra-tools-on-linux-windows-or-macos/ -- Example Windows: certutil -decode metadata.zip.base64 metadata.zip @@ -365,7 +365,7 @@ END; / spool "metadata.zip.base64" print contents -spool off +spool off ``` SIGNATURE diff --git a/package.json b/package.json index 9a02d20..d973e4e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "prebuild": "npx ploc --in src/PLEX.pks --out README.md", "build": "node src/build.js", - "postbuild": "echo exit | sqlplus -S demo/oracle@localhost:1521/xepdb1 @plex_install.sql", + "postbuild": "echo exit | sqlplus -S tests/oracle@localhost/xepdb1 @plex_install.sql", "watch": "chokidar src/PLEX.pks src/PLEX.pkb src/plex_install.sql --initial -c \"npm run build\"" }, "devDependencies": { diff --git a/plex_install.sql b/plex_install.sql index ecfb008..43edd9f 100644 --- a/plex_install.sql +++ b/plex_install.sql @@ -38,7 +38,7 @@ END; prompt Compile package plex (spec) CREATE OR REPLACE PACKAGE PLEX AUTHID current_user IS c_plex_name CONSTANT VARCHAR2(30 CHAR) := 'PLEX - PL/SQL Export Utilities'; -c_plex_version CONSTANT VARCHAR2(10 CHAR) := '2.1.0'; +c_plex_version CONSTANT VARCHAR2(10 CHAR) := '2.1.0.1'; c_plex_url CONSTANT VARCHAR2(40 CHAR) := 'https://github.com/ogobrecht/plex'; c_plex_license CONSTANT VARCHAR2(10 CHAR) := 'MIT'; c_plex_license_url CONSTANT VARCHAR2(60 CHAR) := 'https://github.com/ogobrecht/plex/blob/master/LICENSE.txt'; @@ -259,9 +259,9 @@ SELECT backapp FROM dual; EXAMPLE ZIP FILE SQL*Plus ```sql --- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB --- for spooling the content to the client disk. You need to decode the base64 --- encoded file before you are able to unzip the content. Also see this blog +-- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB +-- for spooling the content to the client disk. You need to decode the base64 +-- encoded file before you are able to unzip the content. Also see this blog -- post how to do this on the different operating systems: -- https://www.igorkromin.net/index.php/2017/04/26/base64-encode-or-decode-on-the-command-line-without-installing-extra-tools-on-linux-windows-or-macos/ -- Example Windows: certutil -decode app_100.zip.base64 app_100.zip @@ -282,7 +282,7 @@ END; {{/}} spool "app_100.zip.base64" print contents -spool off +spool off ``` **/ @@ -390,9 +390,9 @@ SELECT queries_to_csv_zip FROM dual; EXAMPLE ZIP FILE SQL*Plus ```sql --- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB --- for spooling the content to the client disk. You need to decode the base64 --- encoded file before you are able to unzip the content. Also see this blog +-- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB +-- for spooling the content to the client disk. You need to decode the base64 +-- encoded file before you are able to unzip the content. Also see this blog -- post how to do this on the different operating systems: -- https://www.igorkromin.net/index.php/2017/04/26/base64-encode-or-decode-on-the-command-line-without-installing-extra-tools-on-linux-windows-or-macos/ -- Example Windows: certutil -decode metadata.zip.base64 metadata.zip @@ -417,7 +417,7 @@ END; {{/}} spool "metadata.zip.base64" print contents -spool off +spool off ``` **/ @@ -563,7 +563,7 @@ PROCEDURE util_setup_dbms_metadata ( p_segment_attributes IN BOOLEAN DEFAULT false, p_sqlterminator IN BOOLEAN DEFAULT true, p_constraints_as_alter IN BOOLEAN DEFAULT false, - p_emit_schema IN BOOLEAN DEFAULT false); + p_emit_schema IN BOOLEAN DEFAULT false); PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files); @@ -607,6 +607,10 @@ PROCEDURE util_clob_query_to_csv ( p_quote_mark IN VARCHAR2 DEFAULT '"', p_header_prefix IN VARCHAR2 DEFAULT NULL); +PROCEDURE util_clob_table_to_forallinsert ( + p_table_name IN VARCHAR2, + p_max_rows IN NUMBER DEFAULT 1000); + PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files); PROCEDURE util_clob_create_runtime_log (p_export_files IN OUT NOCOPY tab_export_files); @@ -795,6 +799,10 @@ PROCEDURE util_clob_query_to_csv ( p_quote_mark IN VARCHAR2 DEFAULT '"', p_header_prefix IN VARCHAR2 DEFAULT NULL); +PROCEDURE util_clob_table_to_forallinsert ( + p_table_name IN VARCHAR2, + p_max_rows IN NUMBER DEFAULT 1000); + PROCEDURE util_clob_create_runtime_log (p_export_files IN OUT NOCOPY tab_export_files); PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files); @@ -1395,7 +1403,7 @@ IS -- inspired by Tim Hall: https://oracle-base.com/dba/script?category=miscellaneous&file=csv.sql v_line_terminator VARCHAR2(2) := c_crlf; -- to be compatible with Excel we need to use crlf here (multiline text uses lf and is wrapped in quotes) v_cursor PLS_INTEGER; - v_ignore_me PLS_INTEGER; + v_ignore_me PLS_INTEGER; v_data_count PLS_INTEGER := 0; v_col_cnt PLS_INTEGER; v_desc_tab dbms_sql.desc_tab3; @@ -1441,9 +1449,9 @@ IS replace(v_buffer_varchar2, c_crlf, c_lf), c_cr, c_lf); - -- if we have the parameter p_force_quotes set to true or the delimiter character or - -- line feeds in the string then we have to wrap the text in quotes marks and escape - -- the quote marks inside the text by double them + -- if we have the delimiter character or line feeds in the string then we + -- have to wrap the text in quotes marks and escape the quote marks + -- inside the text by double them IF instr(v_buffer_varchar2, p_delimiter) > 0 OR instr(v_buffer_varchar2, c_lf) > 0 THEN v_buffer_varchar2 := p_quote_mark || replace(v_buffer_varchar2, p_quote_mark, p_quote_mark || p_quote_mark) @@ -1540,6 +1548,158 @@ BEGIN END IF; END util_clob_query_to_csv; +PROCEDURE util_clob_table_to_forallinsert ( + p_table_name IN VARCHAR2, + p_max_rows IN NUMBER DEFAULT 1000) +IS + v_line_terminator VARCHAR2(2) := c_crlf; -- to be compatible with Excel we need to use crlf here (multiline text uses lf and is wrapped in quotes) + v_cursor PLS_INTEGER; + v_ignore_me PLS_INTEGER; + v_data_count PLS_INTEGER := 0; + v_col_cnt PLS_INTEGER; + v_desc_tab dbms_sql.desc_tab3; + v_buffer_varchar2 VARCHAR2(32767 CHAR); + v_buffer_clob CLOB; + v_buffer_xmltype XMLTYPE; + v_buffer_long LONG; + v_buffer_long_length PLS_INTEGER; + -- numeric type identfiers + c_number CONSTANT PLS_INTEGER := 2; -- FLOAT + c_binary_float CONSTANT PLS_INTEGER := 100; + c_binary_double CONSTANT PLS_INTEGER := 101; + -- string type identfiers + c_char CONSTANT PLS_INTEGER := 96; -- NCHAR + c_varchar2 CONSTANT PLS_INTEGER := 1; -- NVARCHAR2 + c_long CONSTANT PLS_INTEGER := 8; + c_clob CONSTANT PLS_INTEGER := 112; -- NCLOB + c_xmltype CONSTANT PLS_INTEGER := 109; -- ANYDATA, ANYDATASET, ANYTYPE, Object type, VARRAY, Nested table + c_rowid CONSTANT PLS_INTEGER := 11; + c_urowid CONSTANT PLS_INTEGER := 208; + -- binary type identfiers + c_raw CONSTANT PLS_INTEGER := 23; + c_long_raw CONSTANT PLS_INTEGER := 24; + c_blob CONSTANT PLS_INTEGER := 113; + c_bfile CONSTANT PLS_INTEGER := 114; + -- date type identfiers + c_date CONSTANT PLS_INTEGER := 12; + c_timestamp CONSTANT PLS_INTEGER := 180; + c_timestamp_with_time_zone CONSTANT PLS_INTEGER := 181; + c_timestamp_with_local_tz CONSTANT PLS_INTEGER := 231; + -- interval type identfiers + c_interval_year_to_month CONSTANT PLS_INTEGER := 182; + c_interval_day_to_second CONSTANT PLS_INTEGER := 183; + -- cursor type identfiers + c_ref CONSTANT PLS_INTEGER := 111; + c_ref_cursor CONSTANT PLS_INTEGER := 102; -- same identfiers for strong and weak ref cursor + +-------------------------------------------------------------------------------------------------------------------------------- + + PROCEDURE escape_varchar2_buffer_for_csv IS + BEGIN + NULL; + -- IF v_buffer_varchar2 IS NOT NULL THEN + -- -- normalize line feeds for Excel + -- v_buffer_varchar2 := replace( + -- replace(v_buffer_varchar2, c_crlf, c_lf), + -- c_cr, + -- c_lf); + -- -- if we have the delimiter character or line feeds in the string then we + -- -- have to wrap the text in quotes marks and escape the quote marks + -- -- inside the text by double them + -- IF instr(v_buffer_varchar2, p_delimiter) > 0 OR instr(v_buffer_varchar2, c_lf) > 0 THEN + -- v_buffer_varchar2 := p_quote_mark + -- || replace(v_buffer_varchar2, p_quote_mark, p_quote_mark || p_quote_mark) + -- || p_quote_mark; + -- END IF; + -- END IF; + EXCEPTION + WHEN value_error THEN + v_buffer_varchar2 := 'Value skipped - escaped text larger then ' || c_vc2_max_size || ' characters'; + END escape_varchar2_buffer_for_csv; + + FUNCTION get_table_pk_list RETURN VARCHAR2 IS + begin + return null; + end; + +BEGIN + IF p_table_name IS NOT NULL THEN + v_cursor := dbms_sql.open_cursor; + dbms_sql.parse( + v_cursor, + 'select * from ' || p_table_name || ' order by ' || get_table_pk_list, + dbms_sql.native); + -- https://support.esri.com/en/technical-article/000010110 + -- http://bluefrog-oracle.blogspot.com/2011/11/describing-ref-cursor-using-dbmssql-api.html + dbms_sql.describe_columns3(v_cursor, v_col_cnt, v_desc_tab); + FOR i IN 1..v_col_cnt LOOP + IF v_desc_tab(i).col_type = c_clob THEN + dbms_sql.define_column(v_cursor, i, v_buffer_clob); + ELSIF v_desc_tab(i).col_type = c_xmltype THEN + dbms_sql.define_column(v_cursor, i, v_buffer_xmltype); + ELSIF v_desc_tab(i).col_type = c_long THEN + dbms_sql.define_column_long(v_cursor, i); + ELSIF v_desc_tab(i).col_type IN (c_raw, c_long_raw, c_blob, c_bfile) THEN + NULL; --> we ignore binary data types + ELSE + dbms_sql.define_column(v_cursor, i, v_buffer_varchar2, c_vc2_max_size); + END IF; + END LOOP; + v_ignore_me := dbms_sql.execute(v_cursor); + FOR i IN 1..v_col_cnt LOOP + v_buffer_varchar2 := v_desc_tab(i).col_name; + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + END LOOP; + util_clob_append(v_line_terminator); + -- create data + LOOP + EXIT WHEN dbms_sql.fetch_rows(v_cursor) = 0 OR v_data_count = p_max_rows; + FOR i IN 1..v_col_cnt LOOP + IF v_desc_tab(i).col_type = c_clob THEN + dbms_sql.column_value(v_cursor, i, v_buffer_clob); + IF length(v_buffer_clob) <= c_vc2_max_size THEN + v_buffer_varchar2 := substr(v_buffer_clob, 1, c_vc2_max_size); + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + ELSE + v_buffer_varchar2 := 'CLOB value skipped - larger then ' || c_vc2_max_size || ' characters'; + util_clob_append(v_buffer_varchar2); + END IF; + ELSIF v_desc_tab(i).col_type = c_xmltype THEN + dbms_sql.column_value(v_cursor, i, v_buffer_xmltype); + v_buffer_clob := v_buffer_xmltype.getclobval(); + IF length(v_buffer_clob) <= c_vc2_max_size THEN + v_buffer_varchar2 := substr(v_buffer_clob, 1, c_vc2_max_size); + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + ELSE + v_buffer_varchar2 := 'XML value skipped - larger then ' || c_vc2_max_size || ' characters'; + util_clob_append(v_buffer_varchar2); + END IF; + ELSIF v_desc_tab(i).col_type = c_long THEN + dbms_sql.column_value_long(v_cursor, i, c_vc2_max_size, 0, v_buffer_varchar2, v_buffer_long_length); + IF v_buffer_long_length <= c_vc2_max_size THEN + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + ELSE + util_clob_append('LONG value skipped - larger then ' || c_vc2_max_size || ' characters'); + END IF; + ELSIF v_desc_tab(i).col_type IN (c_raw, c_long_raw, c_blob, c_bfile) THEN + util_clob_append('Binary data type skipped - not supported for CSV'); + ELSE + dbms_sql.column_value(v_cursor, i, v_buffer_varchar2); + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + END IF; + END LOOP; + util_clob_append(v_line_terminator); + v_data_count := v_data_count + 1; + END LOOP; + dbms_sql.close_cursor(v_cursor); + END IF; +END util_clob_table_to_forallinsert; + -------------------------------------------------------------------------------------------------------------------------------- PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files) IS @@ -1628,7 +1788,7 @@ FUNCTION backapp ( p_object_type_not_like IN VARCHAR2 DEFAULT NULL, p_object_name_like IN VARCHAR2 DEFAULT NULL, p_object_name_not_like IN VARCHAR2 DEFAULT NULL, - p_object_view_remove_col_list IN BOOLEAN DEFAULT true, + p_object_view_remove_col_list IN BOOLEAN DEFAULT true, p_include_data IN BOOLEAN DEFAULT false, p_data_as_of_minutes_ago IN NUMBER DEFAULT 0, p_data_max_rows IN NUMBER DEFAULT 1000, @@ -1662,7 +1822,7 @@ RETURN tab_export_files IS BEGIN RETURN 'prompt --' || replace(p_file_path, '.sql', NULL) || c_lf || '@' || '../' || p_file_path || c_lf || c_lf; - END util_get_script_line; + END util_get_script_line; PROCEDURE init IS BEGIN @@ -1866,7 +2026,7 @@ END; --source string dbms_metadata.get_granted_ddl('ROLE_GRANT', v_current_user), --replace all leading whitespace - '^\s*', NULL, 1, 0, 'm')); + '^\s*', NULL, 1, 0, 'm')); END LOOP; util_clob_add_to_export_files( p_export_files => v_export_files, @@ -1931,7 +2091,7 @@ END; file_path VARCHAR2(512)); v_rec obj_rec_typ; no_comments_found EXCEPTION; - PRAGMA EXCEPTION_INIT(no_comments_found, -31608); + PRAGMA EXCEPTION_INIT(no_comments_found, -31608); BEGIN util_log_start(p_base_path_backend || '/open_objects_cursor'); v_query := q'^ @@ -1964,7 +2124,7 @@ WITH t AS ( WHEN 'TYPE' THEN 'TYPE_SPEC' ELSE object_type END AS object_type, - CASE + CASE WHEN object_type like 'JAVA%' AND substr(object_name, 1, 1) = '/' THEN dbms_java.longname(object_name) ELSE @@ -2559,7 +2719,7 @@ if %errorlevel% neq 0 goto END del %zipfile%.base64 echo Unzip file %zipfile% >> %logfile% -echo Unzip file %zipfile% +echo Unzip file %zipfile% echo - For unzip details see %logfile% tar -xvf %zipfile% -C .. 2>> %logfile% if %errorlevel% neq 0 echo ERROR: Unable to unzip %zipfile% :-( >> %logfile% @@ -2580,7 +2740,7 @@ echo( :END rem Remove "pause" for fully automated setup: pause -if %errorlevel% neq 0 exit /b %errorlevel% +if %errorlevel% neq 0 exit /b %errorlevel% ^' ; v_file_path := 'scripts/templates/1_export_app_from_DEV.bat'; util_log_start(v_file_path); @@ -2601,7 +2761,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% END export_batch_file; PROCEDURE export_sq_file IS - BEGIN + BEGIN v_file_template := q'^/******************************************************************************* Template generated by PLEX version {{PLEX_VERSION}} More infos here: {{PLEX_URL}} @@ -2662,7 +2822,7 @@ BEGIN p_app_include_single_file => false, p_app_build_status_run_only => false,^'; $end - $if $$ords_installed $then + $if $$ords_installed $then v_file_template := v_file_template || q'^ p_include_ords_modules => false,^'; @@ -2717,7 +2877,7 @@ prompt END export_sq_file; PROCEDURE install_batch_files IS - BEGIN + BEGIN v_file_template := q'^rem Template generated by PLEX version {{PLEX_VERSION}} rem More infos here: {{PLEX_URL}} @@ -2803,7 +2963,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% END install_batch_files; PROCEDURE install_sql_file IS - BEGIN + BEGIN v_file_template := q'^/******************************************************************************* Template generated by PLEX version {{PLEX_VERSION}} More infos here: {{PLEX_URL}} @@ -2827,7 +2987,7 @@ Example call for Windows: *******************************************************************************/ -set timing on define on +set timing on define on timing start INSTALL_APP set timing off verify off feedback off whenever sqlerror exit sql.sqlcode rollback @@ -2846,7 +3006,7 @@ BEGIN END; {{/}} set define off - + prompt prompt Start Installation @@ -3104,7 +3264,7 @@ END to_zip; FUNCTION to_base64(p_blob IN BLOB) RETURN CLOB IS v_bas64 CLOB; v_step PLS_INTEGER := 14400; -- make sure you set a multiple of 3 not higher than 24573 - -- size of a whole multiple of 48 is beneficial to get NEW_LINE after each 64 characters + -- size of a whole multiple of 48 is beneficial to get NEW_LINE after each 64 characters BEGIN util_log_start('post processing with to_base64'); FOR i IN 0 .. TRUNC((DBMS_LOB.getlength(p_blob) - 1 ) / v_step) LOOP diff --git a/plex_uninstall.sql b/plex_uninstall.sql index f85d4e2..dbc5f51 100644 --- a/plex_uninstall.sql +++ b/plex_uninstall.sql @@ -1,5 +1,6 @@ -SET DEFINE OFF FEEDBACK OFF -WHENEVER SQLERROR EXIT sql.sqlcode ROLLBACK +set define off feedback off +whenever sqlerror exit sql.sqlcode rollback + prompt prompt Uninstalling PL/SQL Export Utilities prompt ==================================== diff --git a/src/PLEX.pkb b/src/PLEX.pkb index 9c29194..26cbd01 100644 --- a/src/PLEX.pkb +++ b/src/PLEX.pkb @@ -175,6 +175,10 @@ PROCEDURE util_clob_query_to_csv ( p_quote_mark IN VARCHAR2 DEFAULT '"', p_header_prefix IN VARCHAR2 DEFAULT NULL); +PROCEDURE util_clob_table_to_forallinsert ( + p_table_name IN VARCHAR2, + p_max_rows IN NUMBER DEFAULT 1000); + PROCEDURE util_clob_create_runtime_log (p_export_files IN OUT NOCOPY tab_export_files); PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files); @@ -775,7 +779,7 @@ IS -- inspired by Tim Hall: https://oracle-base.com/dba/script?category=miscellaneous&file=csv.sql v_line_terminator VARCHAR2(2) := c_crlf; -- to be compatible with Excel we need to use crlf here (multiline text uses lf and is wrapped in quotes) v_cursor PLS_INTEGER; - v_ignore_me PLS_INTEGER; + v_ignore_me PLS_INTEGER; v_data_count PLS_INTEGER := 0; v_col_cnt PLS_INTEGER; v_desc_tab dbms_sql.desc_tab3; @@ -821,9 +825,9 @@ IS replace(v_buffer_varchar2, c_crlf, c_lf), c_cr, c_lf); - -- if we have the parameter p_force_quotes set to true or the delimiter character or - -- line feeds in the string then we have to wrap the text in quotes marks and escape - -- the quote marks inside the text by double them + -- if we have the delimiter character or line feeds in the string then we + -- have to wrap the text in quotes marks and escape the quote marks + -- inside the text by double them IF instr(v_buffer_varchar2, p_delimiter) > 0 OR instr(v_buffer_varchar2, c_lf) > 0 THEN v_buffer_varchar2 := p_quote_mark || replace(v_buffer_varchar2, p_quote_mark, p_quote_mark || p_quote_mark) @@ -920,6 +924,158 @@ BEGIN END IF; END util_clob_query_to_csv; +PROCEDURE util_clob_table_to_forallinsert ( + p_table_name IN VARCHAR2, + p_max_rows IN NUMBER DEFAULT 1000) +IS + v_line_terminator VARCHAR2(2) := c_crlf; -- to be compatible with Excel we need to use crlf here (multiline text uses lf and is wrapped in quotes) + v_cursor PLS_INTEGER; + v_ignore_me PLS_INTEGER; + v_data_count PLS_INTEGER := 0; + v_col_cnt PLS_INTEGER; + v_desc_tab dbms_sql.desc_tab3; + v_buffer_varchar2 VARCHAR2(32767 CHAR); + v_buffer_clob CLOB; + v_buffer_xmltype XMLTYPE; + v_buffer_long LONG; + v_buffer_long_length PLS_INTEGER; + -- numeric type identfiers + c_number CONSTANT PLS_INTEGER := 2; -- FLOAT + c_binary_float CONSTANT PLS_INTEGER := 100; + c_binary_double CONSTANT PLS_INTEGER := 101; + -- string type identfiers + c_char CONSTANT PLS_INTEGER := 96; -- NCHAR + c_varchar2 CONSTANT PLS_INTEGER := 1; -- NVARCHAR2 + c_long CONSTANT PLS_INTEGER := 8; + c_clob CONSTANT PLS_INTEGER := 112; -- NCLOB + c_xmltype CONSTANT PLS_INTEGER := 109; -- ANYDATA, ANYDATASET, ANYTYPE, Object type, VARRAY, Nested table + c_rowid CONSTANT PLS_INTEGER := 11; + c_urowid CONSTANT PLS_INTEGER := 208; + -- binary type identfiers + c_raw CONSTANT PLS_INTEGER := 23; + c_long_raw CONSTANT PLS_INTEGER := 24; + c_blob CONSTANT PLS_INTEGER := 113; + c_bfile CONSTANT PLS_INTEGER := 114; + -- date type identfiers + c_date CONSTANT PLS_INTEGER := 12; + c_timestamp CONSTANT PLS_INTEGER := 180; + c_timestamp_with_time_zone CONSTANT PLS_INTEGER := 181; + c_timestamp_with_local_tz CONSTANT PLS_INTEGER := 231; + -- interval type identfiers + c_interval_year_to_month CONSTANT PLS_INTEGER := 182; + c_interval_day_to_second CONSTANT PLS_INTEGER := 183; + -- cursor type identfiers + c_ref CONSTANT PLS_INTEGER := 111; + c_ref_cursor CONSTANT PLS_INTEGER := 102; -- same identfiers for strong and weak ref cursor + +-------------------------------------------------------------------------------------------------------------------------------- + + PROCEDURE escape_varchar2_buffer_for_csv IS + BEGIN + NULL; + -- IF v_buffer_varchar2 IS NOT NULL THEN + -- -- normalize line feeds for Excel + -- v_buffer_varchar2 := replace( + -- replace(v_buffer_varchar2, c_crlf, c_lf), + -- c_cr, + -- c_lf); + -- -- if we have the delimiter character or line feeds in the string then we + -- -- have to wrap the text in quotes marks and escape the quote marks + -- -- inside the text by double them + -- IF instr(v_buffer_varchar2, p_delimiter) > 0 OR instr(v_buffer_varchar2, c_lf) > 0 THEN + -- v_buffer_varchar2 := p_quote_mark + -- || replace(v_buffer_varchar2, p_quote_mark, p_quote_mark || p_quote_mark) + -- || p_quote_mark; + -- END IF; + -- END IF; + EXCEPTION + WHEN value_error THEN + v_buffer_varchar2 := 'Value skipped - escaped text larger then ' || c_vc2_max_size || ' characters'; + END escape_varchar2_buffer_for_csv; + + FUNCTION get_table_pk_list RETURN VARCHAR2 IS + begin + return null; + end; + +BEGIN + IF p_table_name IS NOT NULL THEN + v_cursor := dbms_sql.open_cursor; + dbms_sql.parse( + v_cursor, + 'select * from ' || p_table_name || ' order by ' || get_table_pk_list, + dbms_sql.native); + -- https://support.esri.com/en/technical-article/000010110 + -- http://bluefrog-oracle.blogspot.com/2011/11/describing-ref-cursor-using-dbmssql-api.html + dbms_sql.describe_columns3(v_cursor, v_col_cnt, v_desc_tab); + FOR i IN 1..v_col_cnt LOOP + IF v_desc_tab(i).col_type = c_clob THEN + dbms_sql.define_column(v_cursor, i, v_buffer_clob); + ELSIF v_desc_tab(i).col_type = c_xmltype THEN + dbms_sql.define_column(v_cursor, i, v_buffer_xmltype); + ELSIF v_desc_tab(i).col_type = c_long THEN + dbms_sql.define_column_long(v_cursor, i); + ELSIF v_desc_tab(i).col_type IN (c_raw, c_long_raw, c_blob, c_bfile) THEN + NULL; --> we ignore binary data types + ELSE + dbms_sql.define_column(v_cursor, i, v_buffer_varchar2, c_vc2_max_size); + END IF; + END LOOP; + v_ignore_me := dbms_sql.execute(v_cursor); + FOR i IN 1..v_col_cnt LOOP + v_buffer_varchar2 := v_desc_tab(i).col_name; + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + END LOOP; + util_clob_append(v_line_terminator); + -- create data + LOOP + EXIT WHEN dbms_sql.fetch_rows(v_cursor) = 0 OR v_data_count = p_max_rows; + FOR i IN 1..v_col_cnt LOOP + IF v_desc_tab(i).col_type = c_clob THEN + dbms_sql.column_value(v_cursor, i, v_buffer_clob); + IF length(v_buffer_clob) <= c_vc2_max_size THEN + v_buffer_varchar2 := substr(v_buffer_clob, 1, c_vc2_max_size); + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + ELSE + v_buffer_varchar2 := 'CLOB value skipped - larger then ' || c_vc2_max_size || ' characters'; + util_clob_append(v_buffer_varchar2); + END IF; + ELSIF v_desc_tab(i).col_type = c_xmltype THEN + dbms_sql.column_value(v_cursor, i, v_buffer_xmltype); + v_buffer_clob := v_buffer_xmltype.getclobval(); + IF length(v_buffer_clob) <= c_vc2_max_size THEN + v_buffer_varchar2 := substr(v_buffer_clob, 1, c_vc2_max_size); + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + ELSE + v_buffer_varchar2 := 'XML value skipped - larger then ' || c_vc2_max_size || ' characters'; + util_clob_append(v_buffer_varchar2); + END IF; + ELSIF v_desc_tab(i).col_type = c_long THEN + dbms_sql.column_value_long(v_cursor, i, c_vc2_max_size, 0, v_buffer_varchar2, v_buffer_long_length); + IF v_buffer_long_length <= c_vc2_max_size THEN + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + ELSE + util_clob_append('LONG value skipped - larger then ' || c_vc2_max_size || ' characters'); + END IF; + ELSIF v_desc_tab(i).col_type IN (c_raw, c_long_raw, c_blob, c_bfile) THEN + util_clob_append('Binary data type skipped - not supported for CSV'); + ELSE + dbms_sql.column_value(v_cursor, i, v_buffer_varchar2); + escape_varchar2_buffer_for_csv; + util_clob_append(v_buffer_varchar2); + END IF; + END LOOP; + util_clob_append(v_line_terminator); + v_data_count := v_data_count + 1; + END LOOP; + dbms_sql.close_cursor(v_cursor); + END IF; +END util_clob_table_to_forallinsert; + -------------------------------------------------------------------------------------------------------------------------------- PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files) IS @@ -1008,7 +1164,7 @@ FUNCTION backapp ( p_object_type_not_like IN VARCHAR2 DEFAULT NULL, p_object_name_like IN VARCHAR2 DEFAULT NULL, p_object_name_not_like IN VARCHAR2 DEFAULT NULL, - p_object_view_remove_col_list IN BOOLEAN DEFAULT true, + p_object_view_remove_col_list IN BOOLEAN DEFAULT true, p_include_data IN BOOLEAN DEFAULT false, p_data_as_of_minutes_ago IN NUMBER DEFAULT 0, p_data_max_rows IN NUMBER DEFAULT 1000, @@ -1042,7 +1198,7 @@ RETURN tab_export_files IS BEGIN RETURN 'prompt --' || replace(p_file_path, '.sql', NULL) || c_lf || '@' || '../' || p_file_path || c_lf || c_lf; - END util_get_script_line; + END util_get_script_line; PROCEDURE init IS BEGIN @@ -1246,7 +1402,7 @@ END; --source string dbms_metadata.get_granted_ddl('ROLE_GRANT', v_current_user), --replace all leading whitespace - '^\s*', NULL, 1, 0, 'm')); + '^\s*', NULL, 1, 0, 'm')); END LOOP; util_clob_add_to_export_files( p_export_files => v_export_files, @@ -1311,7 +1467,7 @@ END; file_path VARCHAR2(512)); v_rec obj_rec_typ; no_comments_found EXCEPTION; - PRAGMA EXCEPTION_INIT(no_comments_found, -31608); + PRAGMA EXCEPTION_INIT(no_comments_found, -31608); BEGIN util_log_start(p_base_path_backend || '/open_objects_cursor'); v_query := q'^ @@ -1344,7 +1500,7 @@ WITH t AS ( WHEN 'TYPE' THEN 'TYPE_SPEC' ELSE object_type END AS object_type, - CASE + CASE WHEN object_type like 'JAVA%' AND substr(object_name, 1, 1) = '/' THEN dbms_java.longname(object_name) ELSE @@ -1939,7 +2095,7 @@ if %errorlevel% neq 0 goto END del %zipfile%.base64 echo Unzip file %zipfile% >> %logfile% -echo Unzip file %zipfile% +echo Unzip file %zipfile% echo - For unzip details see %logfile% tar -xvf %zipfile% -C .. 2>> %logfile% if %errorlevel% neq 0 echo ERROR: Unable to unzip %zipfile% :-( >> %logfile% @@ -1960,7 +2116,7 @@ echo( :END rem Remove "pause" for fully automated setup: pause -if %errorlevel% neq 0 exit /b %errorlevel% +if %errorlevel% neq 0 exit /b %errorlevel% ^' ; v_file_path := 'scripts/templates/1_export_app_from_DEV.bat'; util_log_start(v_file_path); @@ -1981,7 +2137,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% END export_batch_file; PROCEDURE export_sq_file IS - BEGIN + BEGIN v_file_template := q'^/******************************************************************************* Template generated by PLEX version {{PLEX_VERSION}} More infos here: {{PLEX_URL}} @@ -2042,7 +2198,7 @@ BEGIN p_app_include_single_file => false, p_app_build_status_run_only => false,^'; $end - $if $$ords_installed $then + $if $$ords_installed $then v_file_template := v_file_template || q'^ p_include_ords_modules => false,^'; @@ -2097,7 +2253,7 @@ prompt END export_sq_file; PROCEDURE install_batch_files IS - BEGIN + BEGIN v_file_template := q'^rem Template generated by PLEX version {{PLEX_VERSION}} rem More infos here: {{PLEX_URL}} @@ -2183,7 +2339,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% END install_batch_files; PROCEDURE install_sql_file IS - BEGIN + BEGIN v_file_template := q'^/******************************************************************************* Template generated by PLEX version {{PLEX_VERSION}} More infos here: {{PLEX_URL}} @@ -2207,7 +2363,7 @@ Example call for Windows: *******************************************************************************/ -set timing on define on +set timing on define on timing start INSTALL_APP set timing off verify off feedback off whenever sqlerror exit sql.sqlcode rollback @@ -2226,7 +2382,7 @@ BEGIN END; {{/}} set define off - + prompt prompt Start Installation @@ -2484,7 +2640,7 @@ END to_zip; FUNCTION to_base64(p_blob IN BLOB) RETURN CLOB IS v_bas64 CLOB; v_step PLS_INTEGER := 14400; -- make sure you set a multiple of 3 not higher than 24573 - -- size of a whole multiple of 48 is beneficial to get NEW_LINE after each 64 characters + -- size of a whole multiple of 48 is beneficial to get NEW_LINE after each 64 characters BEGIN util_log_start('post processing with to_base64'); FOR i IN 0 .. TRUNC((DBMS_LOB.getlength(p_blob) - 1 ) / v_step) LOOP diff --git a/src/PLEX.pks b/src/PLEX.pks index 3ba451e..d415e90 100644 --- a/src/PLEX.pks +++ b/src/PLEX.pks @@ -1,6 +1,6 @@ CREATE OR REPLACE PACKAGE PLEX AUTHID current_user IS c_plex_name CONSTANT VARCHAR2(30 CHAR) := 'PLEX - PL/SQL Export Utilities'; -c_plex_version CONSTANT VARCHAR2(10 CHAR) := '2.1.0'; +c_plex_version CONSTANT VARCHAR2(10 CHAR) := '2.1.0.1'; c_plex_url CONSTANT VARCHAR2(40 CHAR) := 'https://github.com/ogobrecht/plex'; c_plex_license CONSTANT VARCHAR2(10 CHAR) := 'MIT'; c_plex_license_url CONSTANT VARCHAR2(60 CHAR) := 'https://github.com/ogobrecht/plex/blob/master/LICENSE.txt'; @@ -221,9 +221,9 @@ SELECT backapp FROM dual; EXAMPLE ZIP FILE SQL*Plus ```sql --- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB --- for spooling the content to the client disk. You need to decode the base64 --- encoded file before you are able to unzip the content. Also see this blog +-- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB +-- for spooling the content to the client disk. You need to decode the base64 +-- encoded file before you are able to unzip the content. Also see this blog -- post how to do this on the different operating systems: -- https://www.igorkromin.net/index.php/2017/04/26/base64-encode-or-decode-on-the-command-line-without-installing-extra-tools-on-linux-windows-or-macos/ -- Example Windows: certutil -decode app_100.zip.base64 app_100.zip @@ -244,7 +244,7 @@ END; {{/}} spool "app_100.zip.base64" print contents -spool off +spool off ``` **/ @@ -352,9 +352,9 @@ SELECT queries_to_csv_zip FROM dual; EXAMPLE ZIP FILE SQL*Plus ```sql --- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB --- for spooling the content to the client disk. You need to decode the base64 --- encoded file before you are able to unzip the content. Also see this blog +-- SQL*Plus can only handle CLOBs, no BLOBs - so we are forced to create a CLOB +-- for spooling the content to the client disk. You need to decode the base64 +-- encoded file before you are able to unzip the content. Also see this blog -- post how to do this on the different operating systems: -- https://www.igorkromin.net/index.php/2017/04/26/base64-encode-or-decode-on-the-command-line-without-installing-extra-tools-on-linux-windows-or-macos/ -- Example Windows: certutil -decode metadata.zip.base64 metadata.zip @@ -379,7 +379,7 @@ END; {{/}} spool "metadata.zip.base64" print contents -spool off +spool off ``` **/ @@ -525,7 +525,7 @@ PROCEDURE util_setup_dbms_metadata ( p_segment_attributes IN BOOLEAN DEFAULT false, p_sqlterminator IN BOOLEAN DEFAULT true, p_constraints_as_alter IN BOOLEAN DEFAULT false, - p_emit_schema IN BOOLEAN DEFAULT false); + p_emit_schema IN BOOLEAN DEFAULT false); PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files); @@ -569,6 +569,10 @@ PROCEDURE util_clob_query_to_csv ( p_quote_mark IN VARCHAR2 DEFAULT '"', p_header_prefix IN VARCHAR2 DEFAULT NULL); +PROCEDURE util_clob_table_to_forallinsert ( + p_table_name IN VARCHAR2, + p_max_rows IN NUMBER DEFAULT 1000); + PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files); PROCEDURE util_clob_create_runtime_log (p_export_files IN OUT NOCOPY tab_export_files); diff --git a/src/plex_uninstall.sql b/src/plex_uninstall.sql index f85d4e2..dbc5f51 100644 --- a/src/plex_uninstall.sql +++ b/src/plex_uninstall.sql @@ -1,5 +1,6 @@ -SET DEFINE OFF FEEDBACK OFF -WHENEVER SQLERROR EXIT sql.sqlcode ROLLBACK +set define off feedback off +whenever sqlerror exit sql.sqlcode rollback + prompt prompt Uninstalling PL/SQL Export Utilities prompt ====================================