diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..9c76173
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.sql linguist-language=PLSQL
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b904bf3..0e2217d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,3 @@
-node_modules
-temp
-demo.sql
-L_XE*.sql
*.zip
-plex_install_SYS.bat
-plex_install_APEX_190100.bat
+node_modules
+test_export
diff --git a/README.md b/README.md
index 8360038..4dfacac 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,24 @@
-
-
+
+
PL/SQL Export Utilities
-=======================
+=======================
+
+- [Package PLEX](#plex)
+- [Function backapp](#backapp)
+- [Procedure add_query](#add_query)
+- [Function queries_to_csv](#queries_to_csv)
+- [Function to_zip](#to_zip)
+- [Function to_base64](#to_base64)
+- [Function view_error_log](#view_error_log)
+- [Function view_runtime_log](#view_runtime_log)
+
+
+
Package PLEX
+
+
+PLEX was created to be able to quickstart version control for existing Oracle DB projects and has currently two main functions called **BackApp** and **Queries_to_CSV**. Queries_to_CSV is used by BackApp as a helper function, but its functionality is also useful standalone.
-- [Package PLEX](#plex)
-- [Function backapp](#backapp)
-- [Procedure add_query](#add_query)
-- [Function queries_to_csv](#queries_to_csv)
-- [Function to_zip](#to_zip)
-- [Function view_error_log](#view_error_log)
-- [Function view_runtime_log](#view_runtime_log)
-
-
-Package PLEX
-
-
-PLEX was created to be able to quickstart version control for existing (APEX) apps and has currently two main functions called **BackApp** and **Queries_to_CSV**. Queries_to_CSV is used by BackApp as a helper function, but its functionality is also useful standalone.
-
-See also this resources for more information:
+Also see this resources for more information:
- [Blog post on how to getting started](https://ogobrecht.github.io/posts/2018-08-26-plex-plsql-export-utilities)
- [PLEX project page on GitHub](https://github.com/ogobrecht/plex)
@@ -29,7 +30,8 @@ DEPENDENCIES
The package itself is independend, but functionality varies on the following conditions:
- For APEX app export: APEX >= 5.1.4 installed
-- NOT YET IMPLEMENTED: For ORDS REST service export: ORDS >= FIXME installed
+- For ORDS modules export: ORDS >= 18.3 installed (I think package ords_export is included since this version, but I don't know it)
+ - ATTENTION: There seems to be a bug in ORDS 19.2 which prevents you to export ORDS modules via the package ords_export: https://community.oracle.com/thread/4292776; please see plex_error_log.md, if you miss your ORDS modules after an export - this is no problem of PLEX
INSTALLATION
@@ -43,6 +45,16 @@ INSTALLATION
CHANGELOG
+- 2.1.0 (2019-12-30)
+ - Function BackApp:
+ - New parameter to include ORDS modules (p_include_ords_modules)
+ - New parameter to remove the outer column list on views, which is added by the compiler (p_object_view_remove_col_list); this was done in the past implicitly and can now be switched off; thanks to twitter.com/JKaschuba for the hint
+ - Object DDL: Comments for tables and views are now included
+ - Script templates: Improved export speed by using a base64 encoded zip file instead of a global temporary table to unload the files
+ - Fixed: Unable to export JAVA objects on systems with 30 character object names; thanks to twitter.com/JKaschuba for the hint
+ - Fixed: Views appears two times in resulting collection, each double file is postfixed with "_2" and empty
+ - Fixed: Tables and indices of materialized view definitions are exported (should be hidden)
+ - New function to_base64: convert BLOB into base64 encoded CLOB - this is helpful to download a BLOB file (like a zip file) with SQL*Plus
- 2.0.2 (2019-08-16)
- Fixed: Function BackApp throws error on large APEX UI install files (ORA-06502: PL/SQL: numeric or value error: character string buffer too small)
- 2.0.1 (2019-07-09)
@@ -50,35 +62,36 @@ CHANGELOG
- 2.0.0 (2019-06-20)
- Package is now independend from APEX to be able to export schema object DDL and table data without an APEX installation
- ATTENTION: The return type of functions BackApp and Queries_to_CSV has changed from `apex_t_export_files` to `plex.tab_export_files`
- - New parameters to filter for object types
- - New parameters to change base paths for backend, frontend and data
+ - Function BackApp:
+ - New parameters to filter for object types
+ - New parameters to change base paths for backend, frontend and data
- 1.2.1 (2019-03-13)
- - Fix script templates: Change old parameters in plex.backapp call
+ - Fixed: Script templates for function BackApp used old/invalid parameters
- Add install and uninstall scripts for PLEX itself
- 1.2.0 (2018-10-31)
- - New: All like/not like parameters are now translated internally with the escape character set to backslash like so `... like 'YourExpression' escape '\'`
- - Fixed: Binary data type columns (raw, long_raw, blob, bfile) should no longer break the export data to CSV functionality
+ - Function BackApp: All like/not like parameters are now translated internally with the escape character set to backslash like so `... like 'YourExpression' escape '\'`
+ - Function Queries_to_CSV: Binary data type columns (raw, long_raw, blob, bfile) should no longer break the export
- 1.1.0 (2018-09-23)
- - Change filter parameter from regular expression to list of like expressions for easier handling
+ - Function BackApp: Change filter parameter from regular expression to list of like expressions for easier handling
- 1.0.0 (2018-08-26)
- - First public release
-
-SIGNATURE
-
-```sql
+ - First public release
+
+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.0.2';
+c_plex_version CONSTANT VARCHAR2(10 CHAR) := '2.1.0';
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';
-c_plex_author CONSTANT VARCHAR2(20 CHAR) := 'Ottmar Gobrecht';
-```
-
-
-Function backapp
-
-
+c_plex_author CONSTANT VARCHAR2(20 CHAR) := 'Ottmar Gobrecht';
+```
+
+
+Function backapp
+
+
Get a file collection of an APEX application (or the current user/schema only) including:
- The app export SQL files splitted ready to use for version control and deployment
@@ -93,9 +106,11 @@ DECLARE
l_file_collection plex.tab_export_files;
BEGIN
l_file_collection := plex.backapp(
- p_app_id => 100, -- parameter only available when APEX installed
- p_include_object_ddl => false,
- p_include_data => false);
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => false,
+ p_include_data => false,
+ p_include_templates => false);
-- do something with the file collection
FOR i IN 1..l_file_collection.count LOOP
@@ -114,9 +129,11 @@ DECLARE
l_zip_file BLOB;
BEGIN
l_zip_file := plex.to_zip(plex.backapp(
- p_app_id => 100, -- parameter only available when APEX installed
- p_include_object_ddl => true,
- p_include_data => false));
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true));
-- do something with the zip file
-- Your code here...
END;
@@ -132,90 +149,96 @@ WITH
FUNCTION backapp RETURN BLOB IS
BEGIN
RETURN plex.to_zip(plex.backapp(
- -- All parameters are optional and shown with their defaults
- -- App related options (only available, when APEX is installed):
- p_app_id => NULL,
- p_app_date => true,
- p_app_public_reports => true,
- p_app_private_reports => false,
- p_app_notifications => false,
- p_app_translations => true,
- p_app_pkg_app_mapping => false,
- p_app_original_ids => false,
- p_app_subscriptions => true,
- p_app_comments => true,
- p_app_supporting_objects => NULL,
- p_app_include_single_file => false,
- p_app_build_status_run_only => false,
- -- Object related options:
- p_include_object_ddl => false,
- p_object_type_like => NULL,
- p_object_type_not_like => NULL,
- p_object_name_like => NULL,
- p_object_name_not_like => NULL,
- -- Data related options:
- p_include_data => false,
- p_data_as_of_minutes_ago => 0,
- p_data_max_rows => 1000,
- p_data_table_name_like => NULL,
- p_data_table_name_not_like => NULL,
- -- Miscellaneous options:
- p_include_templates => true,
- p_include_runtime_log => true,
- p_include_error_log => true,
- p_base_path_backend => 'app_backend',
- p_base_path_frontend => 'app_frontend',
- p_base_path_data => 'app_data'));
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true));
END backapp;
SELECT backapp FROM dual;
```
-SIGNATURE
+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
+-- 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
+-- Example Mac: base64 -D -i app_100.zip.base64 -o app_100.zip
+-- Example Linux: base64 -d app_100.zip.base64 > app_100.zip
+set verify off feedback off heading off termout off
+set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
+whenever sqlerror exit sql.sqlcode rollback
+variable contents clob
+BEGIN
+ :contents := plex.to_base64(plex.to_zip(plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true)));
+END;
+/
+spool "app_100.zip.base64"
+print contents
+spool off
+```
+
+SIGNATURE
+
+```sql
FUNCTION backapp (
$if $$apex_installed $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.
- p_app_public_reports IN BOOLEAN DEFAULT true, -- If true, include public reports that a user saved.
- p_app_private_reports IN BOOLEAN DEFAULT false, -- If true, include private reports that a user saved.
- p_app_notifications IN BOOLEAN DEFAULT false, -- If true, include report notifications.
- p_app_translations IN BOOLEAN DEFAULT true, -- If true, include application translation mappings and all text from the translation repository.
- p_app_pkg_app_mapping IN BOOLEAN DEFAULT false, -- If true, export installed packaged applications with references to the packaged application definition. If FALSE, export them as normal applications.
- p_app_original_ids IN BOOLEAN DEFAULT false, -- If true, export with the IDs as they were when the application was imported.
- p_app_subscriptions IN BOOLEAN DEFAULT true, -- If true, components contain subscription references.
- p_app_comments IN BOOLEAN DEFAULT true, -- If true, include developer comments.
- 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.
+ -- APEX App:
+ 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.
+ p_app_public_reports IN BOOLEAN DEFAULT true, -- If true, include public reports that a user saved.
+ p_app_private_reports IN BOOLEAN DEFAULT false, -- If true, include private reports that a user saved.
+ p_app_notifications IN BOOLEAN DEFAULT false, -- If true, include report notifications.
+ p_app_translations IN BOOLEAN DEFAULT true, -- If true, include application translation mappings and all text from the translation repository.
+ p_app_pkg_app_mapping IN BOOLEAN DEFAULT false, -- If true, export installed packaged applications with references to the packaged application definition. If FALSE, export them as normal applications.
+ p_app_original_ids IN BOOLEAN DEFAULT false, -- If true, export with the IDs as they were when the application was imported.
+ p_app_subscriptions IN BOOLEAN DEFAULT true, -- If true, components contain subscription references.
+ p_app_comments IN BOOLEAN DEFAULT true, -- If true, include developer comments.
+ 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_type_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type like '%BODY' escape '\' or object_type like 'JAVA%' escape '\').
- p_object_type_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type not like '%BODY' escape '\' and object_type not like 'JAVA%' escape '\').
- 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: ... from user_objects where ... and (object_name like 'EMP%' escape '\' or object_name like 'DEPT%' escape '\').
- p_object_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: 'EMP%,DEPT%' will be translated to: ... from user_objects where ... and (object_name not like 'EMP%' escape '\' and object_name not like 'DEPT%' escape '\').
- -- Data related options:
- p_include_data IN BOOLEAN DEFAULT false, -- If true, include CSV data of each table.
- p_data_as_of_minutes_ago IN NUMBER DEFAULT 0, -- Read consistent data with the resulting timestamp(SCN).
- p_data_max_rows IN NUMBER DEFAULT 1000, -- Maximum number of rows per table.
- p_data_table_name_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name like 'EMP%' escape '\' or table_name like 'DEPT%' escape '\').
- p_data_table_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name not like 'EMP%' escape '\' and table_name not like 'DEPT%' escape '\').
- -- Miscellaneous options:
- p_include_templates IN BOOLEAN DEFAULT true, -- If true, include templates for README.md, export and install scripts.
- p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with detailed runtime infos.
- p_include_error_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_error_log.md with detailed error messages.
- p_base_path_backend IN VARCHAR2 DEFAULT 'app_backend', -- The base path in the project root for the database DDL files.
- p_base_path_frontend IN VARCHAR2 DEFAULT 'app_frontend', -- The base path in the project root for the APEX UI install files.
- p_base_path_data IN VARCHAR2 DEFAULT 'app_data') -- The base path in the project root for the data files.
-RETURN tab_export_files;
-```
-
-
-Procedure add_query
-
-
+ $if $$ords_installed $then
+ -- ORDS Modules:
+ p_include_ords_modules IN BOOLEAN DEFAULT false, -- If true, include ORDS modules of current user/schema.
+ $end
+ -- Schema Objects:
+ p_include_object_ddl IN BOOLEAN DEFAULT false, -- If true, include DDL of current user/schema and all its objects.
+ p_object_type_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type like '%BODY' escape '\' or object_type like 'JAVA%' escape '\').
+ p_object_type_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type not like '%BODY' escape '\' and object_type not like 'JAVA%' escape '\').
+ 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: ... from user_objects where ... and (object_name like 'EMP%' escape '\' or object_name like 'DEPT%' escape '\').
+ p_object_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: 'EMP%,DEPT%' will be translated to: ... from user_objects where ... and (object_name not like 'EMP%' escape '\' and object_name not like 'DEPT%' escape '\').
+ p_object_view_remove_col_list IN BOOLEAN DEFAULT true, -- If true, the outer column list, added by Oracle on views during compilation, is removed
+ -- Table Data:
+ p_include_data IN BOOLEAN DEFAULT false, -- If true, include CSV data of each table.
+ p_data_as_of_minutes_ago IN NUMBER DEFAULT 0, -- Read consistent data with the resulting timestamp(SCN).
+ p_data_max_rows IN NUMBER DEFAULT 1000, -- Maximum number of rows per table.
+ p_data_table_name_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name like 'EMP%' escape '\' or table_name like 'DEPT%' escape '\').
+ p_data_table_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name not like 'EMP%' escape '\' and table_name not like 'DEPT%' escape '\').
+ -- General Options:
+ p_include_templates IN BOOLEAN DEFAULT true, -- If true, include templates for README.md, export and install scripts.
+ p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with detailed runtime infos.
+ p_include_error_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_error_log.md with detailed error messages.
+ p_base_path_backend IN VARCHAR2 DEFAULT 'app_backend', -- The base path in the project root for the Schema objects.
+ p_base_path_frontend IN VARCHAR2 DEFAULT 'app_frontend', -- The base path in the project root for the APEX app.
+ p_base_path_web_services IN VARCHAR2 DEFAULT 'app_web_services', -- The base path in the project root for the ORDS modules.
+ p_base_path_data IN VARCHAR2 DEFAULT 'app_data') -- The base path in the project root for the table data.
+RETURN tab_export_files;
+```
+
+
+Procedure add_query
+
+
Add a query to be processed by the method queries_to_csv. You can add as many queries as you like.
EXAMPLE
@@ -227,21 +250,21 @@ BEGIN
p_file_name => 'user_tables');
END;
/
-```
-
-SIGNATURE
-
-```sql
+```
+
+SIGNATURE
+
+```sql
PROCEDURE add_query (
p_query IN VARCHAR2, -- The query itself
p_file_name IN VARCHAR2, -- File name like 'Path/to/your/file-without-extension'.
- p_max_rows IN NUMBER DEFAULT 1000); -- The maximum number of rows to be included in your file.
-```
-
-
-Function queries_to_csv
-
-
+ p_max_rows IN NUMBER DEFAULT 1000); -- The maximum number of rows to be included in your file.
+```
+
+
+Function queries_to_csv
+
+
Export one or more queries as CSV data within a file collection.
EXAMPLE BASIC USAGE
@@ -270,7 +293,7 @@ END;
/
```
-EXPORT EXPORT ZIP FILE PL/SQL
+EXAMPLE EXPORT ZIP FILE PL/SQL
```sql
DECLARE
@@ -312,22 +335,55 @@ WITH
SELECT queries_to_csv_zip FROM dual;
```
-SIGNATURE
+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
+-- 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
+-- Example Mac: base64 -D -i metadata.zip.base64 -o metadata.zip
+-- Example Linux: base64 -d metadata.zip.base64 > metadata.zip
+set verify off feedback off heading off termout off
+set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
+whenever sqlerror exit sql.sqlcode rollback
+variable contents clob
+BEGIN
+ --fill the queries array
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ -- process the queries
+ :contents := plex.to_base64(plex.to_zip(plex.queries_to_csv));
+END;
+/
+spool "metadata.zip.base64"
+print contents
+spool off
+```
+
+SIGNATURE
+
+```sql
FUNCTION queries_to_csv (
p_delimiter IN VARCHAR2 DEFAULT ',', -- The column delimiter.
p_quote_mark IN VARCHAR2 DEFAULT '"', -- Used when the data contains the delimiter character.
p_header_prefix IN VARCHAR2 DEFAULT NULL, -- Prefix the header line with this text.
p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with runtime statistics.
p_include_error_log IN BOOLEAN DEFAULT true) -- If true, generate file plex_error_log.md with detailed error messages.
-RETURN tab_export_files;
-```
-
-
-Function to_zip
-
-
+RETURN tab_export_files;
+```
+
+
+Function to_zip
+
+
Convert a file collection to a zip file.
EXAMPLE
@@ -341,50 +397,75 @@ BEGIN
p_include_object_ddl => true));
-- do something with the zip file...
END;
-```
-
-SIGNATURE
-
-```sql
+```
+
+SIGNATURE
+
+```sql
FUNCTION to_zip (
p_file_collection IN tab_export_files) -- The file collection to zip.
-RETURN BLOB;
-```
-
-
-Function view_error_log
-
+RETURN BLOB;
+```
+
+
+Function to_base64
+
+
+Encodes a BLOB into a Base64 CLOB for transfers over a network (like with SQL*Plus). For encoding on the client side see [this blog article](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/).
+```sql
+DECLARE
+ l_clob CLOB;
+BEGIN
+ l_clob := plex.to_base64(plex.to_zip(plex.backapp(
+ p_app_id => 100,
+ p_include_object_ddl => true)));
+ -- do something with the clob...
+END;
+```
+
+SIGNATURE
+
+```sql
+FUNCTION to_base64(
+ p_blob IN BLOB) -- The BLOB to convert.
+RETURN CLOB;
+```
+
+
+Function view_error_log
+
+
View the error log from the last plex run. The internal array for the error log is cleared on each call of BackApp or Queries_to_CSV.
EXAMPLE
```sql
SELECT * FROM TABLE(plex.view_error_log);
-```
-
-SIGNATURE
-
-```sql
-FUNCTION view_error_log RETURN tab_error_log PIPELINED;
-```
-
-
-Function view_runtime_log
-
-
+```
+
+SIGNATURE
+
+```sql
+FUNCTION view_error_log RETURN tab_error_log PIPELINED;
+```
+
+
+Function view_runtime_log
+
+
View the runtime log from the last plex run. The internal array for the runtime log is cleared on each call of BackApp or Queries_to_CSV.
EXAMPLE
```sql
SELECT * FROM TABLE(plex.view_runtime_log);
-```
-
-SIGNATURE
-
-```sql
-FUNCTION view_runtime_log RETURN tab_runtime_log PIPELINED;
-```
-
-
+```
+
+SIGNATURE
+
+```sql
+FUNCTION view_runtime_log RETURN tab_runtime_log PIPELINED;
+```
+
+
diff --git a/package-lock.json b/package-lock.json
index e94149b..8e831d6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -542,7 +542,8 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"aproba": {
"version": "1.2.0",
@@ -560,11 +561,13 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
+ "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -577,15 +580,18 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -688,7 +694,8 @@
},
"inherits": {
"version": "2.0.3",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"ini": {
"version": "1.3.5",
@@ -698,6 +705,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
+ "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -710,17 +718,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
+ "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
+ "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -737,6 +748,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
+ "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -809,7 +821,8 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -819,6 +832,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
+ "optional": true,
"requires": {
"wrappy": "1"
}
@@ -894,7 +908,8 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -924,6 +939,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
+ "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -941,6 +957,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
+ "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -979,11 +996,13 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true
+ "bundled": true,
+ "optional": true
},
"yallist": {
"version": "3.0.3",
- "bundled": true
+ "bundled": true,
+ "optional": true
}
}
},
@@ -1327,9 +1346,9 @@
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"mixin-deep": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
- "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"requires": {
"for-in": "^1.0.2",
"is-extendable": "^1.0.1"
@@ -1640,9 +1659,9 @@
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"set-value": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
- "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"requires": {
"extend-shallow": "^2.0.1",
"is-extendable": "^0.1.1",
@@ -1893,35 +1912,14 @@
}
},
"union-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
- "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"requires": {
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
- "set-value": "^0.4.3"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "requires": {
- "is-extendable": "^0.1.0"
- }
- },
- "set-value": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
- "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.1",
- "to-object-path": "^0.3.0"
- }
- }
+ "set-value": "^2.0.1"
}
},
"unset-value": {
diff --git a/package.json b/package.json
index 8964fd0..69aafc5 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,10 @@
"url": "https://github.com/ogobrecht/plex.git"
},
"scripts": {
- "build:docs": "npx ploc --in PLEX.pks --out README.md",
- "watch:docs": "chokidar PLEX.pks package.json --initial -c \"npm run build:docs\""
+ "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",
+ "watch": "chokidar src/PLEX.pks src/PLEX.pkb src/plex_install.sql --initial -c \"npm run build\""
},
"dependencies": {
"chokidar-cli": "^1.2.2",
diff --git a/plex_install.sql b/plex_install.sql
index 8c235a0..a76d2af 100644
--- a/plex_install.sql
+++ b/plex_install.sql
@@ -1,8 +1,10 @@
-SET DEFINE OFF FEEDBACK OFF
-WHENEVER SQLERROR EXIT sql.sqlcode ROLLBACK
+set define off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+
prompt
prompt Installing PL/SQL Export Utilities
prompt ==================================
+
prompt Set compiler flags
DECLARE
v_apex_installed VARCHAR2(5) := 'FALSE'; -- Do not change (is set dynamically).
@@ -23,21 +25,3115 @@ BEGIN
v_ords_installed := 'TRUE';
END LOOP;
-- Show unset compiler flags as errors (results for example in errors like "PLW-06003: unknown inquiry directive '$$UTILS_PUBLIC'")
- EXECUTE IMMEDIATE 'ALTER SESSION SET plsql_warnings = ''ENABLE:6003''';
+ EXECUTE IMMEDIATE 'alter session set plsql_warnings = ''ENABLE:6003''';
-- Finally set compiler flags
- EXECUTE IMMEDIATE 'ALTER SESSION SET plsql_ccflags = '''
+ EXECUTE IMMEDIATE 'alter session set plsql_ccflags = '''
|| 'apex_installed:' || v_apex_installed || ','
|| 'ords_installed:' || v_ords_installed || ','
|| 'utils_public:' || v_utils_public || ','
|| 'debug_on:' || v_debug_on || '''';
END;
/
+
prompt Compile package plex (spec)
-@plex.pks
+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_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';
+c_plex_author CONSTANT VARCHAR2(20 CHAR) := 'Ottmar Gobrecht';
+/**
+PL/SQL Export Utilities
+=======================
+
+PLEX was created to be able to quickstart version control for existing Oracle DB projects and has currently two main functions called **BackApp** and **Queries_to_CSV**. Queries_to_CSV is used by BackApp as a helper function, but its functionality is also useful standalone.
+
+Also see this resources for more information:
+
+- [Blog post on how to getting started](https://ogobrecht.github.io/posts/2018-08-26-plex-plsql-export-utilities)
+- [PLEX project page on GitHub](https://github.com/ogobrecht/plex)
+- [Give feedback on GitHub](https://github.com/ogobrecht/plex/issues/new).
+
+
+DEPENDENCIES
+
+The package itself is independend, but functionality varies on the following conditions:
+
+- For APEX app export: APEX >= 5.1.4 installed
+- For ORDS modules export: ORDS >= 18.3 installed (I think package ords_export is included since this version, but I don't know it)
+ - ATTENTION: There seems to be a bug in ORDS 19.2 which prevents you to export ORDS modules via the package ords_export: https://community.oracle.com/thread/4292776; please see plex_error_log.md, if you miss your ORDS modules after an export - this is no problem of PLEX
+
+
+INSTALLATION
+
+- Download the [latest version](https://github.com/ogobrecht/plex/releases/latest)
+- Unzip it, open a shell and go into the root directory
+- Start SQL*Plus (or another tool which can run SQL scripts)
+- To install PLEX run the provided install script `plex_install.sql` (script provides compiler flags)
+- To uninstall PLEX run the provided script `plex_uninstall.sql` or drop the package manually
+
+
+CHANGELOG
+
+- 2.1.0 (2019-12-30)
+ - Function BackApp:
+ - New parameter to include ORDS modules (p_include_ords_modules)
+ - New parameter to remove the outer column list on views, which is added by the compiler (p_object_view_remove_col_list); this was done in the past implicitly and can now be switched off; thanks to twitter.com/JKaschuba for the hint
+ - Object DDL: Comments for tables and views are now included
+ - Script templates: Improved export speed by using a base64 encoded zip file instead of a global temporary table to unload the files
+ - Fixed: Unable to export JAVA objects on systems with 30 character object names; thanks to twitter.com/JKaschuba for the hint
+ - Fixed: Views appears two times in resulting collection, each double file is postfixed with "_2" and empty
+ - Fixed: Tables and indices of materialized view definitions are exported (should be hidden)
+ - New function to_base64: convert BLOB into base64 encoded CLOB - this is helpful to download a BLOB file (like a zip file) with SQL*Plus
+- 2.0.2 (2019-08-16)
+ - Fixed: Function BackApp throws error on large APEX UI install files (ORA-06502: PL/SQL: numeric or value error: character string buffer too small)
+- 2.0.1 (2019-07-09)
+ - Fixed: Compile error when DB version is lower then 18.1 (PLS-00306: wrong number or types of arguments in call to 'REC_EXPORT_FILE')
+- 2.0.0 (2019-06-20)
+ - Package is now independend from APEX to be able to export schema object DDL and table data without an APEX installation
+ - ATTENTION: The return type of functions BackApp and Queries_to_CSV has changed from `apex_t_export_files` to `plex.tab_export_files`
+ - Function BackApp:
+ - New parameters to filter for object types
+ - New parameters to change base paths for backend, frontend and data
+- 1.2.1 (2019-03-13)
+ - Fixed: Script templates for function BackApp used old/invalid parameters
+ - Add install and uninstall scripts for PLEX itself
+- 1.2.0 (2018-10-31)
+ - Function BackApp: All like/not like parameters are now translated internally with the escape character set to backslash like so `... like 'YourExpression' escape '\'`
+ - Function Queries_to_CSV: Binary data type columns (raw, long_raw, blob, bfile) should no longer break the export
+- 1.1.0 (2018-09-23)
+ - Function BackApp: Change filter parameter from regular expression to list of like expressions for easier handling
+- 1.0.0 (2018-08-26)
+ - First public release
+**/
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- CONSTANTS, TYPES
+--------------------------------------------------------------------------------------------------------------------------------
+
+c_app_info_length CONSTANT PLS_INTEGER := 64;
+SUBTYPE app_info_text IS VARCHAR2(64 CHAR);
+
+TYPE rec_error_log IS RECORD (
+ time_stamp TIMESTAMP,
+ file_name VARCHAR2(255),
+ error_text VARCHAR2(200),
+ call_stack VARCHAR2(500));
+TYPE tab_error_log IS TABLE OF rec_error_log;
+
+TYPE rec_runtime_log IS RECORD (
+ overall_start_time TIMESTAMP,
+ overall_run_time NUMBER,
+ step INTEGER,
+ elapsed NUMBER,
+ execution NUMBER,
+ module app_info_text,
+ action app_info_text);
+TYPE tab_runtime_log IS TABLE OF rec_runtime_log;
+
+TYPE rec_export_file IS RECORD (
+ name VARCHAR2(255),
+ contents CLOB);
+TYPE tab_export_files IS TABLE OF rec_export_file;
+
+TYPE tab_vc32k IS TABLE OF varchar2(32767);
+TYPE tab_vc1k IS TABLE OF VARCHAR2(1024) INDEX BY BINARY_INTEGER;
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- MAIN METHODS
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION backapp (
+ $if $$apex_installed $then
+ -- APEX App:
+ 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.
+ p_app_public_reports IN BOOLEAN DEFAULT true, -- If true, include public reports that a user saved.
+ p_app_private_reports IN BOOLEAN DEFAULT false, -- If true, include private reports that a user saved.
+ p_app_notifications IN BOOLEAN DEFAULT false, -- If true, include report notifications.
+ p_app_translations IN BOOLEAN DEFAULT true, -- If true, include application translation mappings and all text from the translation repository.
+ p_app_pkg_app_mapping IN BOOLEAN DEFAULT false, -- If true, export installed packaged applications with references to the packaged application definition. If FALSE, export them as normal applications.
+ p_app_original_ids IN BOOLEAN DEFAULT false, -- If true, export with the IDs as they were when the application was imported.
+ p_app_subscriptions IN BOOLEAN DEFAULT true, -- If true, components contain subscription references.
+ p_app_comments IN BOOLEAN DEFAULT true, -- If true, include developer comments.
+ 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
+ $if $$ords_installed $then
+ -- ORDS Modules:
+ p_include_ords_modules IN BOOLEAN DEFAULT false, -- If true, include ORDS modules of current user/schema.
+ $end
+ -- Schema Objects:
+ p_include_object_ddl IN BOOLEAN DEFAULT false, -- If true, include DDL of current user/schema and all its objects.
+ p_object_type_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type like '%BODY' escape '\' or object_type like 'JAVA%' escape '\').
+ p_object_type_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type not like '%BODY' escape '\' and object_type not like 'JAVA%' escape '\').
+ 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: ... from user_objects where ... and (object_name like 'EMP%' escape '\' or object_name like 'DEPT%' escape '\').
+ p_object_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: 'EMP%,DEPT%' will be translated to: ... from user_objects where ... and (object_name not like 'EMP%' escape '\' and object_name not like 'DEPT%' escape '\').
+ p_object_view_remove_col_list IN BOOLEAN DEFAULT true, -- If true, the outer column list, added by Oracle on views during compilation, is removed
+ -- Table Data:
+ p_include_data IN BOOLEAN DEFAULT false, -- If true, include CSV data of each table.
+ p_data_as_of_minutes_ago IN NUMBER DEFAULT 0, -- Read consistent data with the resulting timestamp(SCN).
+ p_data_max_rows IN NUMBER DEFAULT 1000, -- Maximum number of rows per table.
+ p_data_table_name_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name like 'EMP%' escape '\' or table_name like 'DEPT%' escape '\').
+ p_data_table_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name not like 'EMP%' escape '\' and table_name not like 'DEPT%' escape '\').
+ -- General Options:
+ p_include_templates IN BOOLEAN DEFAULT true, -- If true, include templates for README.md, export and install scripts.
+ p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with detailed runtime infos.
+ p_include_error_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_error_log.md with detailed error messages.
+ p_base_path_backend IN VARCHAR2 DEFAULT 'app_backend', -- The base path in the project root for the Schema objects.
+ p_base_path_frontend IN VARCHAR2 DEFAULT 'app_frontend', -- The base path in the project root for the APEX app.
+ p_base_path_web_services IN VARCHAR2 DEFAULT 'app_web_services', -- The base path in the project root for the ORDS modules.
+ p_base_path_data IN VARCHAR2 DEFAULT 'app_data') -- The base path in the project root for the table data.
+RETURN tab_export_files;
+/**
+Get a file collection of an APEX application (or the current user/schema only) including:
+
+- The app export SQL files splitted ready to use for version control and deployment
+- Optional the DDL scripts for all objects and grants
+- Optional the data in CSV files (this option was implemented to track catalog tables, can be used as logical backup, has the typical CSV limitations...)
+- Everything in a (hopefully) nice directory structure
+
+EXAMPLE BASIC USAGE
+
+```sql
+DECLARE
+ l_file_collection plex.tab_export_files;
+BEGIN
+ l_file_collection := plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => false,
+ p_include_data => false,
+ p_include_templates => false);
+
+ -- do something with the file collection
+ FOR i IN 1..l_file_collection.count LOOP
+ dbms_output.put_line(i || ' | '
+ || lpad(round(length(l_file_collection(i).contents) / 1024), 3) || ' kB' || ' | '
+ || l_file_collection(i).name);
+ END LOOP;
+END;
+{{/}}
+```
+
+EXAMPLE ZIP FILE PL/SQL
+
+```sql
+DECLARE
+ l_zip_file BLOB;
+BEGIN
+ l_zip_file := plex.to_zip(plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true));
+ -- do something with the zip file
+ -- Your code here...
+END;
+{{/}}
+```
+
+EXAMPLE ZIP FILE SQL
+
+```sql
+-- Inline function because of boolean parameters (needs Oracle 12c or higher).
+-- Alternative create a helper function and call that in a SQL context.
+WITH
+ FUNCTION backapp RETURN BLOB IS
+ BEGIN
+ RETURN plex.to_zip(plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true));
+ END backapp;
+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
+-- 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
+-- Example Mac: base64 -D -i app_100.zip.base64 -o app_100.zip
+-- Example Linux: base64 -d app_100.zip.base64 > app_100.zip
+set verify off feedback off heading off termout off
+set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
+whenever sqlerror exit sql.sqlcode rollback
+variable contents clob
+BEGIN
+ :contents := plex.to_base64(plex.to_zip(plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true)));
+END;
+{{/}}
+spool "app_100.zip.base64"
+print contents
+spool off
+```
+**/
+
+
+
+PROCEDURE add_query (
+ p_query IN VARCHAR2, -- The query itself
+ p_file_name IN VARCHAR2, -- File name like 'Path/to/your/file-without-extension'.
+ p_max_rows IN NUMBER DEFAULT 1000); -- The maximum number of rows to be included in your file.
+/**
+Add a query to be processed by the method queries_to_csv. You can add as many queries as you like.
+
+EXAMPLE
+
+```sql
+BEGIN
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+END;
+{{/}}
+```
+**/
+
+
+
+FUNCTION queries_to_csv (
+ p_delimiter IN VARCHAR2 DEFAULT ',', -- The column delimiter.
+ p_quote_mark IN VARCHAR2 DEFAULT '"', -- Used when the data contains the delimiter character.
+ p_header_prefix IN VARCHAR2 DEFAULT NULL, -- Prefix the header line with this text.
+ p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with runtime statistics.
+ p_include_error_log IN BOOLEAN DEFAULT true) -- If true, generate file plex_error_log.md with detailed error messages.
+RETURN tab_export_files;
+/**
+Export one or more queries as CSV data within a file collection.
+
+EXAMPLE BASIC USAGE
+
+```sql
+DECLARE
+ l_file_collection plex.tab_export_files;
+BEGIN
+ --fill the queries array
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ -- process the queries
+ l_file_collection := plex.queries_to_csv;
+ -- do something with the file collection
+ FOR i IN 1..l_file_collection.count LOOP
+ dbms_output.put_line(i || ' | '
+ || lpad(round(length(l_file_collection(i).contents) / 1024), 3) || ' kB' || ' | '
+ || l_file_collection(i).name);
+ END LOOP;
+END;
+{{/}}
+```
+
+EXAMPLE EXPORT ZIP FILE PL/SQL
+
+```sql
+DECLARE
+ l_zip_file BLOB;
+BEGIN
+ --fill the queries array
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ -- process the queries
+ l_zip_file := plex.to_zip(plex.queries_to_csv);
+ -- do something with the zip file
+ -- Your code here...
+END;
+{{/}}
+```
+
+EXAMPLE EXPORT ZIP FILE SQL
+
+```sql
+WITH
+ FUNCTION queries_to_csv_zip RETURN BLOB IS
+ v_return BLOB;
+ BEGIN
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ v_return := plex.to_zip(plex.queries_to_csv);
+ RETURN v_return;
+ END queries_to_csv_zip;
+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
+-- 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
+-- Example Mac: base64 -D -i metadata.zip.base64 -o metadata.zip
+-- Example Linux: base64 -d metadata.zip.base64 > metadata.zip
+set verify off feedback off heading off termout off
+set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
+whenever sqlerror exit sql.sqlcode rollback
+variable contents clob
+BEGIN
+ --fill the queries array
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ -- process the queries
+ :contents := plex.to_base64(plex.to_zip(plex.queries_to_csv));
+END;
+{{/}}
+spool "metadata.zip.base64"
+print contents
+spool off
+```
+**/
+
+
+
+FUNCTION to_zip (
+ p_file_collection IN tab_export_files) -- The file collection to zip.
+RETURN BLOB;
+/**
+Convert a file collection to a zip file.
+
+EXAMPLE
+
+```sql
+DECLARE
+ l_zip BLOB;
+BEGIN
+ l_zip := plex.to_zip(plex.backapp(
+ p_app_id => 100,
+ p_include_object_ddl => true));
+ -- do something with the zip file...
+END;
+```
+**/
+
+FUNCTION to_base64(
+ p_blob IN BLOB) -- The BLOB to convert.
+RETURN CLOB;
+/**
+Encodes a BLOB into a Base64 CLOB for transfers over a network (like with SQL*Plus). For encoding on the client side see [this blog article](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/).
+
+```sql
+DECLARE
+ l_clob CLOB;
+BEGIN
+ l_clob := plex.to_base64(plex.to_zip(plex.backapp(
+ p_app_id => 100,
+ p_include_object_ddl => true)));
+ -- do something with the clob...
+END;
+```
+**/
+
+FUNCTION view_error_log RETURN tab_error_log PIPELINED;
+/**
+View the error log from the last plex run. The internal array for the error log is cleared on each call of BackApp or Queries_to_CSV.
+
+EXAMPLE
+
+```sql
+SELECT * FROM TABLE(plex.view_error_log);
+```
+**/
+
+FUNCTION view_runtime_log RETURN tab_runtime_log PIPELINED;
+/**
+View the runtime log from the last plex run. The internal array for the runtime log is cleared on each call of BackApp or Queries_to_CSV.
+
+EXAMPLE
+
+```sql
+SELECT * FROM TABLE(plex.view_runtime_log);
+```
+**/
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- UTILITIES (only available when v_utils_public is set to 'true' in install script plex_install.sql)
+--------------------------------------------------------------------------------------------------------------------------------
+
+$if $$utils_public $then
+
+FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2;
+
+FUNCTION util_string_to_bool (
+ p_bool_string IN VARCHAR2,
+ p_default IN BOOLEAN)
+RETURN BOOLEAN;
+
+FUNCTION util_split (
+ p_string IN VARCHAR2,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN tab_vc32k;
+
+FUNCTION util_join (
+ p_array IN tab_vc32k,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN VARCHAR2;
+
+FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB;
+
+/*
+ZIP UTILS
+- The following four zip utilities are copied from this article:
+ - Blog: https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/
+ - Source: https://technology.amis.nl/wp-content/uploads/2010/06/as_zip10.txt
+- Copyright (c) 2010, 2011 by Anton Scheffer (MIT license)
+- Thank you for sharing this Anton :-)
+*/
+FUNCTION util_zip_blob_to_num (
+ p_blob IN BLOB,
+ p_len IN INTEGER,
+ p_pos IN INTEGER)
+RETURN NUMBER;
+FUNCTION util_zip_little_endian (
+ p_big IN NUMBER,
+ p_bytes IN PLS_INTEGER := 4)
+RETURN RAW;
+PROCEDURE util_zip_add_file (
+ p_zipped_blob IN OUT BLOB,
+ p_name IN VARCHAR2,
+ p_content IN BLOB);
+
+PROCEDURE util_zip_finish (p_zipped_blob IN OUT BLOB);
+
+FUNCTION util_multi_replace (
+ p_source_string VARCHAR2,
+ p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
+ p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
+ p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
+ p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
+ p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
+ p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
+ p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
+ p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
+ p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
+ p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
+ p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
+ p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
+RETURN VARCHAR2;
+
+FUNCTION util_set_build_status_run_only (p_app_export_sql IN CLOB) RETURN CLOB;
+
+FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP;
+
+PROCEDURE util_setup_dbms_metadata (
+ p_pretty IN BOOLEAN DEFAULT true,
+ p_constraints IN BOOLEAN DEFAULT true,
+ p_ref_constraints IN BOOLEAN DEFAULT false,
+ p_partitioning IN BOOLEAN DEFAULT true,
+ p_tablespace IN BOOLEAN DEFAULT false,
+ p_storage IN BOOLEAN DEFAULT false,
+ 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);
+
+PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files);
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- The following tools are working on global private package variables
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_init (p_module IN VARCHAR2);
+
+PROCEDURE util_log_start (p_action IN VARCHAR2);
+
+PROCEDURE util_log_error (p_name VARCHAR2);
+
+PROCEDURE util_log_stop;
+
+FUNCTION util_log_get_runtime (
+ p_start IN TIMESTAMP,
+ p_stop IN TIMESTAMP)
+RETURN NUMBER;
+
+PROCEDURE util_log_calc_runtimes;
+
+PROCEDURE util_clob_append (p_content IN VARCHAR2);
+
+PROCEDURE util_clob_append (p_content IN CLOB);
+
+PROCEDURE util_clob_flush_cache;
+
+PROCEDURE util_clob_add_to_export_files (
+ p_export_files IN OUT NOCOPY tab_export_files,
+ p_name IN VARCHAR2);
+
+PROCEDURE util_clob_query_to_csv (
+ p_query IN VARCHAR2,
+ p_max_rows IN NUMBER DEFAULT 1000,
+ p_delimiter IN VARCHAR2 DEFAULT ',',
+ p_quote_mark IN VARCHAR2 DEFAULT '"',
+ p_header_prefix IN VARCHAR2 DEFAULT NULL);
+
+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);
+
+$end
+
+END plex;
+/
show errors
+
prompt Compile package plex (body)
-@plex.pkb
+CREATE OR REPLACE PACKAGE BODY plex IS
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- CONSTANTS, TYPES, GLOBALS
+--------------------------------------------------------------------------------------------------------------------------------
+
+c_tab CONSTANT VARCHAR2(1) := chr(9);
+c_cr CONSTANT VARCHAR2(1) := chr(13);
+c_lf CONSTANT VARCHAR2(1) := chr(10);
+c_crlf CONSTANT VARCHAR2(2) := chr(13) || chr(10);
+c_space_crlf CONSTANT VARCHAR2(3) := ' ' || chr(13) || chr(10);
+c_at CONSTANT VARCHAR2(1) := '@';
+c_hash CONSTANT VARCHAR2(1) := '#';
+c_slash CONSTANT VARCHAR2(1) := '/';
+c_vc2_max_size CONSTANT PLS_INTEGER := 32767;
+c_zip_local_file_header CONSTANT RAW(4) := hextoraw('504B0304');
+c_zip_end_of_central_directory CONSTANT RAW(4) := hextoraw('504B0506');
+
+TYPE tab_errlog IS TABLE OF rec_error_log INDEX BY BINARY_INTEGER;
+
+TYPE rec_runlog_step IS RECORD (
+ action app_info_text,
+ start_time TIMESTAMP(6),
+ stop_time TIMESTAMP(6),
+ elapsed NUMBER,
+ execution NUMBER);
+TYPE tab_runlog_step IS TABLE OF rec_runlog_step INDEX BY BINARY_INTEGER;
+
+TYPE rec_runlog IS RECORD (
+ module app_info_text,
+ start_time TIMESTAMP(6),
+ stop_time TIMESTAMP(6),
+ run_time NUMBER,
+ measured_time NUMBER,
+ unmeasured_time NUMBER,
+ data tab_runlog_step);
+TYPE rec_queries IS RECORD (--
+ query VARCHAR2(32767 CHAR),
+ file_name VARCHAR2(256 CHAR),
+ max_rows NUMBER DEFAULT 100000);
+TYPE tab_queries IS TABLE OF rec_queries INDEX BY BINARY_INTEGER;
+
+TYPE tab_file_list_lookup IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(256);
+
+TYPE rec_ddl_files IS RECORD (
+ ords_modules_ tab_vc1k,
+ sequences_ tab_vc1k,
+ tables_ tab_vc1k,
+ ref_constraints_ tab_vc1k,
+ indices_ tab_vc1k,
+ views_ tab_vc1k,
+ mviews_ tab_vc1k,
+ types_ tab_vc1k,
+ type_bodies_ tab_vc1k,
+ triggers_ tab_vc1k,
+ functions_ tab_vc1k,
+ procedures_ tab_vc1k,
+ packages_ tab_vc1k,
+ package_bodies_ tab_vc1k,
+ grants_ tab_vc1k,
+ other_objects_ tab_vc1k);
+
+g_clob CLOB;
+g_cache VARCHAR2(32767char);
+g_errlog tab_errlog;
+g_runlog rec_runlog;
+g_queries tab_queries;
+
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- UTILITIES (forward declarations, only compiled when not public)
+--------------------------------------------------------------------------------------------------------------------------------
+
+$if not $$utils_public $then
+FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2;
+
+FUNCTION util_string_to_bool (
+ p_bool_string IN VARCHAR2,
+ p_default IN BOOLEAN)
+RETURN BOOLEAN;
+
+FUNCTION util_split (
+ p_string IN VARCHAR2,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN tab_vc32k;
+
+FUNCTION util_join (
+ p_array IN tab_vc32k,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN VARCHAR2;
+
+FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB;
+
+/*
+ZIP UTILS
+- The following four zip utilities are copied from this article:
+ - Blog: https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/
+ - Source: https://technology.amis.nl/wp-content/uploads/2010/06/as_zip10.txt
+- Copyright (c) 2010, 2011 by Anton Scheffer (MIT license)
+- Thank you for sharing this Anton :-)
+*/
+FUNCTION util_zip_blob_to_num (
+ p_blob IN BLOB,
+ p_len IN INTEGER,
+ p_pos IN INTEGER)
+RETURN NUMBER;
+
+FUNCTION util_zip_little_endian (
+ p_big IN NUMBER,
+ p_bytes IN PLS_INTEGER := 4)
+RETURN RAW;
+
+PROCEDURE util_zip_add_file (
+ p_zipped_blob IN OUT BLOB,
+ p_name IN VARCHAR2,
+ p_content IN BLOB);
+
+PROCEDURE util_zip_finish (p_zipped_blob IN OUT BLOB);
+
+FUNCTION util_multi_replace (
+ p_source_string VARCHAR2,
+ p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
+ p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
+ p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
+ p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
+ p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
+ p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
+ p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
+ p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
+ p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
+ p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
+ p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
+ p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
+RETURN VARCHAR2;
+
+FUNCTION util_set_build_status_run_only (p_app_export_sql IN CLOB) RETURN CLOB;
+
+FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP;
+
+PROCEDURE util_setup_dbms_metadata (
+ p_pretty IN BOOLEAN DEFAULT true,
+ p_constraints IN BOOLEAN DEFAULT true,
+ p_ref_constraints IN BOOLEAN DEFAULT false,
+ p_partitioning IN BOOLEAN DEFAULT true,
+ p_tablespace IN BOOLEAN DEFAULT false,
+ p_storage IN BOOLEAN DEFAULT false,
+ 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);
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- The following tools are working on the global private package variables g_clob, g_clob_varchar_cache, g_runlog and g_queries
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_append (p_content IN VARCHAR2);
+
+PROCEDURE util_clob_append (p_content IN CLOB);
+
+PROCEDURE util_clob_flush_cache;
+
+PROCEDURE util_clob_add_to_export_files (
+ p_export_files IN OUT NOCOPY tab_export_files,
+ p_name IN VARCHAR2);
+
+PROCEDURE util_clob_query_to_csv (
+ p_query IN VARCHAR2,
+ p_max_rows IN NUMBER DEFAULT 1000,
+ p_delimiter IN VARCHAR2 DEFAULT ',',
+ p_quote_mark IN VARCHAR2 DEFAULT '"',
+ p_header_prefix IN VARCHAR2 DEFAULT NULL);
+
+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);
+
+PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files);
+
+PROCEDURE util_log_init (p_module IN VARCHAR2);
+
+PROCEDURE util_log_start (p_action IN VARCHAR2);
+
+PROCEDURE util_log_error (p_name VARCHAR2);
+
+PROCEDURE util_log_stop;
+
+FUNCTION util_log_get_runtime (
+ p_start IN TIMESTAMP,
+ p_stop IN TIMESTAMP)
+RETURN NUMBER;
+
+PROCEDURE util_log_calc_runtimes;
+
+$end
+
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- UTILITIES MAIN CODE
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2 IS
+BEGIN
+ RETURN CASE WHEN p_bool THEN 'TRUE' ELSE 'FALSE' END;
+END util_bool_to_string;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_string_to_bool (
+ p_bool_string IN VARCHAR2,
+ p_default IN BOOLEAN)
+RETURN BOOLEAN IS
+ v_bool_string VARCHAR2(1 CHAR);
+ v_return BOOLEAN;
+BEGIN
+ v_bool_string := upper(substr(p_bool_string, 1, 1));
+ v_return :=
+ CASE
+ WHEN v_bool_string IN ('1', 'Y', 'T') THEN
+ true
+ WHEN v_bool_string IN ('0', 'N', 'F') THEN
+ false
+ ELSE p_default
+ END;
+ RETURN v_return;
+END util_string_to_bool;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_split (
+ p_string IN VARCHAR2,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN tab_vc32k IS
+ v_return tab_vc32k := tab_vc32k();
+ v_offset PLS_INTEGER := 1;
+ v_index PLS_INTEGER := instr(p_string, p_delimiter, v_offset);
+ v_delimiter_length PLS_INTEGER := length(p_delimiter);
+ v_string_length CONSTANT PLS_INTEGER := length(p_string);
+ v_count PLS_INTEGER := 1;
+
+ PROCEDURE add_value (p_value VARCHAR2) IS
+ BEGIN
+ v_return.extend;
+ v_return(v_count) := p_value;
+ v_count := v_count + 1;
+ END add_value;
+
+BEGIN
+ WHILE v_index > 0 LOOP
+ add_value(trim(substr(p_string, v_offset, v_index - v_offset)));
+ v_offset := v_index + v_delimiter_length;
+ v_index := instr(p_string, p_delimiter, v_offset);
+ END LOOP;
+ IF v_string_length - v_offset + 1 > 0 THEN
+ add_value(trim(substr(p_string, v_offset, v_string_length - v_offset + 1)));
+ END IF;
+ RETURN v_return;
+END util_split;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_join (
+ p_array IN tab_vc32k,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN VARCHAR2 IS
+ v_return VARCHAR2(32767);
+BEGIN
+ IF p_array IS NOT NULL AND p_array.count > 0 THEN
+ v_return := p_array(1);
+ FOR i IN 2 ..p_array.count LOOP
+ v_return := v_return || p_delimiter || p_array(i);
+ END LOOP;
+ END IF;
+ RETURN v_return;
+EXCEPTION
+ WHEN value_error THEN
+ RETURN v_return;
+END util_join;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB IS
+ v_blob BLOB;
+ v_lang_context INTEGER := dbms_lob.default_lang_ctx;
+ v_warning INTEGER := dbms_lob.warn_inconvertible_char;
+ v_dest_offset INTEGER := 1;
+ v_src_offset INTEGER := 1;
+BEGIN
+ IF p_clob IS NOT NULL THEN
+ dbms_lob.createtemporary(v_blob, true);
+ dbms_lob.converttoblob(
+ dest_lob => v_blob,
+ src_clob => p_clob,
+ amount => dbms_lob.lobmaxsize,
+ dest_offset => v_dest_offset,
+ src_offset => v_src_offset,
+ blob_csid => nls_charset_id('AL32UTF8'),
+ lang_context => v_lang_context,
+ warning => v_warning);
+ END IF;
+ RETURN v_blob;
+END util_clob_to_blob;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
+FUNCTION util_zip_blob_to_num (
+ p_blob IN BLOB,
+ p_len IN INTEGER,
+ p_pos IN 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 util_zip_blob_to_num;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
+FUNCTION util_zip_little_endian (
+ p_big IN NUMBER,
+ p_bytes IN 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 util_zip_little_endian;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
+PROCEDURE util_zip_add_file (
+ p_zipped_blob IN OUT BLOB,
+ p_name IN VARCHAR2,
+ p_content IN 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;
+ t_name := utl_i18n.string_to_raw(p_name, 'AL32UTF8');
+ dbms_lob.append(
+ p_zipped_blob,
+ utl_raw.concat(
+ c_zip_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,
+ util_zip_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
+ util_zip_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
+ util_zip_little_endian(t_clen), -- compressed size
+ util_zip_little_endian(t_len), -- uncompressed size
+ util_zip_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 util_zip_add_file;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
+PROCEDURE util_zip_finish (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_zip_local_file_header), t_offs) = c_zip_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 + util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26) - 1)
+ IN (hextoraw('2F')/*slash*/, hextoraw('5C')/*backslash*/)
+ THEN hextoraw('10000000') -- a directory/folder
+ ELSE hextoraw('2000B681') -- a file
+ END, -- external file attributes
+ util_zip_little_endian(t_offs - 1), -- relative offset of local file header
+ dbms_lob.substr(
+ p_zipped_blob,
+ util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26),
+ t_offs + 30))); -- File name
+ t_offs := t_offs + 30
+ + util_zip_blob_to_num(p_zipped_blob, 4, t_offs + 18) -- compressed size
+ + util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26) -- file name length
+ + util_zip_blob_to_num(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_zip_end_of_central_directory, -- end of central directory signature
+ hextoraw('0000'), -- number of this disk
+ hextoraw('0000'), -- disk where central directory starts
+ util_zip_little_endian(t_cnt, 2), -- number of central directory records on this disk
+ util_zip_little_endian(t_cnt, 2), -- total number of central directory records
+ util_zip_little_endian(t_offs_end_header - t_offs_dir_header), -- size of central directory
+ util_zip_little_endian(t_offs_dir_header), -- offset of start of central directory, relative to start of archive
+ util_zip_little_endian(nvl(utl_raw.length(t_comment), 0), 2), -- ZIP file comment length
+ t_comment));
+END util_zip_finish;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_multi_replace (
+ p_source_string VARCHAR2,
+ p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
+ p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
+ p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
+ p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
+ p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
+ p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
+ p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
+ p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
+ p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
+ p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
+ p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
+ p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
+RETURN VARCHAR2 IS
+ v_return VARCHAR2(32767);
+BEGIN
+ v_return := p_source_string;
+ IF p_01_find IS NOT NULL THEN v_return := replace(v_return, p_01_find, p_01_replace); END IF;
+ IF p_02_find IS NOT NULL THEN v_return := replace(v_return, p_02_find, p_02_replace); END IF;
+ IF p_03_find IS NOT NULL THEN v_return := replace(v_return, p_03_find, p_03_replace); END IF;
+ IF p_04_find IS NOT NULL THEN v_return := replace(v_return, p_04_find, p_04_replace); END IF;
+ IF p_05_find IS NOT NULL THEN v_return := replace(v_return, p_05_find, p_05_replace); END IF;
+ IF p_06_find IS NOT NULL THEN v_return := replace(v_return, p_06_find, p_06_replace); END IF;
+ IF p_07_find IS NOT NULL THEN v_return := replace(v_return, p_07_find, p_07_replace); END IF;
+ IF p_08_find IS NOT NULL THEN v_return := replace(v_return, p_08_find, p_08_replace); END IF;
+ IF p_09_find IS NOT NULL THEN v_return := replace(v_return, p_09_find, p_09_replace); END IF;
+ IF p_10_find IS NOT NULL THEN v_return := replace(v_return, p_10_find, p_10_replace); END IF;
+ IF p_11_find IS NOT NULL THEN v_return := replace(v_return, p_11_find, p_11_replace); END IF;
+ IF p_12_find IS NOT NULL THEN v_return := replace(v_return, p_12_find, p_12_replace); END IF;
+ RETURN v_return;
+END util_multi_replace;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_set_build_status_run_only (p_app_export_sql CLOB) RETURN CLOB IS
+ v_position PLS_INTEGER;
+BEGIN
+ v_position := instr(p_app_export_sql, ',p_exact_substitutions_only');
+ RETURN substr(p_app_export_sql, 1, v_position - 1)
+ || ',p_build_status=>''RUN_ONLY'''
+ || c_lf
+ || substr(p_app_export_sql, v_position);
+END util_set_build_status_run_only;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP IS
+ v_return TIMESTAMP;
+BEGIN
+ EXECUTE IMMEDIATE
+ replace(
+ q'[SELECT systimestamp - INTERVAL '{{MINUTES}}' MINUTE FROM dual]',
+ '{{MINUTES}}',
+ TO_CHAR(p_as_of_minutes_ago))
+ INTO v_return;
+ RETURN v_return;
+END util_calc_data_timestamp;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_setup_dbms_metadata (
+ p_pretty IN BOOLEAN DEFAULT true,
+ p_constraints IN BOOLEAN DEFAULT true,
+ p_ref_constraints IN BOOLEAN DEFAULT false,
+ p_partitioning IN BOOLEAN DEFAULT true,
+ p_tablespace IN BOOLEAN DEFAULT false,
+ p_storage IN BOOLEAN DEFAULT false,
+ 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)
+IS
+BEGIN
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'PRETTY', p_pretty);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'CONSTRAINTS', p_constraints);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'REF_CONSTRAINTS', p_ref_constraints);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'PARTITIONING', p_partitioning);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'TABLESPACE', p_tablespace);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'STORAGE', p_storage);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'SEGMENT_ATTRIBUTES', p_segment_attributes);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'SQLTERMINATOR', p_sqlterminator);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'CONSTRAINTS_AS_ALTER', p_constraints_as_alter);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'EMIT_SCHEMA', p_emit_schema);
+END util_setup_dbms_metadata;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files) IS
+ v_file_list_lookup tab_file_list_lookup;
+ v_apex_install_file_id PLS_INTEGER;
+ v_file_name VARCHAR2(256);
+ v_extension VARCHAR2(256);
+ v_base_name VARCHAR2(256);
+ v_count PLS_INTEGER;
+BEGIN
+ util_log_start('ensure unique file names in collection');
+ $if $$apex_installed $then
+ -- find apex install file
+ FOR i IN 1..p_export_files.count LOOP
+ IF p_export_files(i).name = 'scripts/install_frontend_generated_by_apex.sql' THEN
+ v_apex_install_file_id := i;
+ END IF;
+ END LOOP;
+ $end
+ FOR i IN 1..p_export_files.count LOOP
+ v_file_name := p_export_files(i).name;
+ v_count := 1;
+ IF instr(v_file_name, '.') > 0 THEN
+ v_base_name := substr(v_file_name, 1, instr(v_file_name, '.', -1) - 1);
+ v_extension := substr(v_file_name, instr(v_file_name, '.', -1));
+ ELSE
+ v_base_name := v_file_name;
+ v_extension := NULL;
+ END IF;
+ WHILE v_file_list_lookup.EXISTS(v_file_name) LOOP
+ v_count := v_count + 1;
+ v_file_name := v_base_name || '_' || v_count || v_extension;
+ END LOOP;
+ v_file_list_lookup(v_file_name) := i;
+ -- correct data if needed
+ IF p_export_files(i).name != v_file_name THEN
+ -- correct the prompt statement
+ p_export_files(i).contents := replace(
+ p_export_files(i).contents,
+ v_base_name,
+ v_base_name || '_' || v_count);
+ -- correct the apex install file
+ IF v_apex_install_file_id IS NOT NULL THEN
+ p_export_files(v_apex_install_file_id).contents := regexp_replace(
+ p_export_files(v_apex_install_file_id).contents,
+ p_export_files(i).name || '$',
+ v_file_name,
+ 1, 2, 'm');
+ END IF;
+ -- correct the file name itself
+ p_export_files(i).name := v_file_name;
+ END IF;
+ END LOOP;
+ util_log_stop;
+END util_ensure_unique_file_names;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_init (p_module IN VARCHAR2) IS
+BEGIN
+ g_runlog.module := substr(p_module, 1, c_app_info_length);
+ g_runlog.start_time := systimestamp;
+ g_runlog.stop_time := NULL;
+ g_runlog.run_time := 0;
+ g_runlog.measured_time := 0;
+ g_runlog.unmeasured_time := 0;
+ g_runlog.data.DELETE;
+ g_errlog.DELETE;
+END util_log_init;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_start (p_action IN VARCHAR2) IS
+ v_index PLS_INTEGER;
+BEGIN
+ dbms_application_info.set_module(
+ module_name => g_runlog.module,
+ action_name => p_action);
+ v_index := g_runlog.data.count + 1;
+ g_runlog.data(v_index).action := substr(p_action, 1, plex.c_app_info_length);
+ g_runlog.data(v_index).start_time := systimestamp;
+END util_log_start;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_error (p_name VARCHAR2) IS
+ v_index PLS_INTEGER;
+ PROCEDURE add_error_to_action IS
+ v_index PLS_INTEGER;
+ BEGIN
+ v_index := g_runlog.data.count;
+ g_runlog.data(v_index).action := substr('ERROR: ' || g_runlog.data(v_index).action, 1, plex.c_app_info_length);
+ END add_error_to_action;
+BEGIN
+ v_index := g_errlog.count + 1;
+ g_errlog(v_index).time_stamp := systimestamp;
+ g_errlog(v_index).file_name := substr(p_name, 1, 255);
+ g_errlog(v_index).error_text := substr(sqlerrm, 1, 200);
+ g_errlog(v_index).call_stack := substr(dbms_utility.format_error_backtrace, 1, 500);
+ add_error_to_action;
+ util_log_stop;
+ g_clob := null;
+END util_log_error;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_stop IS
+ v_index PLS_INTEGER;
+BEGIN
+ v_index := g_runlog.data.count;
+ dbms_application_info.set_module(
+ module_name => NULL,
+ action_name => NULL);
+ g_runlog.data(v_index).stop_time := systimestamp;
+ g_runlog.data(v_index).elapsed := util_log_get_runtime(g_runlog.start_time, g_runlog.data(v_index).stop_time);
+ g_runlog.data(v_index).execution := util_log_get_runtime(g_runlog.data(v_index).start_time, g_runlog.data(v_index).stop_time);
+ g_runlog.measured_time := g_runlog.measured_time + g_runlog.data(v_index).execution;
+END util_log_stop;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_log_get_runtime (
+ p_start IN TIMESTAMP,
+ p_stop IN TIMESTAMP)
+RETURN NUMBER IS
+BEGIN
+ RETURN SYSDATE + ((p_stop - p_start) * 86400) - SYSDATE;
+ --sysdate + (interval_difference * 86400) - sysdate
+ --https://stackoverflow.com/questions/10092032/extracting-the-total-number-of-seconds-from-an-interval-data-type
+END util_log_get_runtime;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_calc_runtimes IS
+BEGIN
+ g_runlog.stop_time := systimestamp;
+ g_runlog.run_time := util_log_get_runtime(g_runlog.start_time, g_runlog.stop_time);
+ g_runlog.unmeasured_time := g_runlog.run_time - g_runlog.measured_time;
+END util_log_calc_runtimes;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_append (p_content IN VARCHAR2) IS
+BEGIN
+ g_cache := g_cache || p_content;
+EXCEPTION
+ WHEN value_error THEN
+ IF g_clob IS NULL THEN
+ g_clob := g_cache;
+ ELSE
+ dbms_lob.writeappend(g_clob, length(g_cache), g_cache);
+ END IF;
+ g_cache := p_content;
+END util_clob_append;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_append (p_content IN CLOB) IS
+BEGIN
+ IF p_content IS NOT NULL THEN
+ util_clob_flush_cache;
+ IF g_clob IS NULL THEN
+ g_clob := p_content;
+ ELSE
+ dbms_lob.writeappend(g_clob, length(p_content), p_content);
+ END IF;
+ END IF;
+END util_clob_append;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_flush_cache IS
+BEGIN
+ IF g_cache IS NOT NULL THEN
+ IF g_clob IS NULL THEN
+ g_clob := g_cache;
+ ELSE
+ dbms_lob.writeappend(g_clob, length(g_cache), g_cache);
+ END IF;
+ g_cache := NULL;
+ END IF;
+END util_clob_flush_cache;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_add_to_export_files (
+ p_export_files IN OUT NOCOPY tab_export_files,
+ p_name IN VARCHAR2)
+IS
+ v_index PLS_INTEGER;
+BEGIN
+ util_clob_flush_cache;
+ v_index := p_export_files.count + 1;
+ p_export_files.extend;
+ p_export_files(v_index).name := p_name;
+ p_export_files(v_index).contents := g_clob;
+ g_clob := null;
+END util_clob_add_to_export_files;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_query_to_csv (
+ p_query IN VARCHAR2,
+ p_max_rows IN NUMBER DEFAULT 1000,
+ p_delimiter IN VARCHAR2 DEFAULT ',',
+ p_quote_mark IN VARCHAR2 DEFAULT '"',
+ p_header_prefix IN VARCHAR2 DEFAULT NULL)
+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_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
+ 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 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 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;
+
+BEGIN
+ IF p_query IS NOT NULL THEN
+ v_cursor := dbms_sql.open_cursor;
+ dbms_sql.parse(
+ v_cursor,
+ regexp_replace(p_query, ';\s*$', NULL),
+ 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);
+ util_clob_append(p_header_prefix);
+ FOR i IN 1..v_col_cnt LOOP
+ IF i > 1 THEN
+ util_clob_append(p_delimiter);
+ END IF;
+ 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 i > 1 THEN
+ util_clob_append(p_delimiter);
+ END IF;
+ 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_query_to_csv;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files) IS
+BEGIN
+ IF g_errlog.count > 0 THEN
+ util_log_start(g_errlog.count || ' error' || CASE WHEN g_errlog.count != 1 THEN 's' END || ' occurred: create error log');
+ util_clob_append(
+ replace('# {{MAIN_FUNCTION}} - Error Log', '{{MAIN_FUNCTION}}', upper(g_runlog.module))
+ || c_crlf || c_crlf || c_crlf);
+ FOR i IN 1..g_errlog.count LOOP
+ util_clob_append('## ' || g_errlog(i).file_name || c_crlf || c_crlf);
+ util_clob_append(to_char(g_errlog(i).time_stamp, 'yyyy-mm-dd hh24:mi:ss.ffffff') || ': ' || g_errlog(i).error_text || c_crlf || c_crlf);
+ util_clob_append('```sql' || c_crlf || g_errlog(i).call_stack || '```' || c_crlf || c_crlf || c_crlf);
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => p_export_files,
+ p_name => 'plex_error_log.md');
+ util_log_stop;
+ END IF;
+END util_clob_create_error_log;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_create_runtime_log (p_export_files IN OUT NOCOPY tab_export_files) IS
+BEGIN
+ util_log_calc_runtimes;
+ util_clob_append(util_multi_replace('# {{MAIN_FUNCTION}} - Runtime Log
+
+- Export started at {{START_TIME}} and took {{RUN_TIME}} seconds to finish with {{ERRORS}}
+- Unmeasured execution time because of system waits, missing log calls or log overhead was {{UNMEASURED_TIME}} seconds
+- The used PLEX version was {{PLEX_VERSION}}
+- More infos here: [PLEX on GitHub]({{PLEX_URL}})
+
+' ,
+ '{{MAIN_FUNCTION}}', upper(g_runlog.module),
+ '{{START_TIME}}', TO_CHAR(g_runlog.start_time, 'yyyy-mm-dd hh24:mi:ss'),
+ '{{RUN_TIME}}', trim(TO_CHAR(g_runlog.run_time, '999G990D000')),
+ '{{UNMEASURED_TIME}}', trim(TO_CHAR(g_runlog.unmeasured_time, '999G990D000')),
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{ERRORS}}', g_errlog.count || ' error' || CASE WHEN g_errlog.count != 1 THEN 's' END));
+ util_clob_append('
+| Step | Elapsed | Execution | Action |
+|------:|----------:|------------:|:-----------------------------------------------------------------|
+' );
+ FOR i IN 1..g_runlog.data.count LOOP
+ util_clob_append(util_multi_replace(
+ '| {{STEP}} | {{ELAPSED}} | {{EXECUTION}} | {{ACTION}} |' || c_lf,
+ '{{STEP}}', lpad(TO_CHAR(i), 5),
+ '{{ELAPSED}}', lpad(trim(TO_CHAR(g_runlog.data(i).elapsed, '99990D000')), 9),
+ '{{EXECUTION}}', lpad(trim(TO_CHAR(g_runlog.data(i).execution, '9990D000000')), 11),
+ '{{ACTION}}', rpad(g_runlog.data(i).action, 64)));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => p_export_files,
+ p_name => 'plex_runtime_log.md');
+END util_clob_create_runtime_log;
+
+
+
+------------------------------------------------------------------------------------------------------------------------------
+-- MAIN CODE
+------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION backapp (
+ $if $$apex_installed $then
+ p_app_id IN NUMBER DEFAULT NULL,
+ p_app_date IN BOOLEAN DEFAULT true,
+ p_app_public_reports IN BOOLEAN DEFAULT true,
+ p_app_private_reports IN BOOLEAN DEFAULT false,
+ p_app_notifications IN BOOLEAN DEFAULT false,
+ p_app_translations IN BOOLEAN DEFAULT true,
+ p_app_pkg_app_mapping IN BOOLEAN DEFAULT false,
+ p_app_original_ids IN BOOLEAN DEFAULT false,
+ p_app_subscriptions IN BOOLEAN DEFAULT true,
+ p_app_comments IN BOOLEAN DEFAULT true,
+ 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
+ $if $$ords_installed $then
+ p_include_ords_modules IN BOOLEAN DEFAULT false,
+ $end
+ p_include_object_ddl IN BOOLEAN DEFAULT false,
+ p_object_type_like IN VARCHAR2 DEFAULT NULL,
+ 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_include_data IN BOOLEAN DEFAULT false,
+ p_data_as_of_minutes_ago IN NUMBER DEFAULT 0,
+ p_data_max_rows IN NUMBER DEFAULT 1000,
+ p_data_table_name_like IN VARCHAR2 DEFAULT NULL,
+ p_data_table_name_not_like IN VARCHAR2 DEFAULT NULL,
+ p_include_templates IN BOOLEAN DEFAULT true,
+ p_include_runtime_log IN BOOLEAN DEFAULT true,
+ p_include_error_log IN BOOLEAN DEFAULT true,
+ p_base_path_backend IN VARCHAR2 DEFAULT 'app_backend',
+ p_base_path_frontend IN VARCHAR2 DEFAULT 'app_frontend',
+ p_base_path_web_services IN VARCHAR2 DEFAULT 'app_web_services',
+ p_base_path_data IN VARCHAR2 DEFAULT 'app_data')
+RETURN tab_export_files IS
+ v_apex_version NUMBER;
+ v_data_timestamp TIMESTAMP;
+ v_data_scn NUMBER;
+ v_file_path VARCHAR2(255);
+ v_current_user user_objects.object_name%TYPE;
+ v_app_workspace user_objects.object_name%TYPE;
+ v_app_owner user_objects.object_name%TYPE;
+ v_app_alias user_objects.object_name%TYPE;
+ v_ddl_files rec_ddl_files;
+ v_contents CLOB;
+ v_export_files tab_export_files;
+ v_file_list_lookup tab_file_list_lookup;
+ TYPE obj_cur_typ IS REF CURSOR;
+ v_cur obj_cur_typ;
+ v_query VARCHAR2(32767);
+
+ FUNCTION util_get_script_line (p_file_path VARCHAR2) RETURN VARCHAR2 IS
+ BEGIN
+ RETURN 'prompt --' || replace(p_file_path, '.sql', NULL)
+ || c_lf || '@' || '../' || p_file_path || c_lf || c_lf;
+ END util_get_script_line;
+
+ PROCEDURE init IS
+ BEGIN
+ util_log_init(
+ p_module => 'plex.backapp'
+ $if $$apex_installed $then
+ || CASE WHEN p_app_id IS NOT NULL THEN '(' || TO_CHAR(p_app_id) || ')' END
+ $end);
+ util_log_start('init');
+ v_export_files := NEW tab_export_files();
+ v_current_user := sys_context('USERENV', 'CURRENT_USER');
+ util_log_stop;
+ END init;
+
+ $if $$apex_installed $then
+ PROCEDURE check_owner IS
+ CURSOR cur_owner IS
+ SELECT workspace,
+ owner,
+ alias
+ FROM apex_applications t
+ WHERE t.application_id = p_app_id;
+ BEGIN
+ util_log_start('check_owner');
+ IF p_app_id IS NOT NULL THEN
+ OPEN cur_owner;
+ FETCH cur_owner INTO
+ v_app_workspace,
+ v_app_owner,
+ v_app_alias;
+ CLOSE cur_owner;
+ END IF;
+ IF p_app_id IS NOT NULL AND v_app_owner IS NULL THEN
+ raise_application_error(
+ -20101,
+ 'Could not find owner for application - are you sure you provided the right app_id?');
+ ELSIF p_app_id IS NOT NULL AND v_app_owner != v_current_user THEN
+ raise_application_error(
+ -20102,
+ 'You are not the owner of the app - please login as the owner.');
+ END IF;
+ util_log_stop;
+ END check_owner;
+ $end
+
+ $if $$apex_installed $then
+ PROCEDURE process_apex_app IS
+ v_apex_files apex_t_export_files;
+ BEGIN
+ -- save as individual files
+ util_log_start(p_base_path_frontend || '/APEX_EXPORT:individual_files');
+ v_apex_files := apex_export.get_application(
+ p_application_id => p_app_id,
+ p_split => true,
+ p_with_date => p_app_date,
+ p_with_ir_public_reports => p_app_public_reports,
+ p_with_ir_private_reports => p_app_private_reports,
+ p_with_ir_notifications => p_app_notifications,
+ p_with_translations => p_app_translations,
+ p_with_pkg_app_mapping => p_app_pkg_app_mapping,
+ p_with_original_ids => p_app_original_ids,
+ p_with_no_subscriptions => CASE WHEN p_app_subscriptions THEN false ELSE true END,
+ p_with_comments => p_app_comments,
+ p_with_supporting_objects => p_app_supporting_objects);
+ FOR i IN 1..v_apex_files.count LOOP
+ v_export_files.extend;
+ -- relocate files to own project structure
+ v_export_files(i).name := replace(
+ v_apex_files(i).name,
+ 'f' || p_app_id || '/application/',
+ p_base_path_frontend || '/');
+ -- correct prompts for relocation
+ v_export_files(i).contents := replace(
+ v_apex_files(i).contents,
+ 'prompt --application/',
+ 'prompt --' || p_base_path_frontend || '/');
+ -- special handling for install file
+ IF v_export_files(i).name = 'f' || p_app_id || '/install.sql' THEN
+ v_export_files(i).name := 'scripts/install_frontend_generated_by_apex.sql';
+ v_export_files(i).contents := '-- DO NOT TOUCH THIS FILE - IT WILL BE OVERWRITTEN ON NEXT PLEX BACKAPP CALL'
+ || c_lf || c_lf
+ || replace(replace(v_export_files(i).contents,
+ '@application/', '@../' || p_base_path_frontend || '/'),
+ 'prompt --install', 'prompt --install_frontend_generated_by_apex');
+ END IF;
+ -- handle build status RUN_ONLY
+ IF v_export_files(i).name = p_base_path_frontend || '/create_application.sql' AND p_app_build_status_run_only THEN
+ v_export_files(i).contents := util_set_build_status_run_only(v_export_files(i).contents);
+ END IF;
+ v_apex_files.DELETE(i);
+ END LOOP;
+ util_log_stop;
+ --
+ IF p_app_include_single_file THEN
+ -- save as single file
+ v_apex_files.DELETE;
+ util_log_start(p_base_path_frontend || '/APEX_EXPORT:single_file');
+ v_apex_files := apex_export.get_application(
+ p_application_id => p_app_id,
+ p_split => false,
+ p_with_date => p_app_date,
+ p_with_ir_public_reports => p_app_public_reports,
+ p_with_ir_private_reports => p_app_private_reports,
+ p_with_ir_notifications => p_app_notifications,
+ p_with_translations => p_app_translations,
+ p_with_pkg_app_mapping => p_app_pkg_app_mapping,
+ p_with_original_ids => p_app_original_ids,
+ p_with_no_subscriptions => CASE WHEN p_app_subscriptions THEN false ELSE true END,
+ p_with_comments => p_app_comments,
+ p_with_supporting_objects => p_app_supporting_objects);
+ IF p_app_build_status_run_only THEN
+ v_apex_files(1).contents := util_set_build_status_run_only(v_apex_files(1).contents);
+ END IF;
+ util_clob_append(v_apex_files(1).contents);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => p_base_path_frontend || '/' || v_apex_files(1).name);
+ v_apex_files.DELETE;
+ util_log_stop;
+ END IF;
+ END process_apex_app;
+ $end
+
+ PROCEDURE replace_query_like_expressions (
+ p_like_list VARCHAR2,
+ p_not_like_list VARCHAR2,
+ p_placeholder_prefix VARCHAR2,
+ p_column_name VARCHAR2)
+ IS
+ v_expression_table tab_vc32k;
+ BEGIN
+ -- process filter "like"
+ v_expression_table := util_split(p_like_list, ',');
+ FOR i IN 1..v_expression_table.count LOOP
+ v_expression_table(i) := p_column_name
+ || ' like '''
+ || trim(v_expression_table(i))
+ || ''' escape ''\''';
+ END LOOP;
+ v_query := replace(
+ v_query,
+ '#' || p_placeholder_prefix || '_LIKE_EXPRESSIONS#',
+ nvl(util_join(v_expression_table, ' or '), '1 = 1'));
+ -- process filter "not like"
+ v_expression_table := util_split(p_not_like_list, ',');
+ FOR i IN 1..v_expression_table.count LOOP
+ v_expression_table(i) := p_column_name
+ || ' not like '''
+ || trim(v_expression_table (i))
+ || ''' escape ''\''';
+ END LOOP;
+ v_query := replace(
+ v_query,
+ '#' || p_placeholder_prefix || '_NOT_LIKE_EXPRESSIONS#',
+ nvl(util_join(v_expression_table, ' and '), '1 = 1'));
+ $if $$debug_on $then
+ dbms_output.put_line(v_query);
+ $end
+ END replace_query_like_expressions;
+
+ PROCEDURE process_user_ddl IS
+
+ PROCEDURE process_user IS
+ BEGIN
+ v_file_path := p_base_path_backend || '/_user/' || v_current_user || '.sql';
+ util_log_start(v_file_path);
+ util_setup_dbms_metadata(p_sqlterminator => false);
+ util_clob_append(util_multi_replace(q'^BEGIN
+ FOR i IN (SELECT '{{CURRENT_USER}}' AS username FROM dual
+ MINUS
+ SELECT username FROM dba_users) LOOP
+ EXECUTE IMMEDIATE q'[
+--------------------------------------------------------------------------------
+{{DDL}}
+--------------------------------------------------------------------------------
+ ]'
+ END LOOP;
+END;
+{{/}}
+^' ,
+ '{{CURRENT_USER}}', v_current_user,
+ '{{DDL}}', ltrim(dbms_metadata.get_ddl('USER', v_current_user), c_space_crlf),
+ '{{/}}', c_slash));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_setup_dbms_metadata;
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_setup_dbms_metadata;
+ util_log_error(v_file_path);
+ END process_user;
+
+ PROCEDURE process_roles IS
+ BEGIN
+ v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_roles.sql';
+ util_log_start(v_file_path);
+ FOR i IN (SELECT DISTINCT username FROM user_role_privs) LOOP
+ util_clob_append(regexp_replace(
+ --source string
+ dbms_metadata.get_granted_ddl('ROLE_GRANT', v_current_user),
+ --replace all leading whitespace
+ '^\s*', NULL, 1, 0, 'm'));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END process_roles;
+
+ PROCEDURE process_system_privileges IS
+ BEGIN
+ v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_system_privileges.sql';
+ util_log_start(v_file_path);
+ FOR i IN (SELECT DISTINCT username FROM user_sys_privs) LOOP
+ util_clob_append(regexp_replace(
+ --source string
+ dbms_metadata.get_granted_ddl('SYSTEM_GRANT', v_current_user),
+ --replace all leading whitespace
+ '^\s*', NULL, 1, 0, 'm'));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END process_system_privileges;
+
+ PROCEDURE process_object_privileges IS
+ BEGIN
+ v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_object_privileges.sql';
+ util_log_start(v_file_path);
+ FOR i IN (SELECT DISTINCT grantee FROM user_tab_privs WHERE grantee = v_current_user) LOOP
+ util_clob_append(regexp_replace(
+ --source string
+ dbms_metadata.get_granted_ddl('OBJECT_GRANT', v_current_user),
+ --replace all leading whitespace
+ '^\s*', NULL, 1, 0, 'm'));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END process_object_privileges;
+
+ BEGIN
+ process_user;
+ process_roles;
+ process_system_privileges;
+ process_object_privileges;
+ END process_user_ddl;
+
+ PROCEDURE process_object_ddl IS
+ TYPE obj_rec_typ IS RECORD (
+ object_type VARCHAR2(128),
+ object_name VARCHAR2(256),
+ file_path VARCHAR2(512));
+ v_rec obj_rec_typ;
+ no_comments_found EXCEPTION;
+ PRAGMA EXCEPTION_INIT(no_comments_found, -31608);
+ BEGIN
+ util_log_start(p_base_path_backend || '/open_objects_cursor');
+ v_query := q'^
+--https://stackoverflow.com/questions/10886450/how-to-generate-entire-ddl-of-an-oracle-schema-scriptable
+--https://stackoverflow.com/questions/3235300/oracles-dbms-metadata-get-ddl-for-object-type-job
+WITH t AS (
+ SELECT CASE object_type
+ --http://psoug.org/reference/dbms_metadata.html
+ WHEN 'UNIFIED AUDIT POLICY' THEN 'AUDIT_OBJ'
+ WHEN 'CONSUMER GROUP' THEN 'RMGR_CONSUMER_GROUP'
+ WHEN 'DATABASE LINK' THEN 'DB_LINK'
+ WHEN 'EVALUATION CONTEXT' THEN 'PROCOBJ'
+ WHEN 'JAVA CLASS' THEN 'JAVA_CLASS'
+ WHEN 'JAVA RESOURCE' THEN 'JAVA_RESOURCE'
+ WHEN 'JAVA SOURCE' THEN 'JAVA_SOURCE'
+ WHEN 'JAVA TYPE' THEN 'JAVA_TYPE'
+ WHEN 'JOB' THEN 'PROCOBJ'
+ WHEN 'JOB CLASS' THEN 'PROCOBJ'
+ WHEN 'MATERIALIZED VIEW' THEN 'MATERIALIZED_VIEW'
+ WHEN 'PACKAGE BODY' THEN 'PACKAGE_BODY'
+ WHEN 'PACKAGE' THEN 'PACKAGE_SPEC'
+ WHEN 'PROGRAM' THEN 'PROCOBJ'
+ WHEN 'QUEUE' THEN 'AQ_QUEUE'
+ WHEN 'RESOURCE PLAN' THEN 'RMGR_PLAN'
+ WHEN 'RULE SET' THEN 'PROCOBJ'
+ WHEN 'RULE' THEN 'PROCOBJ'
+ WHEN 'SCHEDULE' THEN 'PROCOBJ'
+ WHEN 'SCHEDULER GROUP' THEN 'PROCOBJ'
+ WHEN 'TYPE BODY' THEN 'TYPE_BODY'
+ WHEN 'TYPE' THEN 'TYPE_SPEC'
+ ELSE object_type
+ END AS object_type,
+ CASE
+ WHEN object_type like 'JAVA%' AND substr(object_name, 1, 1) = '/' THEN
+ dbms_java.longname(object_name)
+ ELSE
+ object_name
+ END AS object_name
+ FROM ^'
+$if NOT $$debug_on
+$then || 'user_objects'
+$else || '(SELECT MIN(object_name) AS object_name, object_type FROM user_objects GROUP BY object_type)'
+$end || q'^
+ WHERE 1 = 1
+ --- Ignore invalid object types:
+ AND object_type NOT IN ('UNDEFINED','DESTINATION','EDITION','JAVA DATA','WINDOW')
+ --- These objects are included within other object types:
+ AND object_type NOT IN ('INDEX PARTITION','INDEX SUBPARTITION','LOB','LOB PARTITION','TABLE PARTITION','TABLE SUBPARTITION')
+ --- Ignore system-generated types for collection processing:
+ AND NOT (object_type = 'TYPE' AND object_name LIKE 'SYS_PLSQL_%')
+ --- Ignore system-generated sequences for identity columns:
+ AND NOT (object_type = 'SEQUENCE' AND object_name LIKE 'ISEQ$$_%')
+ --- Ignore LOB indices, their DDL is part of the table:
+ AND object_name NOT IN (SELECT index_name FROM user_lobs)
+ --- Ignore nested tables, their DDL is part of their parent table:
+ AND object_name NOT IN (SELECT table_name FROM user_nested_tables)
+ --- Ignore materialized view tables, their DDL is part of the materialized view
+ AND (object_type != 'TABLE' or object_type = 'TABLE' and not exists (select 1 from user_mviews where mview_name = object_name ))
+ --- Ignore indices for materialized view tables
+ AND NOT (object_type = 'INDEX' AND object_name LIKE 'SYS_C_SNAP$_%')
+ --- Set user specific like filters:
+ AND (#TYPE_LIKE_EXPRESSIONS#)
+ AND (#TYPE_NOT_LIKE_EXPRESSIONS#)
+ AND (#NAME_LIKE_EXPRESSIONS#)
+ AND (#NAME_NOT_LIKE_EXPRESSIONS#)
+ ORDER BY
+ object_type,
+ object_name
+)
+SELECT object_type,
+ object_name,
+ '{{BASE_PATH_APP_BACKEND}}/'
+ || replace(lower(
+ CASE
+ WHEN object_type LIKE '%S' THEN object_type || 'ES'
+ WHEN object_type LIKE '%EX' THEN regexp_replace(object_type, 'EX$', 'ICES', 1, 0, 'i')
+ WHEN object_type LIKE '%Y' THEN regexp_replace(object_type, 'Y$', 'IES', 1, 0, 'i')
+ ELSE object_type || 'S'
+ END), ' ', '_')
+ || '/' || object_name
+ || CASE object_type
+ WHEN 'FUNCTION' THEN '.fnc'
+ WHEN 'PACKAGE BODY' THEN '.pkb'
+ WHEN 'PACKAGE' THEN '.pks'
+ WHEN 'PROCEDURE' THEN '.prc'
+ WHEN 'TRIGGER' THEN '.trg'
+ WHEN 'TYPE BODY' THEN '.tpb'
+ WHEN 'TYPE' THEN '.tps'
+ ELSE '.sql'
+ END AS file_path
+ FROM t
+^' ;
+ v_query := replace(
+ v_query,
+ '{{BASE_PATH_APP_BACKEND}}',
+ p_base_path_backend);
+ replace_query_like_expressions(
+ p_like_list => p_object_type_like,
+ p_not_like_list => p_object_type_not_like,
+ p_placeholder_prefix => 'TYPE',
+ p_column_name => 'object_type');
+ replace_query_like_expressions(
+ p_like_list => p_object_name_like,
+ p_not_like_list => p_object_name_not_like,
+ p_placeholder_prefix => 'NAME',
+ p_column_name => 'object_name');
+ util_setup_dbms_metadata;
+ OPEN v_cur FOR v_query;
+ util_log_stop;
+ LOOP
+ FETCH v_cur INTO v_rec;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ util_log_start(v_rec.file_path);
+ CASE v_rec.object_type
+ WHEN 'SEQUENCE' THEN
+ v_ddl_files.sequences_(v_ddl_files.sequences_.count + 1) := v_rec.file_path;
+ WHEN 'TABLE' THEN
+ v_ddl_files.tables_(v_ddl_files.tables_.count + 1) := v_rec.file_path;
+ WHEN 'INDEX' THEN
+ v_ddl_files.indices_(v_ddl_files.indices_.count + 1) := v_rec.file_path;
+ WHEN 'VIEW' THEN
+ v_ddl_files.views_(v_ddl_files.views_.count + 1) := v_rec.file_path;
+ WHEN 'MATERIALIZED_VIEW' THEN
+ v_ddl_files.mviews_(v_ddl_files.mviews_.count + 1) := v_rec.file_path;
+ WHEN 'TYPE_SPEC' THEN
+ v_ddl_files.types_(v_ddl_files.types_.count + 1) := v_rec.file_path;
+ WHEN 'TYPE_BODY' THEN
+ v_ddl_files.type_bodies_(v_ddl_files.type_bodies_.count + 1) := v_rec.file_path;
+ WHEN 'TRIGGER' THEN
+ v_ddl_files.triggers_(v_ddl_files.triggers_.count + 1) := v_rec.file_path;
+ WHEN 'FUNCTION' THEN
+ v_ddl_files.functions_(v_ddl_files.functions_.count + 1) := v_rec.file_path;
+ WHEN 'PROCEDURE' THEN
+ v_ddl_files.procedures_(v_ddl_files.procedures_.count + 1) := v_rec.file_path;
+ WHEN 'PACKAGE_SPEC' THEN
+ v_ddl_files.packages_(v_ddl_files.packages_.count + 1) := v_rec.file_path;
+ WHEN 'PACKAGE_BODY' THEN
+ v_ddl_files.package_bodies_(v_ddl_files.package_bodies_.count + 1) := v_rec.file_path;
+ ELSE
+ v_ddl_files.other_objects_(v_ddl_files.other_objects_.count + 1) := v_rec.file_path;
+ END CASE;
+ CASE
+ WHEN v_rec.object_type = 'VIEW' AND p_object_view_remove_col_list THEN
+ util_clob_append(regexp_replace(
+ -- source string
+ ltrim(dbms_metadata.get_ddl(v_rec.object_type, v_rec.object_name), c_space_crlf),
+ -- regex replace: remove additional column list from the compiler
+ '\(.*\) ', NULL, 1, 1));
+ WHEN v_rec.object_type IN ('TABLE', 'MATERIALIZED_VIEW', 'INDEX', 'SEQUENCE') THEN
+ util_setup_dbms_metadata(p_sqlterminator => false);
+ util_clob_append(replace(q'^BEGIN
+ FOR i IN (SELECT '{{OBJECT_NAME}}' AS object_name FROM dual
+ MINUS
+ SELECT object_name FROM user_objects) LOOP
+ EXECUTE IMMEDIATE q'[
+--------------------------------------------------------------------------------
+^' ,
+ '{{OBJECT_NAME}}',
+ v_rec.object_name)
+ || ltrim(dbms_metadata.get_ddl(v_rec.object_type, v_rec.object_name), c_space_crlf)
+ || replace(q'^
+--------------------------------------------------------------------------------
+ ]';
+ END LOOP;
+END;
+{{/}}
+^' ,
+ '{{/}}',
+ c_slash));
+ util_setup_dbms_metadata;
+ ELSE
+ util_clob_append(ltrim(dbms_metadata.get_ddl(v_rec.object_type, v_rec.object_name), c_space_crlf));
+ END CASE;
+ IF v_rec.object_type IN ('TABLE', 'VIEW', 'MATERIALIZED_VIEW') THEN
+ BEGIN
+ util_clob_append(c_lf || c_lf || regexp_replace(
+ --source string
+ dbms_metadata.get_dependent_ddl('COMMENT', v_rec.object_name),
+ --replace all leading whitespace
+ '^\s*', NULL, 1, 0, 'm'));
+ EXCEPTION
+ WHEN no_comments_found THEN NULL;
+ END;
+ END IF;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_rec.file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_setup_dbms_metadata;
+ util_log_error(v_rec.file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END process_object_ddl;
+
+ PROCEDURE process_object_grants IS
+ TYPE obj_rec_typ IS RECORD (
+ grantor VARCHAR2(128),
+ privilege VARCHAR2(128),
+ object_name VARCHAR2(256),
+ file_path VARCHAR2(512));
+ v_rec obj_rec_typ;
+ BEGIN
+ util_log_start(p_base_path_backend || '/grants:open_cursor');
+ v_query := q'^
+SELECT DISTINCT
+ p.grantor,
+ p.privilege,
+ p.table_name as object_name,
+ '{{BASE_PATH_APP_BACKEND}}/grants/' || p.privilege || '_on_' || p.table_name || '.sql' AS file_path
+FROM user_tab_privs p
+JOIN user_objects o ON p.table_name = o.object_name
+WHERE (#NAME_LIKE_EXPRESSIONS#)
+ AND (#NAME_NOT_LIKE_EXPRESSIONS#)
+ORDER BY
+ privilege,
+ object_name
+^' ;
+ v_query := replace(
+ v_query,
+ '{{BASE_PATH_APP_BACKEND}}',
+ p_base_path_backend);
+ replace_query_like_expressions(
+ p_like_list => p_object_name_like,
+ p_not_like_list => p_object_name_not_like,
+ p_placeholder_prefix => 'NAME',
+ p_column_name => 'o.object_name');
+ OPEN v_cur FOR v_query;
+ util_log_stop;
+ LOOP
+ FETCH v_cur INTO v_rec;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ util_log_start(v_rec.file_path);
+ util_clob_append(ltrim(dbms_metadata.get_dependent_ddl('OBJECT_GRANT', v_rec.object_name, v_rec.grantor), c_space_crlf));
+ v_ddl_files.grants_(v_ddl_files.grants_.count + 1) := v_rec.file_path;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_rec.file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_rec.file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END process_object_grants;
+
+ PROCEDURE process_ref_constraints IS
+ TYPE obj_rec_typ IS RECORD (
+ table_name VARCHAR2(256),
+ constraint_name VARCHAR2(256),
+ file_path VARCHAR2(512));
+ v_rec obj_rec_typ;
+ BEGIN
+ util_log_start(p_base_path_backend || '/ref_constraints:open_cursor');
+ v_query := q'^
+SELECT table_name,
+ constraint_name,
+ '{{BASE_PATH_APP_BACKEND}}/ref_constraints/' || constraint_name || '.sql' AS file_path
+FROM user_constraints
+WHERE constraint_type = 'R'
+ AND (#NAME_LIKE_EXPRESSIONS#)
+ AND (#NAME_NOT_LIKE_EXPRESSIONS#)
+ORDER BY
+ table_name,
+ constraint_name
+^' ;
+ v_query := replace(
+ v_query,
+ '{{BASE_PATH_APP_BACKEND}}',
+ p_base_path_backend);
+ replace_query_like_expressions(
+ p_like_list => p_object_name_like,
+ p_not_like_list => p_object_name_not_like,
+ p_placeholder_prefix => 'NAME',
+ p_column_name => 'table_name');
+ OPEN v_cur FOR v_query;
+ util_log_stop;
+ LOOP
+ FETCH v_cur INTO v_rec;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ util_log_start(v_rec.file_path);
+ util_setup_dbms_metadata(p_sqlterminator => false);
+ util_clob_append(replace(q'^
+BEGIN
+FOR i IN (SELECT '{{CONSTRAINT_NAME}}' AS constraint_name FROM dual
+ MINUS
+ SELECT constraint_name FROM user_constraints) LOOP
+ EXECUTE IMMEDIATE q'[
+--------------------------------------------------------------------------------
+^' ,
+ '{{CONSTRAINT_NAME}}',
+ v_rec.constraint_name)
+ || ltrim(dbms_metadata.get_ddl('REF_CONSTRAINT', v_rec.constraint_name), c_space_crlf)
+ || replace(q'^
+--------------------------------------------------------------------------------
+ ]';
+END LOOP;
+END;
+{{/}}
+^' ,
+ '{{/}}',
+ c_slash));
+ util_setup_dbms_metadata;
+ v_ddl_files.ref_constraints_(v_ddl_files.ref_constraints_.count + 1) := v_rec.file_path;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_rec.file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_setup_dbms_metadata;
+ util_log_error(v_rec.file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END process_ref_constraints;
+
+ PROCEDURE create_backend_install_file IS
+ BEGIN
+ v_file_path := 'scripts/install_backend_generated_by_plex.sql';
+ util_log_start(v_file_path);
+ util_clob_append('/* A T T E N T I O N
+DO NOT TOUCH THIS FILE or set the PLEX.BackApp parameter p_include_object_ddl
+to false - otherwise your changes would be overwritten on next PLEX.BackApp
+call. It is recommended to export your object DDL only ones on initial
+repository creation and then start to use the "files first approach".
+*/
+
+set define off verify off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+
+prompt --install_backend_generated_by_plex
+
+' );
+ FOR i IN 1..v_ddl_files.sequences_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.sequences_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.tables_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.tables_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.ref_constraints_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.ref_constraints_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.types_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.types_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.packages_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.packages_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.views_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.views_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.mviews_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.mviews_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.indices_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.indices_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.type_bodies_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.type_bodies_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.functions_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.functions_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.procedures_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.procedures_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.package_bodies_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.package_bodies_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.triggers_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.triggers_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.grants_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.grants_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.other_objects_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.other_objects_(i)));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END create_backend_install_file;
+
+ $if $$ords_installed $then
+ PROCEDURE process_ords_modules IS
+ v_module_name user_ords_modules.name%type;
+ --
+ PROCEDURE export_ords_modules IS
+ BEGIN
+ util_log_start(p_base_path_web_services || '/open_modules_cursor');
+ OPEN v_cur FOR 'select name from user_ords_modules';
+ util_log_stop;
+ --
+ LOOP
+ FETCH v_cur INTO v_module_name;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ v_file_path := p_base_path_web_services || '/' || v_module_name || '.sql';
+ util_log_start(v_file_path);
+ util_clob_append(ords_export.export_module(p_module_name => v_module_name) || chr(10) || '/');
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ v_ddl_files.ords_modules_(v_ddl_files.ords_modules_.count + 1) := v_file_path;
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END export_ords_modules;
+ --
+ PROCEDURE create_ords_install_file IS
+ BEGIN
+ v_file_path := 'scripts/install_web_services_generated_by_ords.sql';
+ util_log_start(v_file_path);
+ util_clob_append('/* A T T E N T I O N
+DO NOT TOUCH THIS FILE or set the PLEX.BackApp parameter p_include_ords_modules
+to false - otherwise your changes would be overwritten on next PLEX.BackApp
+call.
+*/
+
+set define off verify off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+
+prompt --install_web_services_generated_by_ords
+
+' );
+ FOR i IN 1..v_ddl_files.ords_modules_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.ords_modules_(i)));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END create_ords_install_file;
+
+ BEGIN
+ export_ords_modules;
+ IF v_ddl_files.ords_modules_.count > 0 THEN
+ create_ords_install_file;
+ END IF;
+ END process_ords_modules;
+ $end
+
+ PROCEDURE process_data IS
+ TYPE obj_rec_typ IS RECORD (
+ table_name VARCHAR2(256),
+ pk_columns VARCHAR2(4000));
+ v_rec obj_rec_typ;
+ BEGIN
+ util_log_start(p_base_path_data || '/open_tables_cursor');
+ v_query := q'^
+SELECT table_name,
+ (SELECT LISTAGG(column_name, ', ') WITHIN GROUP(ORDER BY position)
+ FROM user_cons_columns
+ WHERE constraint_name = (SELECT constraint_name
+ FROM user_constraints c
+ WHERE constraint_type = 'P'
+ AND c.table_name = t.table_name)
+ ) AS pk_columns
+ FROM user_tables t
+ WHERE table_name IN (SELECT table_name FROM user_tables
+ MINUS
+ SELECT table_name FROM user_external_tables)
+ AND (#NAME_LIKE_EXPRESSIONS#)
+ AND (#NAME_NOT_LIKE_EXPRESSIONS#)
+ ORDER BY
+ table_name
+^' ;
+ replace_query_like_expressions(
+ p_like_list => p_data_table_name_like,
+ p_not_like_list => p_data_table_name_not_like,
+ p_placeholder_prefix => 'NAME',
+ p_column_name => 'table_name');
+ OPEN v_cur FOR v_query;
+ util_log_stop;
+ --
+ util_log_start(p_base_path_data || '/get_scn');
+ v_data_timestamp := util_calc_data_timestamp(nvl(p_data_as_of_minutes_ago, 0));
+ v_data_scn := timestamp_to_scn(v_data_timestamp);
+ util_log_stop;
+ LOOP
+ FETCH v_cur INTO v_rec;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ v_file_path := p_base_path_data || '/' || v_rec.table_name || '.csv';
+ util_log_start(v_file_path);
+ util_clob_query_to_csv(
+ p_query => 'SELECT * FROM ' || v_rec.table_name || ' AS OF SCN ' || v_data_scn ||
+ CASE
+ WHEN v_rec.pk_columns IS NOT NULL
+ THEN ' ORDER BY ' || v_rec.pk_columns
+ ELSE NULL
+ END,
+ p_max_rows => p_data_max_rows);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END process_data;
+
+ PROCEDURE create_template_files IS
+ v_file_template VARCHAR2(32767 CHAR);
+ PROCEDURE readme_file IS
+ BEGIN
+ v_file_template := q'^Your Global README File
+=======================
+
+It is a good practice to have a README file in the root of your project with
+a high level overview of your application. Put the more detailed docs in the
+docs folder.
+
+You can start with a copy of this file. Rename it to README.md and try to use
+Markdown when writing your content - this has many benefits and you don't waist
+time by formatting your docs. If you are unsure have a look at some projects at
+[Github](https://github.com) or any other code hosting platform.
+
+Depending on your options when calling `plex.backapp` these files are generated
+for you:
+
+- scripts/install_backend_generated_by_plex.sql
+- scripts/install_frontend_generated_by_apex.sql
+- scripts/install_web_services_generated_by_ords.sql
+
+Do not touch these generated install files. They will be overwritten on each
+plex call. Depending on your call parameters it would be ok to modify the file
+install_backend_generated_by_plex - especially when you follow the files first
+approach and export your schema DDL only ones to have a starting point for you
+repository.
+
+If you need to do modifications for the install process then have a look at the
+following templates - they call the generated files and you can do your own
+stuff before or after the calls.
+
+- scripts/templates/1_export_app_from_DEV.bat
+- scripts/templates/2_install_app_into_INT.bat
+- scripts/templates/3_install_app_into_PROD.bat
+- scripts/templates/export_app_custom_code.sql
+- scripts/templates/install_app_custom_code.sql
+
+If you want to use these files please make a copy into the scripts directory
+and modify it to your needs. Doing it this way your changes are overwrite save.
+
+[Feedback is welcome]({{PLEX_URL}}/issues/new)
+^' ;
+ v_file_path := 'plex_README.md';
+ util_log_start(v_file_path);
+ util_clob_append(replace(
+ v_file_template,
+ '{{PLEX_URL}}',
+ c_plex_url));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END readme_file;
+
+ PROCEDURE export_batch_file IS
+ BEGIN
+ v_file_template := q'^rem Template generated by PLEX version {{PLEX_VERSION}}
+rem More infos here: {{PLEX_URL}}
+
+{{@}}echo off
+setlocal
+set "areyousure=N"
+
+rem ### BEGIN CONFIG ###########################################################
+rem Align substrings to your operating system locale: (how it works: https://stackoverflow.com/a/23558738)
+set "datetime=%DATE:~6,4%%DATE:~3,2%%DATE:~0,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
+set "datetime=%datetime: =0%"
+set "systemrole={{SYSTEMROLE}}"
+set "connection=localhost:1521/xepdb1"
+set "app_id={{APP_ID}}"
+set "app_schema={{APP_OWNER}}"
+set "scriptfile=export_app_custom_code.sql"
+set "logfile=logs/%datetime%_export_app_%app_id%_from_%app_schema%_at_%systemrole%.log"
+set "zipfile=BackApp_%app_id%_from_%app_schema%_at_%systemrole%_%datetime%.zip"
+rem ### END CONFIG #############################################################
+
+:PROMPT
+echo(
+echo(
+set /p "areyousure=Run %scriptfile% on %app_schema%@%systemrole%(%connection%) [Y/N]? " || set "areyousure=N"
+if /i %areyousure% neq y goto END
+set NLS_LANG=AMERICAN_AMERICA.UTF8
+set /p "password=Please enter password for %app_schema% [default = oracle]: " || set "password=oracle"
+echo This is the runlog for %scriptfile% on %app_schema%@%systemrole%(%connection%) > %logfile%
+echo exit | sqlplus -S %app_schema%/%password%@%connection% ^
+{{@}}%scriptfile% ^
+%logfile% ^
+%zipfile% ^
+%app_id%
+
+if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-( >> %logfile%
+if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-(
+if %errorlevel% neq 0 goto END
+
+echo Start Post Processing on Client >> %logfile%
+echo Start Post Processing on Client
+echo ========================================================================= >> %logfile%
+echo =========================================================================
+
+echo Decode file %zipfile%.base64 >> %logfile%
+echo Decode file %zipfile%.base64
+certutil -decode %zipfile%.base64 %zipfile% >> %logfile%
+if %errorlevel% neq 0 echo ERROR: Unable to decode %zipfile%.base64 :-( >> %logfile%
+if %errorlevel% neq 0 echo ERROR: Unable to decode %zipfile%.base64 :-(
+if %errorlevel% neq 0 goto END
+del %zipfile%.base64
+
+echo Unzip file %zipfile% >> %logfile%
+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%
+if %errorlevel% neq 0 echo ERROR: Unable to unzip %zipfile% :-(
+if %errorlevel% neq 0 goto END
+
+echo Delete file %zipfile% >> %logfile%
+echo Delete file %zipfile%
+del %zipfile%
+
+echo ========================================================================= >> %logfile%
+echo =========================================================================
+echo Post Processing DONE >> %logfile%
+echo Post Processing DONE
+echo( >> %logfile%
+echo(
+
+:END
+rem Remove "pause" for fully automated setup:
+pause
+if %errorlevel% neq 0 exit /b %errorlevel%
+^' ;
+ v_file_path := 'scripts/templates/1_export_app_from_DEV.bat';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{SYSTEMROLE}}', 'DEV',
+ $if $$apex_installed $then
+ '{{APP_OWNER}}', v_app_owner,
+ '{{APP_ID}}', p_app_id,
+ $end
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END export_batch_file;
+
+ PROCEDURE export_sq_file IS
+ BEGIN
+ v_file_template := q'^/*******************************************************************************
+Template generated by PLEX version {{PLEX_VERSION}}
+More infos here: {{PLEX_URL}}
+
+You need to provide three parameters:
+- logfile: path to the logfile for the script console output
+- zipfile: path to the export zip file - this will be created with the spool command
+- app_id: the APEX app ID you want to export - only relevant when you have APEX installed
+
+Example call for Windows:
+
+ echo exit | sqlplus -S app_schema/password@connection ^
+ {{@}}export_app_custom_code.sql ^
+ my_logfile.log ^
+ my_zipfile.zip ^
+ 100
+
+*******************************************************************************/
+
+set timing on
+timing start EXPORT_APP
+set timing off verify off feedback off heading off
+set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
+whenever sqlerror exit sql.sqlcode rollback
+-- whenever oserror exit failure rollback
+define logfile = "&1."
+define zipfile = "&2..base64"
+spool "&logfile" append
+variable contents clob
+variable app_id number
+BEGIN
+ :app_id := &3;
+END;
+{{/}}
+
+prompt
+prompt Start Export from Database
+prompt =========================================================================
+
+prompt Do the app export and save to zip file
+prompt ATTENTION: Depending on your options this could take some time ...
+BEGIN
+ :contents := plex.to_base64(plex.to_zip(plex.backapp(
+ -- These are the defaults - align it to your needs:^';
+ $if $$apex_installed $then
+ v_file_template := v_file_template || q'^
+ p_app_id => :app_id,
+ p_app_date => true,
+ p_app_public_reports => true,
+ p_app_private_reports => false,
+ p_app_notifications => false,
+ p_app_translations => true,
+ p_app_pkg_app_mapping => false,
+ p_app_original_ids => false,
+ p_app_subscriptions => true,
+ p_app_comments => true,
+ p_app_supporting_objects => null,
+ p_app_include_single_file => false,
+ p_app_build_status_run_only => false,^';
+ $end
+ $if $$ords_installed $then
+ v_file_template := v_file_template || q'^
+
+ p_include_ords_modules => false,^';
+ $end
+ v_file_template := v_file_template || q'^
+
+ p_include_object_ddl => true,
+ p_object_type_like => null,
+ p_object_type_not_like => null,
+ p_object_name_like => null,
+ p_object_name_not_like => null,
+
+ p_include_data => false,
+ p_data_as_of_minutes_ago => 0,
+ p_data_max_rows => 1000,
+ p_data_table_name_like => null,
+ p_data_table_name_not_like => null,
+
+ p_include_templates => true,
+ p_include_runtime_log => true,
+ p_include_error_log => true,
+ p_base_path_backend => 'app_backend',
+ p_base_path_frontend => 'app_frontend',
+ p_base_path_data => 'app_data')));
+END;
+{{/}}
+
+prompt Spool the resulting base64 encoded zip file to client disk
+spool off
+set termout off
+spool "&zipfile"
+print contents
+set termout on
+spool "&logfile." append
+timing stop EXPORT_APP
+prompt =========================================================================
+prompt Export DONE :-)
+prompt
+^' ;
+ v_file_path := 'scripts/templates/export_app_custom_code.sql';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{/}}', c_slash,
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END export_sq_file;
+
+ PROCEDURE install_batch_files IS
+ BEGIN
+ v_file_template := q'^rem Template generated by PLEX version {{PLEX_VERSION}}
+rem More infos here: {{PLEX_URL}}
+
+{{@}}echo off
+setlocal
+set "areyousure=N"
+
+rem ### BEGIN CONFIG ###########################################################
+rem Align substrings to your operating system locale: (how it works: https://stackoverflow.com/a/23558738)
+set "datetime=%DATE:~6,4%%DATE:~3,2%%DATE:~0,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
+set "datetime=%datetime: =0%"
+set "systemrole={{SYSTEMROLE}}"
+set "connection=localhost:1521/xepdb1"
+set "app_id={{APP_ID}}"
+set "app_alias={{APP_ALIAS}}"
+set "app_schema={{APP_OWNER}}"
+set "app_workspace={{APP_WORKSPACE}}"
+set "scriptfile=install_app_custom_code.sql"
+set "logfile=logs/%datetime%_install_app_%app_id%_into_%app_schema%_at_%systemrole%.log"
+rem ### END CONFIG #############################################################
+
+:PROMPT
+echo.
+echo.
+set /p "areyousure=Run %scriptfile% on %app_schema%@%systemrole%(%connection%) [Y/N]? " || set "areyousure=N"
+if /i %areyousure% neq y goto END
+set NLS_LANG=AMERICAN_AMERICA.UTF8
+set /p "password=Please enter password for %app_schema% [default = oracle]: " || set "password=oracle"
+echo This is the runlog for %scriptfile% on %app_schema%@%systemrole%(%connection%) > %logfile%
+echo exit | sqlplus -S %app_schema%/%password%@%connection% ^
+{{@}}%scriptfile% ^
+%logfile% ^
+%app_id% ^
+%app_alias% ^
+%app_schema% ^
+%app_workspace%
+
+if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-( >> %logfile%
+if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-(
+
+:END
+rem Remove "pause" for fully automated setup:
+pause
+if %errorlevel% neq 0 exit /b %errorlevel%
+^' ;
+ v_file_path := 'scripts/templates/2_install_app_into_INT.bat';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{SYSTEMROLE}}', 'INT',
+ $if $$apex_installed $then
+ '{{APP_ID}}', p_app_id,
+ '{{APP_ALIAS}}', v_app_alias,
+ '{{APP_OWNER}}', v_app_owner,
+ '{{APP_WORKSPACE}}', v_app_workspace,
+ $end
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+
+ v_file_path := 'scripts/templates/3_install_app_into_PROD.bat';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{SYSTEMROLE}}', 'PROD',
+ $if $$apex_installed $then
+ '{{APP_ID}}', p_app_id,
+ '{{APP_ALIAS}}', v_app_alias,
+ '{{APP_OWNER}}', v_app_owner,
+ '{{APP_WORKSPACE}}', v_app_workspace,
+ $end
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END install_batch_files;
+
+ PROCEDURE install_sql_file IS
+ BEGIN
+ v_file_template := q'^/*******************************************************************************
+Template generated by PLEX version {{PLEX_VERSION}}
+More infos here: {{PLEX_URL}}
+
+You need to provide five parameters:
+- logfile: path to the logfile for the script console output
+- app_id: the APEX app ID you want to have for the installation
+- app_alias: the alias you want to have for your app
+- app_schema: the parsing schema for your app
+- app_workspace: the workspace where your app should be installed
+
+Example call for Windows:
+
+ echo exit | sqlplus -S app_schema/password@connection ^
+ {{@}}install_app_custom_code.sql ^
+ my_logfile.log ^
+ 100 ^
+ MY_APP_ALIAS ^
+ MY_APP_SCHEMA ^
+ MY_APP_WORKSPACE
+
+*******************************************************************************/
+
+set timing on define on
+timing start INSTALL_APP
+set timing off verify off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+-- whenever oserror exit failure rollback
+define logfile = "&1"
+spool "&logfile" append
+variable app_id varchar2(100)
+variable app_alias varchar2(100)
+variable app_schema varchar2(100)
+variable app_workspace varchar2(100)
+BEGIN
+ :app_id := &2;
+ :app_alias := '&3';
+ :app_schema := '&4';
+ :app_workspace := '&5';
+END;
+{{/}}
+set define off
+
+
+prompt
+prompt Start Installation
+prompt =========================================================================
+
+prompt Install Backend
+{{@}}install_backend_generated_by_plex.sql
+
+prompt Compile Invalid Objects
+BEGIN
+ dbms_utility.compile_schema(
+ schema => user,
+ compile_all => false,
+ reuse_settings => true);
+END;
+{{/}}
+
+prompt Check Invalid Objects
+DECLARE
+ v_count PLS_INTEGER;
+ v_objects VARCHAR2(4000);
+BEGIN
+ SELECT COUNT(*), chr(10) ||
+ listagg('- ' || object_name || ' (' || object_type || ')', chr(10)) within GROUP(ORDER BY object_name)
+ INTO v_count, v_objects
+ FROM user_objects
+ WHERE status = 'INVALID';
+ IF v_count > 0 THEN
+ raise_application_error(-20000, chr(10) || chr(10) ||
+ 'Found ' || v_count || ' invalid object' || CASE WHEN v_count > 1 THEN 's' END ||
+ ' :-( ' || chr(10) || '=============================' || v_objects || chr(10) || chr(10) );
+ END IF;
+END;
+{{/}}
+
+prompt Install Web Services
+{{@}}install_web_services_generated_by_ords.sql
+
+prompt Install Frontend
+BEGIN
+ apex_application_install.set_workspace_id(APEX_UTIL.find_security_group_id(:app_workspace));
+ apex_application_install.set_application_alias(:app_alias);
+ apex_application_install.set_application_id(:app_id);
+ apex_application_install.set_schema(:app_schema);
+ apex_application_install.generate_offset;
+END;
+{{/}}
+{{@}}install_frontend_generated_by_apex.sql
+
+timing stop INSTALL_APP
+prompt =========================================================================
+prompt Installation DONE :-)
+prompt
+^' ;
+ v_file_path := 'scripts/templates/install_app_custom_code.sql';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{/}}', c_slash,
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END install_sql_file;
+
+ BEGIN
+ readme_file;
+ export_batch_file;
+ export_sq_file;
+ install_batch_files;
+ install_sql_file;
+ END create_template_files;
+
+ PROCEDURE create_directory_keepers IS
+ v_the_point VARCHAR2(30) := '. < this is the point ;-)';
+ BEGIN
+ v_file_path := 'docs/_save_your_docs_here.txt';
+ util_log_start(v_file_path);
+ util_clob_append(v_the_point);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ --
+ v_file_path := 'scripts/logs/_spool_your_script_logs_here.txt';
+ util_log_start(v_file_path);
+ util_clob_append(v_the_point);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ --
+ v_file_path := 'tests/_save_your_tests_here.txt';
+ util_log_start(v_file_path);
+ util_clob_append(v_the_point);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END create_directory_keepers;
+
+ PROCEDURE finish IS
+ BEGIN
+ util_ensure_unique_file_names(v_export_files);
+ IF p_include_error_log THEN
+ util_clob_create_error_log(v_export_files);
+ END IF;
+ IF p_include_runtime_log THEN
+ util_clob_create_runtime_log(v_export_files);
+ END IF;
+ END finish;
+
+BEGIN
+ init;
+ $if $$apex_installed $then
+ check_owner;
+ IF p_app_id IS NOT NULL THEN
+ process_apex_app;
+ END IF;
+ $end
+ IF p_include_object_ddl THEN
+ process_user_ddl;
+ process_object_ddl;
+ $if NOT $$debug_on $then
+ -- excluded in debug mode (potential long running object types)
+ process_object_grants;
+ process_ref_constraints;
+ $end
+ create_backend_install_file;
+ END IF;
+ $if $$ords_installed $then
+ IF p_include_ords_modules THEN
+ process_ords_modules;
+ END IF;
+ $end
+ IF p_include_data THEN
+ process_data;
+ END IF;
+ IF p_include_templates THEN
+ create_template_files;
+ create_directory_keepers;
+ END IF;
+ finish;
+ RETURN v_export_files;
+END backapp;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE add_query (
+ p_query VARCHAR2,
+ p_file_name VARCHAR2,
+ p_max_rows NUMBER DEFAULT 1000)
+IS
+ v_index PLS_INTEGER;
+BEGIN
+ v_index := g_queries.count + 1;
+ g_queries(v_index).query := p_query;
+ g_queries(v_index).file_name := p_file_name;
+ g_queries(v_index).max_rows := p_max_rows;
+END add_query;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION queries_to_csv (
+ p_delimiter IN VARCHAR2 DEFAULT ',',
+ p_quote_mark IN VARCHAR2 DEFAULT '"',
+ p_header_prefix IN VARCHAR2 DEFAULT NULL,
+ p_include_runtime_log IN BOOLEAN DEFAULT true,
+ p_include_error_log IN BOOLEAN DEFAULT true)
+RETURN tab_export_files IS
+ v_export_files tab_export_files;
+
+ PROCEDURE init IS
+ BEGIN
+ IF g_queries.count = 0 THEN
+ raise_application_error(
+ -20201,
+ 'You need first to add queries by using plex.add_query. Calling plex.queries_to_csv clears the global queries array for subsequent processing.');
+ END IF;
+ util_log_init(p_module => 'plex.queries_to_csv');
+ util_log_start('init');
+ v_export_files := NEW tab_export_files();
+ util_log_stop;
+ END init;
+
+ PROCEDURE process_queries IS
+ BEGIN
+ FOR i IN g_queries.first..g_queries.last LOOP
+ BEGIN
+ util_log_start('process_query ' || TO_CHAR(i) || ': ' || g_queries(i).file_name);
+ util_clob_query_to_csv(
+ p_query => g_queries(i).query,
+ p_max_rows => g_queries(i).max_rows,
+ p_delimiter => p_delimiter,
+ p_quote_mark => p_quote_mark,
+ p_header_prefix => p_header_prefix);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => g_queries(i).file_name || '.csv');
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(g_queries(i).file_name);
+ END;
+ END LOOP;
+ END process_queries;
+
+ PROCEDURE finish IS
+ BEGIN
+ g_queries.DELETE;
+ util_ensure_unique_file_names(v_export_files);
+ IF p_include_error_log THEN
+ util_clob_create_error_log(v_export_files);
+ END IF;
+ IF p_include_runtime_log THEN
+ util_clob_create_runtime_log(v_export_files);
+ END IF;
+ END finish;
+
+BEGIN
+ init;
+ process_queries;
+ finish;
+ RETURN v_export_files;
+EXCEPTION
+ WHEN others THEN
+ g_queries.DELETE;
+END queries_to_csv;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION to_zip (p_file_collection IN tab_export_files) RETURN BLOB IS
+ v_zip BLOB;
+BEGIN
+ dbms_lob.createtemporary(v_zip, true);
+ util_log_start('post processing with to_zip: ' || p_file_collection.count || ' files');
+ FOR i IN 1..p_file_collection.count LOOP
+ util_zip_add_file(
+ p_zipped_blob => v_zip,
+ p_name => p_file_collection(i).name,
+ p_content => util_clob_to_blob(p_file_collection(i).contents));
+ END LOOP;
+ util_zip_finish(v_zip);
+ util_log_stop;
+ util_log_calc_runtimes;
+ RETURN v_zip;
+END to_zip;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Tim Hall, see https://oracle-base.com/dba/miscellaneous/base64encode.sql
+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
+BEGIN
+ util_log_start('post processing with to_base64');
+ FOR i IN 0 .. TRUNC((DBMS_LOB.getlength(p_blob) - 1 ) / v_step) LOOP
+ v_bas64 := v_bas64 || UTL_RAW.cast_to_varchar2(UTL_ENCODE.base64_encode(DBMS_LOB.substr(p_blob, v_step, i * v_step + 1)));
+ END LOOP;
+ util_log_stop;
+ util_log_calc_runtimes;
+ RETURN v_bas64;
+END;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION view_error_log RETURN tab_error_log PIPELINED IS
+BEGIN
+ FOR i IN 1..g_errlog.count LOOP
+ PIPE ROW (g_errlog(i));
+ END LOOP;
+END view_error_log;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION view_runtime_log RETURN tab_runtime_log PIPELINED IS
+ v_return rec_runtime_log;
+BEGIN
+ v_return.overall_start_time := g_runlog.start_time;
+ v_return.overall_run_time := round(g_runlog.run_time, 3);
+ FOR i IN 1..g_runlog.data.count LOOP
+ v_return.step := i;
+ v_return.elapsed := round(g_runlog.data(i).elapsed, 3);
+ v_return.execution := round(g_runlog.data(i).execution, 6);
+ v_return.module := g_runlog.module;
+ v_return.action := g_runlog.data(i).action;
+ PIPE ROW (v_return);
+ END LOOP;
+END view_runtime_log;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+BEGIN
+ IF dbms_lob.istemporary(g_clob) = 0 THEN
+ dbms_lob.createtemporary(g_clob, true);
+ END IF;
+END plex;
+/
show errors
+
prompt ==================================
prompt Installation Done
prompt
diff --git a/PLEX.pkb b/src/PLEX.pkb
old mode 100755
new mode 100644
similarity index 76%
rename from PLEX.pkb
rename to src/PLEX.pkb
index e6bd8f6..04c21d7
--- a/PLEX.pkb
+++ b/src/PLEX.pkb
@@ -1,2354 +1,2518 @@
-CREATE OR REPLACE PACKAGE BODY plex IS
-
---------------------------------------------------------------------------------------------------------------------------------
--- CONSTANTS, TYPES
---------------------------------------------------------------------------------------------------------------------------------
-
-c_tab CONSTANT VARCHAR2(1) := chr(9);
-c_cr CONSTANT VARCHAR2(1) := chr(13);
-c_lf CONSTANT VARCHAR2(1) := chr(10);
-c_crlf CONSTANT VARCHAR2(2) := chr(13) || chr(10);
-c_at CONSTANT VARCHAR2(1) := '@';
-c_hash CONSTANT VARCHAR2(1) := '#';
-c_slash CONSTANT VARCHAR2(1) := '/';
-c_vc2_max_size CONSTANT PLS_INTEGER := 32767;
-c_zip_local_file_header CONSTANT RAW(4) := hextoraw('504B0304');
-c_zip_end_of_central_directory CONSTANT RAW(4) := hextoraw('504B0506');
-
-TYPE tab_errlog IS TABLE OF rec_error_log INDEX BY BINARY_INTEGER;
-
-TYPE rec_runlog_step IS RECORD (
- action app_info_text,
- start_time TIMESTAMP(6),
- stop_time TIMESTAMP(6),
- elapsed NUMBER,
- execution NUMBER);
-TYPE tab_runlog_step IS TABLE OF rec_runlog_step INDEX BY BINARY_INTEGER;
-
-TYPE rec_runlog IS RECORD (
- module app_info_text,
- start_time TIMESTAMP(6),
- stop_time TIMESTAMP(6),
- run_time NUMBER,
- measured_time NUMBER,
- unmeasured_time NUMBER,
- data tab_runlog_step);
-TYPE rec_queries IS RECORD (--
- query VARCHAR2(32767 CHAR),
- file_name VARCHAR2(256 CHAR),
- max_rows NUMBER DEFAULT 100000);
-TYPE tab_queries IS TABLE OF rec_queries INDEX BY BINARY_INTEGER;
-
-TYPE tab_file_list_lookup IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(256);
-
-TYPE rec_ddl_files IS RECORD (
- sequences_ tab_vc1k,
- tables_ tab_vc1k,
- ref_constraints_ tab_vc1k,
- indices_ tab_vc1k,
- views_ tab_vc1k,
- types_ tab_vc1k,
- type_bodies_ tab_vc1k,
- triggers_ tab_vc1k,
- functions_ tab_vc1k,
- procedures_ tab_vc1k,
- packages_ tab_vc1k,
- package_bodies_ tab_vc1k,
- grants_ tab_vc1k,
- other_objects_ tab_vc1k);
-
-
--- GLOBAL VARIABLES
-g_clob CLOB;
-g_clob_vc_cache VARCHAR2(32767char);
-g_errlog tab_errlog;
-g_runlog rec_runlog;
-g_queries tab_queries;
-
-
-
---------------------------------------------------------------------------------------------------------------------------------
--- UTILITIES (forward declarations, only compiled when not public)
---------------------------------------------------------------------------------------------------------------------------------
-
-$if not $$utils_public $then
-FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2;
-
-FUNCTION util_string_to_bool (
- p_bool_string IN VARCHAR2,
- p_default IN BOOLEAN)
-RETURN BOOLEAN;
-
-FUNCTION util_split (
- p_string IN VARCHAR2,
- p_delimiter IN VARCHAR2 DEFAULT ',')
-RETURN tab_vc32k;
-
-FUNCTION util_join (
- p_array IN tab_vc32k,
- p_delimiter IN VARCHAR2 DEFAULT ',')
-RETURN VARCHAR2;
-
-FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB;
-
-/*
-ZIP UTILS
-- The following four zip utilities are copied from this article:
- - Blog: https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/
- - Source: https://technology.amis.nl/wp-content/uploads/2010/06/as_zip10.txt
-- Copyright (c) 2010, 2011 by Anton Scheffer (MIT license)
-- Thank you for sharing this Anton :-)
-*/
-FUNCTION util_zip_blob_to_num (
- p_blob IN BLOB,
- p_len IN INTEGER,
- p_pos IN INTEGER)
-RETURN NUMBER;
-
-FUNCTION util_zip_little_endian (
- p_big IN NUMBER,
- p_bytes IN PLS_INTEGER := 4)
-RETURN RAW;
-
-PROCEDURE util_zip_add_file (
- p_zipped_blob IN OUT BLOB,
- p_name IN VARCHAR2,
- p_content IN BLOB);
-
-PROCEDURE util_zip_finish (p_zipped_blob IN OUT BLOB);
-
-FUNCTION util_multi_replace (
- p_source_string VARCHAR2,
- p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
- p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
- p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
- p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
- p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
- p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
- p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
- p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
- p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
- p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
- p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
- p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
-RETURN VARCHAR2;
-
-FUNCTION util_set_build_status_run_only (p_app_export_sql IN CLOB) RETURN CLOB;
-
-FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP;
-
-PROCEDURE util_setup_dbms_metadata (
- p_pretty IN BOOLEAN DEFAULT true,
- p_constraints IN BOOLEAN DEFAULT true,
- p_ref_constraints IN BOOLEAN DEFAULT false,
- p_partitioning IN BOOLEAN DEFAULT true,
- p_tablespace IN BOOLEAN DEFAULT false,
- p_storage IN BOOLEAN DEFAULT false,
- 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);
-
---------------------------------------------------------------------------------------------------------------------------------
--- The following tools are working on the global private package variables g_clob, g_clob_varchar_cache, g_runlog and g_queries
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_clob_append (p_content IN VARCHAR2);
-
-PROCEDURE util_clob_append (p_content IN CLOB);
-
-PROCEDURE util_clob_flush_cache;
-
-PROCEDURE util_clob_add_to_export_files (
- p_export_files IN OUT NOCOPY tab_export_files,
- p_name IN VARCHAR2);
-
-PROCEDURE util_clob_query_to_csv (
- p_query IN VARCHAR2,
- p_max_rows IN NUMBER DEFAULT 1000,
- p_delimiter IN VARCHAR2 DEFAULT ',',
- p_quote_mark IN VARCHAR2 DEFAULT '"',
- p_header_prefix IN VARCHAR2 DEFAULT NULL);
-
-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);
-
-PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files);
-
-PROCEDURE util_log_init (p_module IN VARCHAR2);
-
-PROCEDURE util_log_start (p_action IN VARCHAR2);
-
-PROCEDURE util_log_error (p_name VARCHAR2);
-
-PROCEDURE util_log_stop;
-
-FUNCTION util_log_get_runtime (
- p_start IN TIMESTAMP,
- p_stop IN TIMESTAMP)
-RETURN NUMBER;
-
-PROCEDURE util_log_calc_runtimes;
-
-$end
-
-
-
---------------------------------------------------------------------------------------------------------------------------------
--- UTILITIES MAIN CODE
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2 IS
-BEGIN
- RETURN CASE WHEN p_bool THEN 'TRUE' ELSE 'FALSE' END;
-END util_bool_to_string;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_string_to_bool (
- p_bool_string IN VARCHAR2,
- p_default IN BOOLEAN)
-RETURN BOOLEAN IS
- v_bool_string VARCHAR2(1 CHAR);
- v_return BOOLEAN;
-BEGIN
- v_bool_string := upper(substr(p_bool_string, 1, 1));
- v_return :=
- CASE
- WHEN v_bool_string IN ('1', 'Y', 'T') THEN
- true
- WHEN v_bool_string IN ('0', 'N', 'F') THEN
- false
- ELSE p_default
- END;
- RETURN v_return;
-END util_string_to_bool;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_split (
- p_string IN VARCHAR2,
- p_delimiter IN VARCHAR2 DEFAULT ',')
-RETURN tab_vc32k IS
- v_return tab_vc32k := tab_vc32k();
- v_offset PLS_INTEGER := 1;
- v_index PLS_INTEGER := instr(p_string, p_delimiter, v_offset);
- v_delimiter_length PLS_INTEGER := length(p_delimiter);
- v_string_length CONSTANT PLS_INTEGER := length(p_string);
- v_count PLS_INTEGER := 1;
-
- PROCEDURE add_value (p_value VARCHAR2) IS
- BEGIN
- v_return.extend;
- v_return(v_count) := p_value;
- v_count := v_count + 1;
- END add_value;
-
-BEGIN
- WHILE v_index > 0 LOOP
- add_value(trim(substr(p_string, v_offset, v_index - v_offset)));
- v_offset := v_index + v_delimiter_length;
- v_index := instr(p_string, p_delimiter, v_offset);
- END LOOP;
- IF v_string_length - v_offset + 1 > 0 THEN
- add_value(trim(substr(p_string, v_offset, v_string_length - v_offset + 1)));
- END IF;
- RETURN v_return;
-END util_split;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_join (
- p_array IN tab_vc32k,
- p_delimiter IN VARCHAR2 DEFAULT ',')
-RETURN VARCHAR2 IS
- v_return VARCHAR2(32767);
-BEGIN
- IF p_array IS NOT NULL AND p_array.count > 0 THEN
- v_return := p_array(1);
- FOR i IN 2 ..p_array.count LOOP
- v_return := v_return || p_delimiter || p_array(i);
- END LOOP;
- END IF;
- RETURN v_return;
-EXCEPTION
- WHEN value_error THEN
- RETURN v_return;
-END util_join;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB IS
- v_blob BLOB;
- v_lang_context INTEGER := dbms_lob.default_lang_ctx;
- v_warning INTEGER := dbms_lob.warn_inconvertible_char;
- v_dest_offset INTEGER := 1;
- v_src_offset INTEGER := 1;
-BEGIN
- IF p_clob IS NOT NULL THEN
- dbms_lob.createtemporary(v_blob, true);
- dbms_lob.converttoblob(
- dest_lob => v_blob,
- src_clob => p_clob,
- amount => dbms_lob.lobmaxsize,
- dest_offset => v_dest_offset,
- src_offset => v_src_offset,
- blob_csid => nls_charset_id('AL32UTF8'),
- lang_context => v_lang_context,
- warning => v_warning);
- END IF;
- RETURN v_blob;
-END util_clob_to_blob;
-
---------------------------------------------------------------------------------------------------------------------------------
-
--- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
-FUNCTION util_zip_blob_to_num (
- p_blob IN BLOB,
- p_len IN INTEGER,
- p_pos IN 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 util_zip_blob_to_num;
-
---------------------------------------------------------------------------------------------------------------------------------
-
--- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
-FUNCTION util_zip_little_endian (
- p_big IN NUMBER,
- p_bytes IN 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 util_zip_little_endian;
-
---------------------------------------------------------------------------------------------------------------------------------
-
--- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
-PROCEDURE util_zip_add_file (
- p_zipped_blob IN OUT BLOB,
- p_name IN VARCHAR2,
- p_content IN 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;
- t_name := utl_i18n.string_to_raw(p_name, 'AL32UTF8');
- dbms_lob.append(
- p_zipped_blob,
- utl_raw.concat(
- c_zip_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,
- util_zip_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
- util_zip_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
- util_zip_little_endian(t_clen), -- compressed size
- util_zip_little_endian(t_len), -- uncompressed size
- util_zip_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 util_zip_add_file;
-
---------------------------------------------------------------------------------------------------------------------------------
-
--- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
-PROCEDURE util_zip_finish (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_zip_local_file_header), t_offs) = c_zip_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 + util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26) - 1)
- IN (hextoraw('2F')/*slash*/, hextoraw('5C')/*backslash*/)
- THEN hextoraw('10000000') -- a directory/folder
- ELSE hextoraw('2000B681') -- a file
- END, -- external file attributes
- util_zip_little_endian(t_offs - 1), -- relative offset of local file header
- dbms_lob.substr(
- p_zipped_blob,
- util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26),
- t_offs + 30))); -- File name
- t_offs := t_offs + 30
- + util_zip_blob_to_num(p_zipped_blob, 4, t_offs + 18) -- compressed size
- + util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26) -- file name length
- + util_zip_blob_to_num(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_zip_end_of_central_directory, -- end of central directory signature
- hextoraw('0000'), -- number of this disk
- hextoraw('0000'), -- disk where central directory starts
- util_zip_little_endian(t_cnt, 2), -- number of central directory records on this disk
- util_zip_little_endian(t_cnt, 2), -- total number of central directory records
- util_zip_little_endian(t_offs_end_header - t_offs_dir_header), -- size of central directory
- util_zip_little_endian(t_offs_dir_header), -- offset of start of central directory, relative to start of archive
- util_zip_little_endian(nvl(utl_raw.length(t_comment), 0), 2), -- ZIP file comment length
- t_comment));
-END util_zip_finish;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_multi_replace (
- p_source_string VARCHAR2,
- p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
- p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
- p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
- p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
- p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
- p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
- p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
- p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
- p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
- p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
- p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
- p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
-RETURN VARCHAR2 IS
- v_return VARCHAR2(32767);
-BEGIN
- v_return := p_source_string;
- IF p_01_find IS NOT NULL THEN v_return := replace(v_return, p_01_find, p_01_replace); END IF;
- IF p_02_find IS NOT NULL THEN v_return := replace(v_return, p_02_find, p_02_replace); END IF;
- IF p_03_find IS NOT NULL THEN v_return := replace(v_return, p_03_find, p_03_replace); END IF;
- IF p_04_find IS NOT NULL THEN v_return := replace(v_return, p_04_find, p_04_replace); END IF;
- IF p_05_find IS NOT NULL THEN v_return := replace(v_return, p_05_find, p_05_replace); END IF;
- IF p_06_find IS NOT NULL THEN v_return := replace(v_return, p_06_find, p_06_replace); END IF;
- IF p_07_find IS NOT NULL THEN v_return := replace(v_return, p_07_find, p_07_replace); END IF;
- IF p_08_find IS NOT NULL THEN v_return := replace(v_return, p_08_find, p_08_replace); END IF;
- IF p_09_find IS NOT NULL THEN v_return := replace(v_return, p_09_find, p_09_replace); END IF;
- IF p_10_find IS NOT NULL THEN v_return := replace(v_return, p_10_find, p_10_replace); END IF;
- IF p_11_find IS NOT NULL THEN v_return := replace(v_return, p_11_find, p_11_replace); END IF;
- IF p_12_find IS NOT NULL THEN v_return := replace(v_return, p_12_find, p_12_replace); END IF;
- RETURN v_return;
-END util_multi_replace;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_set_build_status_run_only (p_app_export_sql CLOB) RETURN CLOB IS
- v_position PLS_INTEGER;
-BEGIN
- v_position := instr(p_app_export_sql, ',p_exact_substitutions_only');
- RETURN substr(p_app_export_sql, 1, v_position - 1)
- || ',p_build_status=>''RUN_ONLY'''
- || c_lf
- || substr(p_app_export_sql, v_position);
-END util_set_build_status_run_only;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP IS
- v_return TIMESTAMP;
-BEGIN
- EXECUTE IMMEDIATE
- replace(
- q'[SELECT systimestamp - INTERVAL '{{MINUTES}}' MINUTE FROM dual]',
- '{{MINUTES}}',
- TO_CHAR(p_as_of_minutes_ago))
- INTO v_return;
- RETURN v_return;
-END util_calc_data_timestamp;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_setup_dbms_metadata (
- p_pretty IN BOOLEAN DEFAULT true,
- p_constraints IN BOOLEAN DEFAULT true,
- p_ref_constraints IN BOOLEAN DEFAULT false,
- p_partitioning IN BOOLEAN DEFAULT true,
- p_tablespace IN BOOLEAN DEFAULT false,
- p_storage IN BOOLEAN DEFAULT false,
- 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)
-IS
-BEGIN
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'PRETTY', p_pretty);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'CONSTRAINTS', p_constraints);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'REF_CONSTRAINTS', p_ref_constraints);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'PARTITIONING', p_partitioning);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'TABLESPACE', p_tablespace);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'STORAGE', p_storage);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'SEGMENT_ATTRIBUTES', p_segment_attributes);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'SQLTERMINATOR', p_sqlterminator);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'CONSTRAINTS_AS_ALTER', p_constraints_as_alter);
- dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'EMIT_SCHEMA', p_emit_schema);
-END util_setup_dbms_metadata;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files) IS
- v_file_list_lookup tab_file_list_lookup;
- v_apex_install_file_id PLS_INTEGER;
- v_file_name VARCHAR2(256);
- v_extension VARCHAR2(256);
- v_base_name VARCHAR2(256);
- v_count PLS_INTEGER;
-BEGIN
- util_log_start('ensure unique file names in collection');
- $if $$apex_installed $then
- -- find apex install file
- FOR i IN 1..p_export_files.count LOOP
- IF p_export_files(i).name = 'scripts/install_frontend_generated_by_apex.sql' THEN
- v_apex_install_file_id := i;
- END IF;
- END LOOP;
- $end
- FOR i IN 1..p_export_files.count LOOP
- v_file_name := p_export_files(i).name;
- v_count := 1;
- IF instr(v_file_name, '.') > 0 THEN
- v_base_name := substr(v_file_name, 1, instr(v_file_name, '.', -1) - 1);
- v_extension := substr(v_file_name, instr(v_file_name, '.', -1));
- ELSE
- v_base_name := v_file_name;
- v_extension := NULL;
- END IF;
- WHILE v_file_list_lookup.EXISTS(v_file_name) LOOP
- v_count := v_count + 1;
- v_file_name := v_base_name || '_' || v_count || v_extension;
- END LOOP;
- v_file_list_lookup(v_file_name) := i;
- -- correct data if needed
- IF p_export_files(i).name != v_file_name THEN
- -- correct the prompt statement
- p_export_files(i).contents := replace(
- p_export_files(i).contents,
- v_base_name,
- v_base_name || '_' || v_count);
- -- correct the apex install file
- IF v_apex_install_file_id IS NOT NULL THEN
- p_export_files(v_apex_install_file_id).contents := regexp_replace(
- p_export_files(v_apex_install_file_id).contents,
- p_export_files(i).name || '$',
- v_file_name,
- 1, 2, 'm');
- END IF;
- -- correct the file name itself
- p_export_files(i).name := v_file_name;
- END IF;
- END LOOP;
- util_log_stop;
-END util_ensure_unique_file_names;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_log_init (p_module IN VARCHAR2) IS
-BEGIN
- g_runlog.module := substr(p_module, 1, c_app_info_length);
- g_runlog.start_time := systimestamp;
- g_runlog.stop_time := NULL;
- g_runlog.run_time := 0;
- g_runlog.measured_time := 0;
- g_runlog.unmeasured_time := 0;
- g_runlog.data.DELETE;
- g_errlog.DELETE;
-END util_log_init;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_log_start (p_action IN VARCHAR2) IS
- v_index PLS_INTEGER;
-BEGIN
- dbms_application_info.set_module(
- module_name => g_runlog.module,
- action_name => p_action);
- v_index := g_runlog.data.count + 1;
- g_runlog.data(v_index).action := substr(p_action, 1, plex.c_app_info_length);
- g_runlog.data(v_index).start_time := systimestamp;
-END util_log_start;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_log_error (p_name VARCHAR2) IS
- v_index PLS_INTEGER;
- PROCEDURE add_error_to_action IS
- v_index PLS_INTEGER;
- BEGIN
- v_index := g_runlog.data.count;
- g_runlog.data(v_index).action := substr('ERROR: ' || g_runlog.data(v_index).action, 1, plex.c_app_info_length);
- END add_error_to_action;
-BEGIN
- v_index := g_errlog.count + 1;
- g_errlog(v_index).time_stamp := systimestamp;
- g_errlog(v_index).file_name := substr(p_name, 1, 255);
- g_errlog(v_index).error_text := substr(sqlerrm, 1, 200);
- g_errlog(v_index).call_stack := substr(dbms_utility.format_error_backtrace, 1, 500);
- add_error_to_action;
- util_log_stop;
- g_clob := null;
-END util_log_error;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_log_stop IS
- v_index PLS_INTEGER;
-BEGIN
- v_index := g_runlog.data.count;
- dbms_application_info.set_module(
- module_name => NULL,
- action_name => NULL);
- g_runlog.data(v_index).stop_time := systimestamp;
- g_runlog.data(v_index).elapsed := util_log_get_runtime(g_runlog.start_time, g_runlog.data(v_index).stop_time);
- g_runlog.data(v_index).execution := util_log_get_runtime(g_runlog.data(v_index).start_time, g_runlog.data(v_index).stop_time);
- g_runlog.measured_time := g_runlog.measured_time + g_runlog.data(v_index).execution;
-END util_log_stop;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION util_log_get_runtime (
- p_start IN TIMESTAMP,
- p_stop IN TIMESTAMP)
-RETURN NUMBER IS
-BEGIN
- RETURN SYSDATE + ((p_stop - p_start) * 86400) - SYSDATE;
- --sysdate + (interval_difference * 86400) - sysdate
- --https://stackoverflow.com/questions/10092032/extracting-the-total-number-of-seconds-from-an-interval-data-type
-END util_log_get_runtime;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_log_calc_runtimes IS
-BEGIN
- g_runlog.stop_time := systimestamp;
- g_runlog.run_time := util_log_get_runtime(g_runlog.start_time, g_runlog.stop_time);
- g_runlog.unmeasured_time := g_runlog.run_time - g_runlog.measured_time;
-END util_log_calc_runtimes;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_clob_append (p_content IN VARCHAR2) IS
-BEGIN
- g_clob_vc_cache := g_clob_vc_cache || p_content;
-EXCEPTION
- WHEN value_error THEN
- IF g_clob IS NULL THEN
- g_clob := g_clob_vc_cache;
- ELSE
- dbms_lob.writeappend(g_clob, length(g_clob_vc_cache), g_clob_vc_cache);
- END IF;
- g_clob_vc_cache := p_content;
-END util_clob_append;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_clob_append (p_content IN CLOB) IS
-BEGIN
- IF p_content IS NOT NULL THEN
- util_clob_flush_cache;
- IF g_clob IS NULL THEN
- g_clob := p_content;
- ELSE
- dbms_lob.writeappend(g_clob, length(p_content), p_content);
- END IF;
- END IF;
-END util_clob_append;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_clob_flush_cache IS
-BEGIN
- IF g_clob_vc_cache IS NOT NULL THEN
- IF g_clob IS NULL THEN
- g_clob := g_clob_vc_cache;
- ELSE
- dbms_lob.writeappend(g_clob, length(g_clob_vc_cache), g_clob_vc_cache);
- END IF;
- g_clob_vc_cache := NULL;
- END IF;
-END util_clob_flush_cache;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_clob_add_to_export_files (
- p_export_files IN OUT NOCOPY tab_export_files,
- p_name IN VARCHAR2)
-IS
- v_index PLS_INTEGER;
-BEGIN
- util_clob_flush_cache;
- v_index := p_export_files.count + 1;
- p_export_files.extend;
- p_export_files(v_index).name := p_name;
- p_export_files(v_index).contents := g_clob;
- g_clob := null;
-END util_clob_add_to_export_files;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_clob_query_to_csv (
- p_query IN VARCHAR2,
- p_max_rows IN NUMBER DEFAULT 1000,
- p_delimiter IN VARCHAR2 DEFAULT ',',
- p_quote_mark IN VARCHAR2 DEFAULT '"',
- p_header_prefix IN VARCHAR2 DEFAULT NULL)
-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_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
- 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 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 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;
-
-BEGIN
- IF p_query IS NOT NULL THEN
- v_cursor := dbms_sql.open_cursor;
- dbms_sql.parse(
- v_cursor,
- regexp_replace(p_query, ';\s*$', NULL),
- 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);
- util_clob_append(p_header_prefix);
- FOR i IN 1..v_col_cnt LOOP
- IF i > 1 THEN
- util_clob_append(p_delimiter);
- END IF;
- 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 i > 1 THEN
- util_clob_append(p_delimiter);
- END IF;
- 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_query_to_csv;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files) IS
-BEGIN
- IF g_errlog.count > 0 THEN
- util_log_start(g_errlog.count || ' error' || CASE WHEN g_errlog.count != 1 THEN 's' END || ' occurred: create error log');
- util_clob_append(
- replace('# {{MAIN_FUNCTION}} - Error Log', '{{MAIN_FUNCTION}}', upper(g_runlog.module))
- || c_crlf || c_crlf || c_crlf);
- FOR i IN 1..g_errlog.count LOOP
- util_clob_append('## ' || g_errlog(i).file_name || c_crlf || c_crlf);
- util_clob_append(to_char(g_errlog(i).time_stamp, 'yyyy-mm-dd hh24:mi:ss.ffffff') || ': ' || g_errlog(i).error_text || c_crlf || c_crlf);
- util_clob_append('```sql' || c_crlf || g_errlog(i).call_stack || '```' || c_crlf || c_crlf || c_crlf);
- END LOOP;
- util_clob_add_to_export_files(
- p_export_files => p_export_files,
- p_name => 'plex_error_log.md');
- util_log_stop;
- END IF;
-END util_clob_create_error_log;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_clob_create_runtime_log (p_export_files IN OUT NOCOPY tab_export_files) IS
-BEGIN
- util_log_calc_runtimes;
- util_clob_append(util_multi_replace('# {{MAIN_FUNCTION}} - Runtime Log
-
-- Export started at {{START_TIME}} and took {{RUN_TIME}} seconds to finish with {{ERRORS}}
-- Unmeasured execution time because of system waits, missing log calls or log overhead was {{UNMEASURED_TIME}} seconds
-- The used PLEX version was {{PLEX_VERSION}}
-- More infos here: [PLEX on GitHub]({{PLEX_URL}})
-
-' ,
- '{{MAIN_FUNCTION}}', upper(g_runlog.module),
- '{{START_TIME}}', TO_CHAR(g_runlog.start_time, 'yyyy-mm-dd hh24:mi:ss'),
- '{{RUN_TIME}}', trim(TO_CHAR(g_runlog.run_time, '999G990D000')),
- '{{UNMEASURED_TIME}}', trim(TO_CHAR(g_runlog.unmeasured_time, '999G990D000')),
- '{{PLEX_VERSION}}', c_plex_version,
- '{{PLEX_URL}}', c_plex_url,
- '{{ERRORS}}', g_errlog.count || ' error' || CASE WHEN g_errlog.count != 1 THEN 's' END));
- util_clob_append('
-| Step | Elapsed | Execution | Action |
-|------:|----------:|------------:|:-----------------------------------------------------------------|
-' );
- FOR i IN 1..g_runlog.data.count LOOP
- util_clob_append(util_multi_replace(
- '| {{STEP}} | {{ELAPSED}} | {{EXECUTION}} | {{ACTION}} |' || c_lf,
- '{{STEP}}', lpad(TO_CHAR(i), 5),
- '{{ELAPSED}}', lpad(trim(TO_CHAR(g_runlog.data(i).elapsed, '99990D000')), 9),
- '{{EXECUTION}}', lpad(trim(TO_CHAR(g_runlog.data(i).execution, '9990D000000')), 11),
- '{{ACTION}}', rpad(g_runlog.data(i).action, 64)));
- END LOOP;
- util_clob_add_to_export_files(
- p_export_files => p_export_files,
- p_name => 'plex_runtime_log.md');
-END util_clob_create_runtime_log;
-
-
-
-------------------------------------------------------------------------------------------------------------------------------
--- MAIN CODE
-------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION backapp (
- $if $$apex_installed $then
- p_app_id IN NUMBER DEFAULT NULL,
- p_app_date IN BOOLEAN DEFAULT true,
- p_app_public_reports IN BOOLEAN DEFAULT true,
- p_app_private_reports IN BOOLEAN DEFAULT false,
- p_app_notifications IN BOOLEAN DEFAULT false,
- p_app_translations IN BOOLEAN DEFAULT true,
- p_app_pkg_app_mapping IN BOOLEAN DEFAULT false,
- p_app_original_ids IN BOOLEAN DEFAULT false,
- p_app_subscriptions IN BOOLEAN DEFAULT true,
- p_app_comments IN BOOLEAN DEFAULT true,
- 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_type_like IN VARCHAR2 DEFAULT NULL,
- 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_include_data IN BOOLEAN DEFAULT false,
- p_data_as_of_minutes_ago IN NUMBER DEFAULT 0,
- p_data_max_rows IN NUMBER DEFAULT 1000,
- p_data_table_name_like IN VARCHAR2 DEFAULT NULL,
- p_data_table_name_not_like IN VARCHAR2 DEFAULT NULL,
- p_include_templates IN BOOLEAN DEFAULT true,
- p_include_runtime_log IN BOOLEAN DEFAULT true,
- p_include_error_log IN BOOLEAN DEFAULT true,
- p_base_path_backend IN VARCHAR2 DEFAULT 'app_backend',
- p_base_path_frontend IN VARCHAR2 DEFAULT 'app_frontend',
- p_base_path_data IN VARCHAR2 DEFAULT 'app_data')
-RETURN tab_export_files IS
- v_apex_version NUMBER;
- v_data_timestamp TIMESTAMP;
- v_data_scn NUMBER;
- v_file_path VARCHAR2(255);
- v_current_user user_objects.object_name%TYPE;
- v_app_workspace user_objects.object_name%TYPE;
- v_app_owner user_objects.object_name%TYPE;
- v_app_alias user_objects.object_name%TYPE;
- v_ddl_files rec_ddl_files;
- v_contents CLOB;
- v_export_files tab_export_files;
- v_file_list_lookup tab_file_list_lookup;
- TYPE obj_cur_typ IS REF CURSOR;
- v_cur obj_cur_typ;
- v_query VARCHAR2(32767);
-
- PROCEDURE init IS
- BEGIN
- util_log_init(
- p_module => 'plex.backapp'
- $if $$apex_installed $then
- || CASE WHEN p_app_id IS NOT NULL THEN '(' || TO_CHAR(p_app_id) || ')' END
- $end);
- util_log_start('init');
- v_export_files := NEW tab_export_files();
- v_current_user := sys_context('USERENV', 'CURRENT_USER');
- util_log_stop;
- END init;
-
- $if $$apex_installed $then
- PROCEDURE check_owner IS
- CURSOR cur_owner IS
- SELECT workspace,
- owner,
- alias
- FROM apex_applications t
- WHERE t.application_id = p_app_id;
- BEGIN
- util_log_start('check_owner');
- IF p_app_id IS NOT NULL THEN
- OPEN cur_owner;
- FETCH cur_owner INTO
- v_app_workspace,
- v_app_owner,
- v_app_alias;
- CLOSE cur_owner;
- END IF;
- IF p_app_id IS NOT NULL AND v_app_owner IS NULL THEN
- raise_application_error(
- -20101,
- 'Could not find owner for application - are you sure you provided the right app_id?');
- ELSIF p_app_id IS NOT NULL AND v_app_owner != v_current_user THEN
- raise_application_error(
- -20102,
- 'You are not the owner of the app - please login as the owner.');
- END IF;
- util_log_stop;
- END check_owner;
- $end
-
- $if $$apex_installed $then
- PROCEDURE process_apex_app IS
- v_apex_files apex_t_export_files;
- BEGIN
- -- save as individual files
- util_log_start(p_base_path_frontend || '/APEX_EXPORT:individual_files');
- v_apex_files := apex_export.get_application(
- p_application_id => p_app_id,
- p_split => true,
- p_with_date => p_app_date,
- p_with_ir_public_reports => p_app_public_reports,
- p_with_ir_private_reports => p_app_private_reports,
- p_with_ir_notifications => p_app_notifications,
- p_with_translations => p_app_translations,
- p_with_pkg_app_mapping => p_app_pkg_app_mapping,
- p_with_original_ids => p_app_original_ids,
- p_with_no_subscriptions => CASE WHEN p_app_subscriptions THEN false ELSE true END,
- p_with_comments => p_app_comments,
- p_with_supporting_objects => p_app_supporting_objects);
- FOR i IN 1..v_apex_files.count LOOP
- v_export_files.extend;
- -- relocate files to own project structure
- v_export_files(i).name := replace(
- v_apex_files(i).name,
- 'f' || p_app_id || '/application/',
- p_base_path_frontend || '/');
- -- correct prompts for relocation
- v_export_files(i).contents := replace(
- v_apex_files(i).contents,
- 'prompt --application/',
- 'prompt --' || p_base_path_frontend || '/');
- -- special handling for install file
- IF v_export_files(i).name = 'f' || p_app_id || '/install.sql' THEN
- v_export_files(i).name := 'scripts/install_frontend_generated_by_apex.sql';
- v_export_files(i).contents := '-- DO NOT TOUCH THIS FILE - IT WILL BE OVERWRITTEN ON NEXT PLEX BACKAPP CALL'
- || c_lf || c_lf
- || replace(replace(v_export_files(i).contents,
- '@application/', '@../' || p_base_path_frontend || '/'),
- 'prompt --install', 'prompt --install_frontend_generated_by_apex');
- END IF;
- -- handle build status RUN_ONLY
- IF v_export_files(i).name = p_base_path_frontend || '/create_application.sql' AND p_app_build_status_run_only THEN
- v_export_files(i).contents := util_set_build_status_run_only(v_export_files(i).contents);
- END IF;
- v_apex_files.DELETE(i);
- END LOOP;
- util_log_stop;
- --
- IF p_app_include_single_file THEN
- -- save as single file
- v_apex_files.DELETE;
- util_log_start(p_base_path_frontend || '/APEX_EXPORT:single_file');
- v_apex_files := apex_export.get_application(
- p_application_id => p_app_id,
- p_split => false,
- p_with_date => p_app_date,
- p_with_ir_public_reports => p_app_public_reports,
- p_with_ir_private_reports => p_app_private_reports,
- p_with_ir_notifications => p_app_notifications,
- p_with_translations => p_app_translations,
- p_with_pkg_app_mapping => p_app_pkg_app_mapping,
- p_with_original_ids => p_app_original_ids,
- p_with_no_subscriptions => CASE WHEN p_app_subscriptions THEN false ELSE true END,
- p_with_comments => p_app_comments,
- p_with_supporting_objects => p_app_supporting_objects);
- IF p_app_build_status_run_only THEN
- v_apex_files(1).contents := util_set_build_status_run_only(v_apex_files(1).contents);
- END IF;
- util_clob_append(v_apex_files(1).contents);
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => p_base_path_frontend || '/' || v_apex_files(1).name);
- v_apex_files.DELETE;
- util_log_stop;
- END IF;
- END process_apex_app;
- $end
-
- PROCEDURE replace_query_like_expressions (
- p_like_list VARCHAR2,
- p_not_like_list VARCHAR2,
- p_placeholder_prefix VARCHAR2,
- p_column_name VARCHAR2)
- IS
- v_expression_table tab_vc32k;
- BEGIN
- -- process filter "like"
- v_expression_table := util_split(p_like_list, ',');
- FOR i IN 1..v_expression_table.count LOOP
- v_expression_table(i) := p_column_name
- || ' like '''
- || trim(v_expression_table(i))
- || ''' escape ''\''';
- END LOOP;
- v_query := replace(
- v_query,
- '#' || p_placeholder_prefix || '_LIKE_EXPRESSIONS#',
- nvl(util_join(v_expression_table, ' or '), '1 = 1'));
- -- process filter "not like"
- v_expression_table := util_split(p_not_like_list, ',');
- FOR i IN 1..v_expression_table.count LOOP
- v_expression_table(i) := p_column_name
- || ' not like '''
- || trim(v_expression_table (i))
- || ''' escape ''\''';
- END LOOP;
- v_query := replace(
- v_query,
- '#' || p_placeholder_prefix || '_NOT_LIKE_EXPRESSIONS#',
- nvl(util_join(v_expression_table, ' and '), '1 = 1'));
- $if $$debug_on $then
- dbms_output.put_line(v_query);
- $end
- END replace_query_like_expressions;
-
- PROCEDURE process_user_ddl IS
-
- PROCEDURE process_user IS
- BEGIN
- v_file_path := p_base_path_backend || '/_user/' || v_current_user || '.sql';
- util_log_start(v_file_path);
- util_setup_dbms_metadata(p_sqlterminator => false);
- util_clob_append(util_multi_replace(q'^
-BEGIN
- FOR i IN (SELECT '{{CURRENT_USER}}' AS username FROM dual
- MINUS
- SELECT username FROM dba_users) LOOP
- EXECUTE IMMEDIATE q'[
---------------------------------------------------------------------------------
-{{DDL}}
---------------------------------------------------------------------------------
- ]'
- END LOOP;
-END;
-{{/}}
-^' ,
- '{{CURRENT_USER}}', v_current_user,
- '{{DDL}}', dbms_metadata.get_ddl('USER', v_current_user),
- '{{/}}', c_slash));
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_setup_dbms_metadata;
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_setup_dbms_metadata;
- util_log_error(v_file_path);
- END process_user;
-
- PROCEDURE process_roles IS
- BEGIN
- v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_roles.sql';
- util_log_start(v_file_path);
- FOR i IN (SELECT DISTINCT username FROM user_role_privs) LOOP
- util_clob_append(dbms_metadata.get_granted_ddl(
- 'ROLE_GRANT',
- v_current_user));
- END LOOP;
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_log_error(v_file_path);
- END process_roles;
-
- PROCEDURE process_system_privileges IS
- BEGIN
- v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_system_privileges.sql';
- util_log_start(v_file_path);
- FOR i IN (SELECT DISTINCT username FROM user_sys_privs) LOOP
- util_clob_append(dbms_metadata.get_granted_ddl(
- 'SYSTEM_GRANT',
- v_current_user));
- END LOOP;
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_log_error(v_file_path);
- END process_system_privileges;
-
- PROCEDURE process_object_privileges IS
- BEGIN
- v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_object_privileges.sql';
- util_log_start(v_file_path);
- FOR i IN (SELECT DISTINCT grantee FROM user_tab_privs WHERE grantee = v_current_user) LOOP
- util_clob_append(dbms_metadata.get_granted_ddl(
- 'OBJECT_GRANT',
- v_current_user));
- END LOOP;
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_log_error(v_file_path);
- END process_object_privileges;
-
- BEGIN
- process_user;
- process_roles;
- process_system_privileges;
- process_object_privileges;
- END process_user_ddl;
-
- PROCEDURE process_object_ddl IS
- TYPE obj_rec_typ IS RECORD (
- object_type VARCHAR2(128),
- object_name VARCHAR2(256),
- file_path VARCHAR2(512));
- v_rec obj_rec_typ;
- BEGIN
- util_log_start(p_base_path_backend || '/open_objects_cursor');
- v_query := q'^
---https://stackoverflow.com/questions/10886450/how-to-generate-entire-ddl-of-an-oracle-schema-scriptable
---https://stackoverflow.com/questions/3235300/oracles-dbms-metadata-get-ddl-for-object-type-job
-SELECT CASE object_type
- --http://psoug.org/reference/dbms_metadata.html
- WHEN 'UNIFIED AUDIT POLICY' THEN 'AUDIT_OBJ'
- WHEN 'CONSUMER GROUP' THEN 'RMGR_CONSUMER_GROUP'
- WHEN 'DATABASE LINK' THEN 'DB_LINK'
- WHEN 'EVALUATION CONTEXT' THEN 'PROCOBJ'
- WHEN 'JAVA CLASS' THEN 'JAVA_CLASS'
- WHEN 'JAVA RESOURCE' THEN 'JAVA_RESOURCE'
- WHEN 'JAVA SOURCE' THEN 'JAVA_SOURCE'
- WHEN 'JAVA TYPE' THEN 'JAVA_TYPE'
- WHEN 'JOB' THEN 'PROCOBJ'
- WHEN 'JOB CLASS' THEN 'PROCOBJ'
- WHEN 'MATERIALIZED VIEW' THEN 'MATERIALIZED_VIEW'
- WHEN 'PACKAGE BODY' THEN 'PACKAGE_BODY'
- WHEN 'PACKAGE' THEN 'PACKAGE_SPEC'
- WHEN 'PROGRAM' THEN 'PROCOBJ'
- WHEN 'QUEUE' THEN 'AQ_QUEUE'
- WHEN 'RESOURCE PLAN' THEN 'RMGR_PLAN'
- WHEN 'RULE SET' THEN 'PROCOBJ'
- WHEN 'RULE' THEN 'PROCOBJ'
- WHEN 'SCHEDULE' THEN 'PROCOBJ'
- WHEN 'SCHEDULER GROUP' THEN 'PROCOBJ'
- WHEN 'TYPE BODY' THEN 'TYPE_BODY'
- WHEN 'TYPE' THEN 'TYPE_SPEC'
- ELSE object_type
- END AS object_type,
- object_name,
- '{{BASE_PATH_APP_BACKEND}}/'
- || replace(lower(
- CASE
- WHEN object_type LIKE '%S' THEN object_type || 'ES'
- WHEN object_type LIKE '%EX' THEN regexp_replace(object_type, 'EX$', 'ICES', 1, 0, 'i')
- WHEN object_type LIKE '%Y' THEN regexp_replace(object_type, 'Y$', 'IES', 1, 0, 'i')
- ELSE object_type || 'S'
- END), ' ', '_')
- || '/' || object_name
- || CASE object_type
- WHEN 'FUNCTION' THEN '.fnc'
- WHEN 'PACKAGE BODY' THEN '.pkb'
- WHEN 'PACKAGE' THEN '.pks'
- WHEN 'PROCEDURE' THEN '.prc'
- WHEN 'TRIGGER' THEN '.trg'
- WHEN 'TYPE BODY' THEN '.tpb'
- WHEN 'TYPE' THEN '.tps'
- ELSE '.sql'
- END AS file_path
- FROM ^'
-$if NOT $$debug_on
-$then || 'user_objects'
-$else || '(SELECT MIN(object_name) AS object_name, object_type FROM user_objects GROUP BY object_type)'
-$end || q'^
- WHERE -- ignore invalid object types
- object_type NOT IN ('UNDEFINED','DESTINATION','EDITION','JAVA DATA','WINDOW')
- --These objects are included within other object types:
- AND object_type NOT IN ('INDEX PARTITION','INDEX SUBPARTITION','LOB','LOB PARTITION','TABLE PARTITION','TABLE SUBPARTITION')
- --Ignore system-generated types for collection processing:
- AND NOT (object_type = 'TYPE' AND object_name LIKE 'SYS_PLSQL_%')
- --Ignore system-generated sequences for identity columns:
- AND NOT (object_type = 'SEQUENCE' AND object_name LIKE 'ISEQ$$_%')
- --Ignore LOB indices, their DDL is part of the table:
- AND object_name NOT IN (SELECT index_name FROM user_lobs)
- --Ignore nested tables, their DDL is part of their parent table:
- AND object_name NOT IN (SELECT table_name FROM user_nested_tables)
- --Set user specific like filters:
- AND (#TYPE_LIKE_EXPRESSIONS#)
- AND (#TYPE_NOT_LIKE_EXPRESSIONS#)
- AND (#NAME_LIKE_EXPRESSIONS#)
- AND (#NAME_NOT_LIKE_EXPRESSIONS#)
- ORDER BY
- object_type,
- object_name
-^' ;
- v_query := replace(
- v_query,
- '{{BASE_PATH_APP_BACKEND}}',
- p_base_path_backend);
- replace_query_like_expressions(
- p_like_list => p_object_type_like,
- p_not_like_list => p_object_type_not_like,
- p_placeholder_prefix => 'TYPE',
- p_column_name => 'object_type');
- replace_query_like_expressions(
- p_like_list => p_object_name_like,
- p_not_like_list => p_object_name_not_like,
- p_placeholder_prefix => 'NAME',
- p_column_name => 'object_name');
- util_setup_dbms_metadata;
- OPEN v_cur FOR v_query;
- util_log_stop;
- LOOP
- FETCH v_cur INTO v_rec;
- EXIT WHEN v_cur%notfound;
- BEGIN
- util_log_start(v_rec.file_path);
- CASE v_rec.object_type
- WHEN 'SEQUENCE' THEN
- v_ddl_files.sequences_(v_ddl_files.sequences_.count + 1) := v_rec.file_path;
- WHEN 'TABLE' THEN
- v_ddl_files.tables_(v_ddl_files.tables_.count + 1) := v_rec.file_path;
- WHEN 'INDEX' THEN
- v_ddl_files.indices_(v_ddl_files.indices_.count + 1) := v_rec.file_path;
- WHEN 'VIEW' THEN
- v_ddl_files.views_(v_ddl_files.views_.count + 1) := v_rec.file_path;
- WHEN 'TYPE_SPEC' THEN
- v_ddl_files.types_(v_ddl_files.types_.count + 1) := v_rec.file_path;
- WHEN 'TYPE_BODY' THEN
- v_ddl_files.type_bodies_(v_ddl_files.type_bodies_.count + 1) := v_rec.file_path;
- WHEN 'TRIGGER' THEN
- v_ddl_files.triggers_(v_ddl_files.triggers_.count + 1) := v_rec.file_path;
- WHEN 'FUNCTION' THEN
- v_ddl_files.functions_(v_ddl_files.functions_.count + 1) := v_rec.file_path;
- WHEN 'PROCEDURE' THEN
- v_ddl_files.procedures_(v_ddl_files.procedures_.count + 1) := v_rec.file_path;
- WHEN 'PACKAGE_SPEC' THEN
- v_ddl_files.packages_(v_ddl_files.packages_.count + 1) := v_rec.file_path;
- WHEN 'PACKAGE_BODY' THEN
- v_ddl_files.package_bodies_(v_ddl_files.package_bodies_.count + 1) := v_rec.file_path;
- ELSE
- v_ddl_files.other_objects_(v_ddl_files.other_objects_.count + 1) := v_rec.file_path;
- END CASE;
- CASE
- WHEN v_rec.object_type = 'VIEW' THEN
- util_clob_append(ltrim(regexp_replace(regexp_replace(
- -- source string
- dbms_metadata.get_ddl(
- object_type => v_rec.object_type,
- name => v_rec.object_name,
- schema => v_current_user),
- -- regex replace: remove additional column list from the compiler
- '\(.*\) ', NULL, 1, 1),
- -- regex replace: remove additional whitespace from the compiler
- '^\s*SELECT', 'SELECT', 1, 1, 'im'),
- -- ltrim: remove leading whitspace
- ' ' || c_lf));
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_rec.file_path);
- WHEN v_rec.object_type IN ('TABLE', 'INDEX', 'SEQUENCE') THEN
- util_setup_dbms_metadata(p_sqlterminator => false);
- util_clob_append(replace(q'^
-BEGIN
- FOR i IN (SELECT '{{OBJECT_NAME}}' AS object_name FROM dual
- MINUS
- SELECT object_name FROM user_objects) LOOP
- EXECUTE IMMEDIATE q'[
---------------------------------------------------------------------------------
-^' ,
- '{{OBJECT_NAME}}',
- v_rec.object_name)
- || dbms_metadata.get_ddl(v_rec.object_type, v_rec.object_name, v_current_user)
- || replace(q'^
---------------------------------------------------------------------------------
- ]';
- END LOOP;
-END;
-{{/}}
-
--- Put your ALTER statements below in the same style as before to ensure that
--- the script is restartable.
-^' ,
- '{{/}}',
- c_slash));
- util_setup_dbms_metadata;
- ELSE
- util_clob_append(dbms_metadata.get_ddl(
- object_type => v_rec.object_type,
- name => v_rec.object_name,
- schema => v_current_user));
- END CASE;
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_rec.file_path);
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_setup_dbms_metadata;
- util_log_error(v_rec.file_path);
- END;
- END LOOP;
- CLOSE v_cur;
- END process_object_ddl;
-
- PROCEDURE process_object_grants IS
- TYPE obj_rec_typ IS RECORD (
- grantor VARCHAR2(128),
- privilege VARCHAR2(128),
- object_name VARCHAR2(256),
- file_path VARCHAR2(512));
- v_rec obj_rec_typ;
- BEGIN
- util_log_start(p_base_path_backend || '/grants:open_cursor');
- v_query := q'^
-SELECT DISTINCT
- p.grantor,
- p.privilege,
- p.table_name as object_name,
- '{{BASE_PATH_APP_BACKEND}}/grants/' || p.privilege || '_on_' || p.table_name || '.sql' AS file_path
-FROM user_tab_privs p
-JOIN user_objects o ON p.table_name = o.object_name
-WHERE (#NAME_LIKE_EXPRESSIONS#)
- AND (#NAME_NOT_LIKE_EXPRESSIONS#)
-ORDER BY
- privilege,
- object_name
-^' ;
- v_query := replace(
- v_query,
- '{{BASE_PATH_APP_BACKEND}}',
- p_base_path_backend);
- replace_query_like_expressions(
- p_like_list => p_object_name_like,
- p_not_like_list => p_object_name_not_like,
- p_placeholder_prefix => 'NAME',
- p_column_name => 'o.object_name');
- OPEN v_cur FOR v_query;
- util_log_stop;
- LOOP
- FETCH v_cur INTO v_rec;
- EXIT WHEN v_cur%notfound;
- BEGIN
- util_log_start(v_rec.file_path);
- util_clob_append(dbms_metadata.get_dependent_ddl(
- 'OBJECT_GRANT',
- v_rec.object_name,
- v_rec.grantor));
- v_ddl_files.grants_(v_ddl_files.grants_.count + 1) := v_rec.file_path;
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_rec.file_path);
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_log_error(v_rec.file_path);
- END;
- END LOOP;
- CLOSE v_cur;
- END process_object_grants;
-
- PROCEDURE process_ref_constraints IS
- TYPE obj_rec_typ IS RECORD (
- table_name VARCHAR2(256),
- constraint_name VARCHAR2(256),
- file_path VARCHAR2(512));
- v_rec obj_rec_typ;
- BEGIN
- util_log_start(p_base_path_backend || '/ref_constraints:open_cursor');
- v_query := q'^
-SELECT table_name,
- constraint_name,
- '{{BASE_PATH_APP_BACKEND}}/ref_constraints/' || constraint_name || '.sql' AS file_path
-FROM user_constraints
-WHERE constraint_type = 'R'
- AND (#NAME_LIKE_EXPRESSIONS#)
- AND (#NAME_NOT_LIKE_EXPRESSIONS#)
-ORDER BY
- table_name,
- constraint_name
-^' ;
- v_query := replace(
- v_query,
- '{{BASE_PATH_APP_BACKEND}}',
- p_base_path_backend);
- replace_query_like_expressions(
- p_like_list => p_object_name_like,
- p_not_like_list => p_object_name_not_like,
- p_placeholder_prefix => 'NAME',
- p_column_name => 'table_name');
- OPEN v_cur FOR v_query;
- util_log_stop;
- LOOP
- FETCH v_cur INTO v_rec;
- EXIT WHEN v_cur%notfound;
- BEGIN
- util_log_start(v_rec.file_path);
- util_setup_dbms_metadata(p_sqlterminator => false);
- util_clob_append(replace(q'^
-BEGIN
-FOR i IN (SELECT '{{CONSTRAINT_NAME}}' AS constraint_name FROM dual
- MINUS
- SELECT constraint_name FROM user_constraints) LOOP
- EXECUTE IMMEDIATE q'[
---------------------------------------------------------------------------------
-^' ,
- '{{CONSTRAINT_NAME}}',
- v_rec.constraint_name)
- || dbms_metadata.get_ddl('REF_CONSTRAINT', v_rec.constraint_name)
- || replace(q'^
---------------------------------------------------------------------------------
- ]';
-END LOOP;
-END;
-{{/}}
-^' ,
- '{{/}}',
- c_slash));
- util_setup_dbms_metadata;
- v_ddl_files.ref_constraints_(v_ddl_files.ref_constraints_.count + 1) := v_rec.file_path;
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_rec.file_path);
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_setup_dbms_metadata;
- util_log_error(v_rec.file_path);
- END;
- END LOOP;
- CLOSE v_cur;
- END process_ref_constraints;
-
- PROCEDURE create_backend_install_file IS
-
- FUNCTION get_script_line (p_file_path VARCHAR2) RETURN VARCHAR2 IS
- BEGIN
- RETURN 'prompt --' || replace(p_file_path, '.sql', NULL)
- || c_lf || '@' || '../' || p_file_path || c_lf || c_lf;
- END get_script_line;
-
- BEGIN
- v_file_path := 'scripts/install_backend_generated_by_plex.sql';
- util_log_start(v_file_path);
- util_clob_append('/* A T T E N T I O N
-DO NOT TOUCH THIS FILE or set the PLEX.BackApp parameter p_include_object_ddl
-to false - otherwise your changes would be overwritten on next PLEX.BackApp
-call. It is recommended to export your object ddl only ones on initial
-repository creation and then start to use the "files first approach".
-*/
-
-set define off verify off feedback off
-whenever sqlerror exit sql.sqlcode rollback
-
-prompt --install_backend_generated_by_plex
-
-' );
- FOR i IN 1..v_ddl_files.sequences_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.sequences_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.tables_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.tables_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.ref_constraints_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.ref_constraints_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.indices_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.indices_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.views_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.views_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.types_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.types_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.type_bodies_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.type_bodies_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.triggers_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.triggers_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.functions_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.functions_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.procedures_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.procedures_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.packages_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.packages_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.package_bodies_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.package_bodies_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.grants_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.grants_(i)));
- END LOOP;
- FOR i IN 1..v_ddl_files.other_objects_.count LOOP
- util_clob_append(get_script_line(v_ddl_files.other_objects_(i)));
- END LOOP;
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- END create_backend_install_file;
-
- PROCEDURE process_data IS
- TYPE obj_rec_typ IS RECORD (
- table_name VARCHAR2(256),
- pk_columns VARCHAR2(4000));
- v_rec obj_rec_typ;
- BEGIN
- util_log_start(p_base_path_data || '/open_tables_cursor');
- v_query := q'^
-SELECT table_name,
- (SELECT LISTAGG(column_name, ', ') WITHIN GROUP(ORDER BY position)
- FROM user_cons_columns
- WHERE constraint_name = (SELECT constraint_name
- FROM user_constraints c
- WHERE constraint_type = 'P'
- AND c.table_name = t.table_name)
- ) AS pk_columns
- FROM user_tables t
- WHERE table_name IN (SELECT table_name FROM user_tables
- MINUS
- SELECT table_name FROM user_external_tables)
- AND (#NAME_LIKE_EXPRESSIONS#)
- AND (#NAME_NOT_LIKE_EXPRESSIONS#)
- ORDER BY
- table_name
-^' ;
- replace_query_like_expressions(
- p_like_list => p_data_table_name_like,
- p_not_like_list => p_data_table_name_not_like,
- p_placeholder_prefix => 'NAME',
- p_column_name => 'table_name');
- OPEN v_cur FOR v_query;
- util_log_stop;
- --
- util_log_start(p_base_path_data || '/get_scn');
- v_data_timestamp := util_calc_data_timestamp(nvl(p_data_as_of_minutes_ago, 0));
- v_data_scn := timestamp_to_scn(v_data_timestamp);
- util_log_stop;
- LOOP
- FETCH v_cur INTO v_rec;
- EXIT WHEN v_cur%notfound;
- BEGIN
- v_file_path := p_base_path_data || '/' || v_rec.table_name || '.csv';
- util_log_start(v_file_path);
- util_clob_query_to_csv(
- p_query => 'SELECT * FROM ' || v_rec.table_name || ' AS OF SCN ' || v_data_scn ||
- CASE
- WHEN v_rec.pk_columns IS NOT NULL
- THEN ' ORDER BY ' || v_rec.pk_columns
- ELSE NULL
- END,
- p_max_rows => p_data_max_rows);
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_log_error(v_file_path);
- END;
- END LOOP;
- CLOSE v_cur;
- END process_data;
-
- PROCEDURE create_template_files IS
- v_file_template VARCHAR2(32767 CHAR);
- BEGIN
- v_file_template := q'^Your Global README File
-=======================
-
-It is a good practice to have a README file in the root of your project with
-a high level overview of your application. Put the more detailed docs in the
-docs folder.
-
-You can start with a copy of this file. Rename it to README.md and try to use
-Markdown when writing your content - this has many benefits and you don't waist
-time by formatting your docs. If you are unsure have a look at some projects at
-[Github](https://github.com) or any other code hosting platform.
-
-Depending on your options when calling `plex.backapp` these files are generated
-for you:
-
-- scripts/install_backend_generated_by_plex.sql
-- scripts/install_frontend_generated_by_apex.sql
-
-Do not touch these generated install files. They will be overwritten on each
-plex call. Depending on your call parameters it would be ok to modify the file
-install_backend_generated_by_plex - especially when you follow the files first
-approach and export your schema DDL only ones to have a starting point for you
-repository.
-
-If you need to do modifications for the install process then have a look at the
-following templates - they call the generated files and you can do your own
-stuff before or after the calls.
-
-- scripts/templates/1_export_app_from_DEV.bat
-- scripts/templates/2_install_app_into_TEST.bat
-- scripts/templates/3_install_app_into_PROD.bat
-- scripts/templates/export_app_custom_code.sql
-- scripts/templates/install_app_custom_code.sql
-
-If you want to use these files please make a copy into the scripts directory
-and modify it to your needs. Doing it this way your changes are overwrite save.
-
-[Feedback is welcome]({{PLEX_URL}}/issues/new)
-^' ;
- v_file_path := 'plex_README.md';
- util_log_start(v_file_path);
- util_clob_append(replace(
- v_file_template,
- '{{PLEX_URL}}',
- c_plex_url));
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- v_file_template := q'^rem Template generated by PLEX version {{PLEX_VERSION}}
-rem More infos here: {{PLEX_URL}}
-
-{{@}}echo off
-setlocal
-set "areyousure=N"
-
-rem ### BEGIN CONFIG ###########################################################
-rem Align delimiters to your operating system locale:
-for /f "tokens=1-3 delims=. " %%a in ("%DATE%") do (set "mydate=%%c%%b%%a")
-for /f "tokens=1-3 delims=:." %%a in ("%TIME: =0%") do (set "mytime=%%a%%b%%c")
-set "systemrole={{SYSTEMROLE}}"
-set "connection=localhost:1521/orcl"
-set "scriptfile={{SCRIPTFILE}}"
-set "app_id={{APP_ID}}"
-set "app_alias={{APP_ALIAS}}"
-set "app_schema={{APP_OWNER}}"
-set "app_workspace={{APP_WORKSPACE}}"
-set "logfile={{LOGFILE}}"
-rem ### END CONFIG #############################################################
-
-:PROMPT
-echo.
-echo.
-set /p "areyousure=Run %scriptfile% on %app_schema%@%systemrole%(%connection%) [Y/N]? " || set "areyousure=N"
-if /i %areyousure% neq y goto END
-set NLS_LANG=AMERICAN_AMERICA.UTF8
-set /p "password=Please enter password for %app_schema% [default = oracle]: " || set "password=oracle"
-echo This is the runlog for %scriptfile% on %app_schema%@%systemrole%(%connection%) > %logfile%
-echo exit | sqlplus -S %app_schema%/%password%@%connection% ^
-{{@}}%scriptfile% ^
-%logfile% ^
-%app_id% ^
-%app_alias% ^
-%app_schema% ^
-%app_workspace%
-
-if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-( >> %logfile%
-if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-(
-
-:END
-rem Remove "pause" for fully automated setup:
-pause
-if %errorlevel% neq 0 exit /b %errorlevel%
-^' ;
- v_file_path := 'scripts/templates/1_export_app_from_DEV.bat';
- util_log_start(v_file_path);
- util_clob_append(util_multi_replace(
- v_file_template,
- '{{PLEX_VERSION}}', c_plex_version,
- '{{PLEX_URL}}', c_plex_url,
- '{{SYSTEMROLE}}', 'DEV',
- $if $$apex_installed $then
- '{{APP_ID}}', p_app_id,
- '{{APP_ALIAS}}', v_app_alias,
- '{{APP_OWNER}}', v_app_owner,
- '{{APP_WORKSPACE}}', v_app_workspace,
- $end
- '{{SCRIPTFILE}}', 'export_app_custom_code.sql',
- '{{LOGFILE}}', 'logs/export_app_%app_id%_from_%app_schema%_at_%systemrole%_%mydate%_%mytime%.log',
- '{{@}}', c_at));
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
-
- v_file_path := 'scripts/templates/2_install_app_into_TEST.bat';
- util_log_start(v_file_path);
- util_clob_append(util_multi_replace(
- v_file_template,
- '{{PLEX_VERSION}}', c_plex_version,
- '{{PLEX_URL}}', c_plex_url,
- '{{SYSTEMROLE}}', 'TEST',
- $if $$apex_installed $then
- '{{APP_ID}}', p_app_id,
- '{{APP_ALIAS}}', v_app_alias,
- '{{APP_OWNER}}', v_app_owner,
- '{{APP_WORKSPACE}}', v_app_workspace,
- $end
- '{{SCRIPTFILE}}', 'install_app_custom_code.sql',
- '{{LOGFILE}}', 'logs/install_app_%app_id%_into_%app_schema%_at_%systemrole%_%mydate%_%mytime%.log',
- '{{@}}', c_at));
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
-
- v_file_path := 'scripts/templates/3_install_app_into_PROD.bat';
- util_log_start(v_file_path);
- util_clob_append(util_multi_replace(
- v_file_template,
- '{{PLEX_VERSION}}', c_plex_version,
- '{{PLEX_URL}}', c_plex_url,
- '{{SYSTEMROLE}}', 'PROD',
- $if $$apex_installed $then
- '{{APP_ID}}', p_app_id,
- '{{APP_ALIAS}}', v_app_alias,
- '{{APP_OWNER}}', v_app_owner,
- '{{APP_WORKSPACE}}', v_app_workspace,
- $end
- '{{SCRIPTFILE}}', 'install_app_custom_code.sql',
- '{{LOGFILE}}', 'logs/install_app_%app_id%_into_%app_schema%_at_%systemrole%_%mydate%_%mytime%.log',
- '{{@}}', c_at));
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
-
- v_file_template := q'^-- Template generated by PLEX version {{PLEX_VERSION}}
--- More infos here: {{PLEX_URL}}
-
-set verify off feedback off heading off
-set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
-whenever sqlerror exit sql.sqlcode rollback
--- whenever oserror exit failure rollback
-define logfile = "&1"
-spool "&logfile" append
-variable app_id varchar2(100)
-variable app_alias varchar2(100)
-variable app_schema varchar2(100)
-variable app_workspace varchar2(100)
-BEGIN
- :app_id := &2;
- :app_alias := '&3';
- :app_schema := '&4';
- :app_workspace := '&5';
-END;
-{{/}}
-
-
-prompt
-prompt Start Export
-prompt =========================================================================
-prompt Create global temporary table temp_export_files if not exist
-BEGIN
- FOR i IN (SELECT 'TEMP_EXPORT_FILES' AS object_name FROM dual
- MINUS
- SELECT object_name FROM user_objects) LOOP
- EXECUTE IMMEDIATE '
---------------------------------------------------------------------------------
-CREATE GLOBAL TEMPORARY TABLE temp_export_files (
- name VARCHAR2(255),
- contents CLOB)
-ON COMMIT DELETE ROWS
---------------------------------------------------------------------------------
- ';
- END LOOP;
-END;
-{{/}}
-
-
-prompt Do the app export, relocate files and save to temporary table
-prompt ATTENTION: Depending on your options this could take some time ...
-DECLARE
- v_files tab_export_files;
-BEGIN
- v_files := plex.backapp(
- -- These are the defaults - align it to your needs:^';
- $if $$apex_installed $then
- v_file_template := v_file_template || q'^
- p_app_id => :app_id,
- p_app_date => true,
- p_app_public_reports => true,
- p_app_private_reports => false,
- p_app_notifications => false,
- p_app_translations => true,
- p_app_pkg_app_mapping => false,
- p_app_original_ids => false,
- p_app_subscriptions => true,
- p_app_comments => true,
- p_app_supporting_objects => null,
- p_app_include_single_file => false,
- p_app_build_status_run_only => false,^';
- $end
- v_file_template := v_file_template || q'^
- p_include_object_ddl => true,
- p_object_type_like => null,
- p_object_type_not_like => null,
- p_object_name_like => null,
- p_object_name_not_like => null,
-
- p_include_data => false,
- p_data_as_of_minutes_ago => 0,
- p_data_max_rows => 1000,
- p_data_table_name_like => null,
- p_data_table_name_not_like => null,
-
- p_include_templates => true,
- p_include_runtime_log => true,
- p_include_error_log => true,
- p_base_path_backend => 'app_backend',
- p_base_path_frontend => 'app_frontend',
- p_base_path_data => 'app_data');
-
- -- relocate files to own project structure, we are inside the scripts folder
- FOR i IN 1..v_files.count LOOP
- v_files(i).name := '../' || v_files(i).name;
- END LOOP;
-
- FORALL i IN 1..v_files.count
- INSERT INTO temp_export_files VALUES (
- v_files(i).name,
- v_files(i).contents);
-END;
-{{/}}
-
-
-prompt Create intermediate script file to unload the table contents into files
-spool off
-set termout off serveroutput on
-spool "logs/temp_export_files.sql"
-BEGIN
- -- create host commands for the needed directories (spool does not create missing directories)
- FOR i IN (WITH t AS (SELECT regexp_substr(
- name,
- '^((\w|\.)+\/)+' /*path without file name*/) AS dir
- FROM temp_export_files)
- SELECT DISTINCT
- dir,
- -- This is for Windows to create a directory and suppress warning if it exist.
- -- Align the command to your operating system:
- 'host mkdir "' || replace(dir,'/','\') || '" 2>NUL' AS mkdir
- FROM t
- WHERE dir IS NOT NULL) LOOP
- dbms_output.put_line('set termout on');
- dbms_output.put_line('spool "&logfile." append');
- dbms_output.put_line('prompt --create directory if not exist: ' || i.dir);
- dbms_output.put_line('spool off');
- dbms_output.put_line('set termout off');
- dbms_output.put_line(i.mkdir);
- dbms_output.put_line('-----');
- END LOOP;
- -- create the spool calls for unload the files
- FOR i IN (SELECT * FROM temp_export_files) LOOP
- dbms_output.put_line('set termout on');
- dbms_output.put_line('spool "&logfile." append');
- dbms_output.put_line('prompt --' || i.name);
- dbms_output.put_line('spool off');
- dbms_output.put_line('set termout off');
- dbms_output.put_line('spool "' || i.name || '"');
- dbms_output.put_line('select contents from temp_export_files where name = ''' || i.name || ''';');
- dbms_output.put_line('spool off');
- dbms_output.put_line('-----');
- END LOOP;
-END;
-{{/}}
-spool off
-set termout on serveroutput off
-spool "&logfile." append
-
-
-prompt Call the intermediate script file to save the files
-spool off
-{{@}}logs/temp_export_files.sql
-set termout on serveroutput off
-spool "&logfile." append
-
-
-prompt Delete files from the global temporary table
-COMMIT;
-
-
-prompt =========================================================================
-prompt Export DONE :-)
-prompt
-^' ;
- v_file_path := 'scripts/templates/export_app_custom_code.sql';
- util_log_start(v_file_path);
- util_clob_append(util_multi_replace(
- v_file_template,
- '{{PLEX_VERSION}}', c_plex_version,
- '{{PLEX_URL}}', c_plex_url,
- '{{/}}', c_slash,
- '{{@}}', c_at));
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- --
- v_file_template := q'^-- Template generated by PLEX version {{PLEX_VERSION}}
--- More infos here: {{PLEX_URL}}
-
-set define on verify off feedback off
-whenever sqlerror exit sql.sqlcode rollback
--- whenever oserror exit failure rollback
-define logfile = "&1"
-spool "&logfile" append
-variable app_id varchar2(100)
-variable app_alias varchar2(100)
-variable app_schema varchar2(100)
-variable app_workspace varchar2(100)
-BEGIN
- :app_id := &2;
- :app_alias := '&3';
- :app_schema := '&4';
- :app_workspace := '&5';
-END;
-{{/}}
-set define off
-
-
-prompt
-prompt Start Installation
-prompt =========================================================================
-prompt Start backend installation
-
-prompt Call PLEX backend install script
-{{@}}install_backend_generated_by_plex.sql
-
-prompt Compile invalid objects
-BEGIN
- dbms_utility.compile_schema(
- schema => user,
- compile_all => false,
- reuse_settings => true);
-END;
-{{/}}
-
-prompt Check invalid objects
-DECLARE
- v_count PLS_INTEGER;
- v_objects VARCHAR2(4000);
-BEGIN
- SELECT COUNT(*), chr(10) ||
- listagg('- ' || object_name || ' (' || object_type || ')', chr(10)) within GROUP(ORDER BY object_name)
- INTO v_count, v_objects
- FROM user_objects
- WHERE status = 'INVALID';
- IF v_count > 0 THEN
- raise_application_error(-20000, chr(10) || chr(10) ||
- 'Found ' || v_count || ' invalid object' || CASE WHEN v_count > 1 THEN 's' END ||
- ' :-( ' || chr(10) || '=============================' || v_objects || chr(10) || chr(10) );
- END IF;
-END;
-{{/}}
-
-prompt Start frontend installation
-BEGIN
- apex_application_install.set_workspace_id(APEX_UTIL.find_security_group_id(:app_workspace));
- apex_application_install.set_application_alias(:app_alias);
- apex_application_install.set_application_id(:app_id);
- apex_application_install.set_schema(:app_schema);
- apex_application_install.generate_offset;
-END;
-{{/}}
-
-prompt Call APEX frontend install script
-{{@}}install_frontend_generated_by_APEX.sql
-
-prompt =========================================================================
-prompt Installation DONE :-)
-prompt
-^' ;
- v_file_path := 'scripts/templates/install_app_custom_code.sql';
- util_log_start(v_file_path);
- util_clob_append(util_multi_replace(
- v_file_template,
- '{{PLEX_VERSION}}', c_plex_version,
- '{{PLEX_URL}}', c_plex_url,
- '{{/}}', c_slash,
- '{{@}}', c_at));
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- END create_template_files;
-
- PROCEDURE create_directory_keepers IS
- v_the_point VARCHAR2(30) := '. < this is the point ;-)';
- BEGIN
- v_file_path := 'docs/_save_your_docs_here.txt';
- util_log_start(v_file_path);
- util_clob_append(v_the_point);
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- --
- v_file_path := 'scripts/logs/_spool_your_script_logs_here.txt';
- util_log_start(v_file_path);
- util_clob_append(v_the_point);
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- --
- v_file_path := 'tests/_save_your_tests_here.txt';
- util_log_start(v_file_path);
- util_clob_append(v_the_point);
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => v_file_path);
- util_log_stop;
- END create_directory_keepers;
-
- PROCEDURE finish IS
- BEGIN
- util_ensure_unique_file_names(v_export_files);
- IF p_include_error_log THEN
- util_clob_create_error_log(v_export_files);
- END IF;
- IF p_include_runtime_log THEN
- util_clob_create_runtime_log(v_export_files);
- END IF;
- END finish;
-
-BEGIN
- init;
- $if $$apex_installed $then
- check_owner;
- IF p_app_id IS NOT NULL THEN
- process_apex_app;
- END IF;
- $end
- IF p_include_object_ddl THEN
- process_user_ddl;
- process_object_ddl;
- $if NOT $$debug_on $then
- -- excluded in debug mode (potential long running object types)
- process_object_grants;
- process_ref_constraints;
- $end
- create_backend_install_file;
- END IF;
- IF p_include_data THEN
- process_data;
- END IF;
- IF p_include_templates THEN
- create_template_files;
- create_directory_keepers;
- END IF;
- finish;
- RETURN v_export_files;
-END backapp;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE add_query (
- p_query VARCHAR2,
- p_file_name VARCHAR2,
- p_max_rows NUMBER DEFAULT 1000)
-IS
- v_index PLS_INTEGER;
-BEGIN
- v_index := g_queries.count + 1;
- g_queries(v_index).query := p_query;
- g_queries(v_index).file_name := p_file_name;
- g_queries(v_index).max_rows := p_max_rows;
-END add_query;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION queries_to_csv (
- p_delimiter IN VARCHAR2 DEFAULT ',',
- p_quote_mark IN VARCHAR2 DEFAULT '"',
- p_header_prefix IN VARCHAR2 DEFAULT NULL,
- p_include_runtime_log IN BOOLEAN DEFAULT true,
- p_include_error_log IN BOOLEAN DEFAULT true)
-RETURN tab_export_files IS
- v_export_files tab_export_files;
-
- PROCEDURE init IS
- BEGIN
- IF g_queries.count = 0 THEN
- raise_application_error(
- -20201,
- 'You need first to add queries by using plex.add_query. Calling plex.queries_to_csv clears the global queries array for subsequent processing.');
- END IF;
- util_log_init(p_module => 'plex.queries_to_csv');
- util_log_start('init');
- v_export_files := NEW tab_export_files();
- util_log_stop;
- END init;
-
- PROCEDURE process_queries IS
- BEGIN
- FOR i IN g_queries.first..g_queries.last LOOP
- BEGIN
- util_log_start('process_query ' || TO_CHAR(i) || ': ' || g_queries(i).file_name);
- util_clob_query_to_csv(
- p_query => g_queries(i).query,
- p_max_rows => g_queries(i).max_rows,
- p_delimiter => p_delimiter,
- p_quote_mark => p_quote_mark,
- p_header_prefix => p_header_prefix);
- util_clob_add_to_export_files(
- p_export_files => v_export_files,
- p_name => g_queries(i).file_name || '.csv');
- util_log_stop;
- EXCEPTION
- WHEN OTHERS THEN
- util_log_error(g_queries(i).file_name);
- END;
- END LOOP;
- END process_queries;
-
- PROCEDURE finish IS
- BEGIN
- g_queries.DELETE;
- util_ensure_unique_file_names(v_export_files);
- IF p_include_error_log THEN
- util_clob_create_error_log(v_export_files);
- END IF;
- IF p_include_runtime_log THEN
- util_clob_create_runtime_log(v_export_files);
- END IF;
- END finish;
-
-BEGIN
- init;
- process_queries;
- finish;
- RETURN v_export_files;
-EXCEPTION
- WHEN others THEN
- g_queries.DELETE;
-END queries_to_csv;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION to_zip (p_file_collection IN tab_export_files) RETURN BLOB IS
- v_zip BLOB;
-BEGIN
- dbms_lob.createtemporary(v_zip, true);
- util_log_start('post processing with to_zip: ' || p_file_collection.count || ' files');
- FOR i IN 1..p_file_collection.count LOOP
- util_zip_add_file(
- p_zipped_blob => v_zip,
- p_name => p_file_collection(i).name,
- p_content => util_clob_to_blob(p_file_collection(i).contents));
- END LOOP;
- util_zip_finish(v_zip);
- util_log_stop;
- util_log_calc_runtimes;
- RETURN v_zip;
-END to_zip;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION view_error_log RETURN tab_error_log PIPELINED IS
-BEGIN
- FOR i IN 1..g_errlog.count LOOP
- PIPE ROW (g_errlog(i));
- END LOOP;
-END view_error_log;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION view_runtime_log RETURN tab_runtime_log PIPELINED IS
- v_return rec_runtime_log;
-BEGIN
- v_return.overall_start_time := g_runlog.start_time;
- v_return.overall_run_time := round(g_runlog.run_time, 3);
- FOR i IN 1..g_runlog.data.count LOOP
- v_return.step := i;
- v_return.elapsed := round(g_runlog.data(i).elapsed, 3);
- v_return.execution := round(g_runlog.data(i).execution, 6);
- v_return.module := g_runlog.module;
- v_return.action := g_runlog.data(i).action;
- PIPE ROW (v_return);
- END LOOP;
-END view_runtime_log;
-
---------------------------------------------------------------------------------------------------------------------------------
-
-BEGIN
- IF dbms_lob.istemporary(g_clob) = 0 THEN
- dbms_lob.createtemporary(g_clob, true);
- END IF;
-END plex;
+CREATE OR REPLACE PACKAGE BODY plex IS
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- CONSTANTS, TYPES, GLOBALS
+--------------------------------------------------------------------------------------------------------------------------------
+
+c_tab CONSTANT VARCHAR2(1) := chr(9);
+c_cr CONSTANT VARCHAR2(1) := chr(13);
+c_lf CONSTANT VARCHAR2(1) := chr(10);
+c_crlf CONSTANT VARCHAR2(2) := chr(13) || chr(10);
+c_space_crlf CONSTANT VARCHAR2(3) := ' ' || chr(13) || chr(10);
+c_at CONSTANT VARCHAR2(1) := '@';
+c_hash CONSTANT VARCHAR2(1) := '#';
+c_slash CONSTANT VARCHAR2(1) := '/';
+c_vc2_max_size CONSTANT PLS_INTEGER := 32767;
+c_zip_local_file_header CONSTANT RAW(4) := hextoraw('504B0304');
+c_zip_end_of_central_directory CONSTANT RAW(4) := hextoraw('504B0506');
+
+TYPE tab_errlog IS TABLE OF rec_error_log INDEX BY BINARY_INTEGER;
+
+TYPE rec_runlog_step IS RECORD (
+ action app_info_text,
+ start_time TIMESTAMP(6),
+ stop_time TIMESTAMP(6),
+ elapsed NUMBER,
+ execution NUMBER);
+TYPE tab_runlog_step IS TABLE OF rec_runlog_step INDEX BY BINARY_INTEGER;
+
+TYPE rec_runlog IS RECORD (
+ module app_info_text,
+ start_time TIMESTAMP(6),
+ stop_time TIMESTAMP(6),
+ run_time NUMBER,
+ measured_time NUMBER,
+ unmeasured_time NUMBER,
+ data tab_runlog_step);
+TYPE rec_queries IS RECORD (--
+ query VARCHAR2(32767 CHAR),
+ file_name VARCHAR2(256 CHAR),
+ max_rows NUMBER DEFAULT 100000);
+TYPE tab_queries IS TABLE OF rec_queries INDEX BY BINARY_INTEGER;
+
+TYPE tab_file_list_lookup IS TABLE OF PLS_INTEGER INDEX BY VARCHAR2(256);
+
+TYPE rec_ddl_files IS RECORD (
+ ords_modules_ tab_vc1k,
+ sequences_ tab_vc1k,
+ tables_ tab_vc1k,
+ ref_constraints_ tab_vc1k,
+ indices_ tab_vc1k,
+ views_ tab_vc1k,
+ mviews_ tab_vc1k,
+ types_ tab_vc1k,
+ type_bodies_ tab_vc1k,
+ triggers_ tab_vc1k,
+ functions_ tab_vc1k,
+ procedures_ tab_vc1k,
+ packages_ tab_vc1k,
+ package_bodies_ tab_vc1k,
+ grants_ tab_vc1k,
+ other_objects_ tab_vc1k);
+
+g_clob CLOB;
+g_cache VARCHAR2(32767char);
+g_errlog tab_errlog;
+g_runlog rec_runlog;
+g_queries tab_queries;
+
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- UTILITIES (forward declarations, only compiled when not public)
+--------------------------------------------------------------------------------------------------------------------------------
+
+$if not $$utils_public $then
+FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2;
+
+FUNCTION util_string_to_bool (
+ p_bool_string IN VARCHAR2,
+ p_default IN BOOLEAN)
+RETURN BOOLEAN;
+
+FUNCTION util_split (
+ p_string IN VARCHAR2,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN tab_vc32k;
+
+FUNCTION util_join (
+ p_array IN tab_vc32k,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN VARCHAR2;
+
+FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB;
+
+/*
+ZIP UTILS
+- The following four zip utilities are copied from this article:
+ - Blog: https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/
+ - Source: https://technology.amis.nl/wp-content/uploads/2010/06/as_zip10.txt
+- Copyright (c) 2010, 2011 by Anton Scheffer (MIT license)
+- Thank you for sharing this Anton :-)
+*/
+FUNCTION util_zip_blob_to_num (
+ p_blob IN BLOB,
+ p_len IN INTEGER,
+ p_pos IN INTEGER)
+RETURN NUMBER;
+
+FUNCTION util_zip_little_endian (
+ p_big IN NUMBER,
+ p_bytes IN PLS_INTEGER := 4)
+RETURN RAW;
+
+PROCEDURE util_zip_add_file (
+ p_zipped_blob IN OUT BLOB,
+ p_name IN VARCHAR2,
+ p_content IN BLOB);
+
+PROCEDURE util_zip_finish (p_zipped_blob IN OUT BLOB);
+
+FUNCTION util_multi_replace (
+ p_source_string VARCHAR2,
+ p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
+ p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
+ p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
+ p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
+ p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
+ p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
+ p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
+ p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
+ p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
+ p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
+ p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
+ p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
+RETURN VARCHAR2;
+
+FUNCTION util_set_build_status_run_only (p_app_export_sql IN CLOB) RETURN CLOB;
+
+FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP;
+
+PROCEDURE util_setup_dbms_metadata (
+ p_pretty IN BOOLEAN DEFAULT true,
+ p_constraints IN BOOLEAN DEFAULT true,
+ p_ref_constraints IN BOOLEAN DEFAULT false,
+ p_partitioning IN BOOLEAN DEFAULT true,
+ p_tablespace IN BOOLEAN DEFAULT false,
+ p_storage IN BOOLEAN DEFAULT false,
+ 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);
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- The following tools are working on the global private package variables g_clob, g_clob_varchar_cache, g_runlog and g_queries
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_append (p_content IN VARCHAR2);
+
+PROCEDURE util_clob_append (p_content IN CLOB);
+
+PROCEDURE util_clob_flush_cache;
+
+PROCEDURE util_clob_add_to_export_files (
+ p_export_files IN OUT NOCOPY tab_export_files,
+ p_name IN VARCHAR2);
+
+PROCEDURE util_clob_query_to_csv (
+ p_query IN VARCHAR2,
+ p_max_rows IN NUMBER DEFAULT 1000,
+ p_delimiter IN VARCHAR2 DEFAULT ',',
+ p_quote_mark IN VARCHAR2 DEFAULT '"',
+ p_header_prefix IN VARCHAR2 DEFAULT NULL);
+
+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);
+
+PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files);
+
+PROCEDURE util_log_init (p_module IN VARCHAR2);
+
+PROCEDURE util_log_start (p_action IN VARCHAR2);
+
+PROCEDURE util_log_error (p_name VARCHAR2);
+
+PROCEDURE util_log_stop;
+
+FUNCTION util_log_get_runtime (
+ p_start IN TIMESTAMP,
+ p_stop IN TIMESTAMP)
+RETURN NUMBER;
+
+PROCEDURE util_log_calc_runtimes;
+
+$end
+
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- UTILITIES MAIN CODE
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2 IS
+BEGIN
+ RETURN CASE WHEN p_bool THEN 'TRUE' ELSE 'FALSE' END;
+END util_bool_to_string;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_string_to_bool (
+ p_bool_string IN VARCHAR2,
+ p_default IN BOOLEAN)
+RETURN BOOLEAN IS
+ v_bool_string VARCHAR2(1 CHAR);
+ v_return BOOLEAN;
+BEGIN
+ v_bool_string := upper(substr(p_bool_string, 1, 1));
+ v_return :=
+ CASE
+ WHEN v_bool_string IN ('1', 'Y', 'T') THEN
+ true
+ WHEN v_bool_string IN ('0', 'N', 'F') THEN
+ false
+ ELSE p_default
+ END;
+ RETURN v_return;
+END util_string_to_bool;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_split (
+ p_string IN VARCHAR2,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN tab_vc32k IS
+ v_return tab_vc32k := tab_vc32k();
+ v_offset PLS_INTEGER := 1;
+ v_index PLS_INTEGER := instr(p_string, p_delimiter, v_offset);
+ v_delimiter_length PLS_INTEGER := length(p_delimiter);
+ v_string_length CONSTANT PLS_INTEGER := length(p_string);
+ v_count PLS_INTEGER := 1;
+
+ PROCEDURE add_value (p_value VARCHAR2) IS
+ BEGIN
+ v_return.extend;
+ v_return(v_count) := p_value;
+ v_count := v_count + 1;
+ END add_value;
+
+BEGIN
+ WHILE v_index > 0 LOOP
+ add_value(trim(substr(p_string, v_offset, v_index - v_offset)));
+ v_offset := v_index + v_delimiter_length;
+ v_index := instr(p_string, p_delimiter, v_offset);
+ END LOOP;
+ IF v_string_length - v_offset + 1 > 0 THEN
+ add_value(trim(substr(p_string, v_offset, v_string_length - v_offset + 1)));
+ END IF;
+ RETURN v_return;
+END util_split;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_join (
+ p_array IN tab_vc32k,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN VARCHAR2 IS
+ v_return VARCHAR2(32767);
+BEGIN
+ IF p_array IS NOT NULL AND p_array.count > 0 THEN
+ v_return := p_array(1);
+ FOR i IN 2 ..p_array.count LOOP
+ v_return := v_return || p_delimiter || p_array(i);
+ END LOOP;
+ END IF;
+ RETURN v_return;
+EXCEPTION
+ WHEN value_error THEN
+ RETURN v_return;
+END util_join;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB IS
+ v_blob BLOB;
+ v_lang_context INTEGER := dbms_lob.default_lang_ctx;
+ v_warning INTEGER := dbms_lob.warn_inconvertible_char;
+ v_dest_offset INTEGER := 1;
+ v_src_offset INTEGER := 1;
+BEGIN
+ IF p_clob IS NOT NULL THEN
+ dbms_lob.createtemporary(v_blob, true);
+ dbms_lob.converttoblob(
+ dest_lob => v_blob,
+ src_clob => p_clob,
+ amount => dbms_lob.lobmaxsize,
+ dest_offset => v_dest_offset,
+ src_offset => v_src_offset,
+ blob_csid => nls_charset_id('AL32UTF8'),
+ lang_context => v_lang_context,
+ warning => v_warning);
+ END IF;
+ RETURN v_blob;
+END util_clob_to_blob;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
+FUNCTION util_zip_blob_to_num (
+ p_blob IN BLOB,
+ p_len IN INTEGER,
+ p_pos IN 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 util_zip_blob_to_num;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
+FUNCTION util_zip_little_endian (
+ p_big IN NUMBER,
+ p_bytes IN 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 util_zip_little_endian;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
+PROCEDURE util_zip_add_file (
+ p_zipped_blob IN OUT BLOB,
+ p_name IN VARCHAR2,
+ p_content IN 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;
+ t_name := utl_i18n.string_to_raw(p_name, 'AL32UTF8');
+ dbms_lob.append(
+ p_zipped_blob,
+ utl_raw.concat(
+ c_zip_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,
+ util_zip_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
+ util_zip_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
+ util_zip_little_endian(t_clen), -- compressed size
+ util_zip_little_endian(t_len), -- uncompressed size
+ util_zip_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 util_zip_add_file;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Anton Scheffer (MIT license, see https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/)
+PROCEDURE util_zip_finish (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_zip_local_file_header), t_offs) = c_zip_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 + util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26) - 1)
+ IN (hextoraw('2F')/*slash*/, hextoraw('5C')/*backslash*/)
+ THEN hextoraw('10000000') -- a directory/folder
+ ELSE hextoraw('2000B681') -- a file
+ END, -- external file attributes
+ util_zip_little_endian(t_offs - 1), -- relative offset of local file header
+ dbms_lob.substr(
+ p_zipped_blob,
+ util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26),
+ t_offs + 30))); -- File name
+ t_offs := t_offs + 30
+ + util_zip_blob_to_num(p_zipped_blob, 4, t_offs + 18) -- compressed size
+ + util_zip_blob_to_num(p_zipped_blob, 2, t_offs + 26) -- file name length
+ + util_zip_blob_to_num(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_zip_end_of_central_directory, -- end of central directory signature
+ hextoraw('0000'), -- number of this disk
+ hextoraw('0000'), -- disk where central directory starts
+ util_zip_little_endian(t_cnt, 2), -- number of central directory records on this disk
+ util_zip_little_endian(t_cnt, 2), -- total number of central directory records
+ util_zip_little_endian(t_offs_end_header - t_offs_dir_header), -- size of central directory
+ util_zip_little_endian(t_offs_dir_header), -- offset of start of central directory, relative to start of archive
+ util_zip_little_endian(nvl(utl_raw.length(t_comment), 0), 2), -- ZIP file comment length
+ t_comment));
+END util_zip_finish;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_multi_replace (
+ p_source_string VARCHAR2,
+ p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
+ p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
+ p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
+ p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
+ p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
+ p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
+ p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
+ p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
+ p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
+ p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
+ p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
+ p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
+RETURN VARCHAR2 IS
+ v_return VARCHAR2(32767);
+BEGIN
+ v_return := p_source_string;
+ IF p_01_find IS NOT NULL THEN v_return := replace(v_return, p_01_find, p_01_replace); END IF;
+ IF p_02_find IS NOT NULL THEN v_return := replace(v_return, p_02_find, p_02_replace); END IF;
+ IF p_03_find IS NOT NULL THEN v_return := replace(v_return, p_03_find, p_03_replace); END IF;
+ IF p_04_find IS NOT NULL THEN v_return := replace(v_return, p_04_find, p_04_replace); END IF;
+ IF p_05_find IS NOT NULL THEN v_return := replace(v_return, p_05_find, p_05_replace); END IF;
+ IF p_06_find IS NOT NULL THEN v_return := replace(v_return, p_06_find, p_06_replace); END IF;
+ IF p_07_find IS NOT NULL THEN v_return := replace(v_return, p_07_find, p_07_replace); END IF;
+ IF p_08_find IS NOT NULL THEN v_return := replace(v_return, p_08_find, p_08_replace); END IF;
+ IF p_09_find IS NOT NULL THEN v_return := replace(v_return, p_09_find, p_09_replace); END IF;
+ IF p_10_find IS NOT NULL THEN v_return := replace(v_return, p_10_find, p_10_replace); END IF;
+ IF p_11_find IS NOT NULL THEN v_return := replace(v_return, p_11_find, p_11_replace); END IF;
+ IF p_12_find IS NOT NULL THEN v_return := replace(v_return, p_12_find, p_12_replace); END IF;
+ RETURN v_return;
+END util_multi_replace;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_set_build_status_run_only (p_app_export_sql CLOB) RETURN CLOB IS
+ v_position PLS_INTEGER;
+BEGIN
+ v_position := instr(p_app_export_sql, ',p_exact_substitutions_only');
+ RETURN substr(p_app_export_sql, 1, v_position - 1)
+ || ',p_build_status=>''RUN_ONLY'''
+ || c_lf
+ || substr(p_app_export_sql, v_position);
+END util_set_build_status_run_only;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP IS
+ v_return TIMESTAMP;
+BEGIN
+ EXECUTE IMMEDIATE
+ replace(
+ q'[SELECT systimestamp - INTERVAL '{{MINUTES}}' MINUTE FROM dual]',
+ '{{MINUTES}}',
+ TO_CHAR(p_as_of_minutes_ago))
+ INTO v_return;
+ RETURN v_return;
+END util_calc_data_timestamp;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_setup_dbms_metadata (
+ p_pretty IN BOOLEAN DEFAULT true,
+ p_constraints IN BOOLEAN DEFAULT true,
+ p_ref_constraints IN BOOLEAN DEFAULT false,
+ p_partitioning IN BOOLEAN DEFAULT true,
+ p_tablespace IN BOOLEAN DEFAULT false,
+ p_storage IN BOOLEAN DEFAULT false,
+ 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)
+IS
+BEGIN
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'PRETTY', p_pretty);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'CONSTRAINTS', p_constraints);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'REF_CONSTRAINTS', p_ref_constraints);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'PARTITIONING', p_partitioning);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'TABLESPACE', p_tablespace);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'STORAGE', p_storage);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'SEGMENT_ATTRIBUTES', p_segment_attributes);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'SQLTERMINATOR', p_sqlterminator);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'CONSTRAINTS_AS_ALTER', p_constraints_as_alter);
+ dbms_metadata.set_transform_param(dbms_metadata.session_transform, 'EMIT_SCHEMA', p_emit_schema);
+END util_setup_dbms_metadata;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files) IS
+ v_file_list_lookup tab_file_list_lookup;
+ v_apex_install_file_id PLS_INTEGER;
+ v_file_name VARCHAR2(256);
+ v_extension VARCHAR2(256);
+ v_base_name VARCHAR2(256);
+ v_count PLS_INTEGER;
+BEGIN
+ util_log_start('ensure unique file names in collection');
+ $if $$apex_installed $then
+ -- find apex install file
+ FOR i IN 1..p_export_files.count LOOP
+ IF p_export_files(i).name = 'scripts/install_frontend_generated_by_apex.sql' THEN
+ v_apex_install_file_id := i;
+ END IF;
+ END LOOP;
+ $end
+ FOR i IN 1..p_export_files.count LOOP
+ v_file_name := p_export_files(i).name;
+ v_count := 1;
+ IF instr(v_file_name, '.') > 0 THEN
+ v_base_name := substr(v_file_name, 1, instr(v_file_name, '.', -1) - 1);
+ v_extension := substr(v_file_name, instr(v_file_name, '.', -1));
+ ELSE
+ v_base_name := v_file_name;
+ v_extension := NULL;
+ END IF;
+ WHILE v_file_list_lookup.EXISTS(v_file_name) LOOP
+ v_count := v_count + 1;
+ v_file_name := v_base_name || '_' || v_count || v_extension;
+ END LOOP;
+ v_file_list_lookup(v_file_name) := i;
+ -- correct data if needed
+ IF p_export_files(i).name != v_file_name THEN
+ -- correct the prompt statement
+ p_export_files(i).contents := replace(
+ p_export_files(i).contents,
+ v_base_name,
+ v_base_name || '_' || v_count);
+ -- correct the apex install file
+ IF v_apex_install_file_id IS NOT NULL THEN
+ p_export_files(v_apex_install_file_id).contents := regexp_replace(
+ p_export_files(v_apex_install_file_id).contents,
+ p_export_files(i).name || '$',
+ v_file_name,
+ 1, 2, 'm');
+ END IF;
+ -- correct the file name itself
+ p_export_files(i).name := v_file_name;
+ END IF;
+ END LOOP;
+ util_log_stop;
+END util_ensure_unique_file_names;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_init (p_module IN VARCHAR2) IS
+BEGIN
+ g_runlog.module := substr(p_module, 1, c_app_info_length);
+ g_runlog.start_time := systimestamp;
+ g_runlog.stop_time := NULL;
+ g_runlog.run_time := 0;
+ g_runlog.measured_time := 0;
+ g_runlog.unmeasured_time := 0;
+ g_runlog.data.DELETE;
+ g_errlog.DELETE;
+END util_log_init;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_start (p_action IN VARCHAR2) IS
+ v_index PLS_INTEGER;
+BEGIN
+ dbms_application_info.set_module(
+ module_name => g_runlog.module,
+ action_name => p_action);
+ v_index := g_runlog.data.count + 1;
+ g_runlog.data(v_index).action := substr(p_action, 1, plex.c_app_info_length);
+ g_runlog.data(v_index).start_time := systimestamp;
+END util_log_start;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_error (p_name VARCHAR2) IS
+ v_index PLS_INTEGER;
+ PROCEDURE add_error_to_action IS
+ v_index PLS_INTEGER;
+ BEGIN
+ v_index := g_runlog.data.count;
+ g_runlog.data(v_index).action := substr('ERROR: ' || g_runlog.data(v_index).action, 1, plex.c_app_info_length);
+ END add_error_to_action;
+BEGIN
+ v_index := g_errlog.count + 1;
+ g_errlog(v_index).time_stamp := systimestamp;
+ g_errlog(v_index).file_name := substr(p_name, 1, 255);
+ g_errlog(v_index).error_text := substr(sqlerrm, 1, 200);
+ g_errlog(v_index).call_stack := substr(dbms_utility.format_error_backtrace, 1, 500);
+ add_error_to_action;
+ util_log_stop;
+ g_clob := null;
+END util_log_error;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_stop IS
+ v_index PLS_INTEGER;
+BEGIN
+ v_index := g_runlog.data.count;
+ dbms_application_info.set_module(
+ module_name => NULL,
+ action_name => NULL);
+ g_runlog.data(v_index).stop_time := systimestamp;
+ g_runlog.data(v_index).elapsed := util_log_get_runtime(g_runlog.start_time, g_runlog.data(v_index).stop_time);
+ g_runlog.data(v_index).execution := util_log_get_runtime(g_runlog.data(v_index).start_time, g_runlog.data(v_index).stop_time);
+ g_runlog.measured_time := g_runlog.measured_time + g_runlog.data(v_index).execution;
+END util_log_stop;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION util_log_get_runtime (
+ p_start IN TIMESTAMP,
+ p_stop IN TIMESTAMP)
+RETURN NUMBER IS
+BEGIN
+ RETURN SYSDATE + ((p_stop - p_start) * 86400) - SYSDATE;
+ --sysdate + (interval_difference * 86400) - sysdate
+ --https://stackoverflow.com/questions/10092032/extracting-the-total-number-of-seconds-from-an-interval-data-type
+END util_log_get_runtime;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_calc_runtimes IS
+BEGIN
+ g_runlog.stop_time := systimestamp;
+ g_runlog.run_time := util_log_get_runtime(g_runlog.start_time, g_runlog.stop_time);
+ g_runlog.unmeasured_time := g_runlog.run_time - g_runlog.measured_time;
+END util_log_calc_runtimes;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_append (p_content IN VARCHAR2) IS
+BEGIN
+ g_cache := g_cache || p_content;
+EXCEPTION
+ WHEN value_error THEN
+ IF g_clob IS NULL THEN
+ g_clob := g_cache;
+ ELSE
+ dbms_lob.writeappend(g_clob, length(g_cache), g_cache);
+ END IF;
+ g_cache := p_content;
+END util_clob_append;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_append (p_content IN CLOB) IS
+BEGIN
+ IF p_content IS NOT NULL THEN
+ util_clob_flush_cache;
+ IF g_clob IS NULL THEN
+ g_clob := p_content;
+ ELSE
+ dbms_lob.writeappend(g_clob, length(p_content), p_content);
+ END IF;
+ END IF;
+END util_clob_append;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_flush_cache IS
+BEGIN
+ IF g_cache IS NOT NULL THEN
+ IF g_clob IS NULL THEN
+ g_clob := g_cache;
+ ELSE
+ dbms_lob.writeappend(g_clob, length(g_cache), g_cache);
+ END IF;
+ g_cache := NULL;
+ END IF;
+END util_clob_flush_cache;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_add_to_export_files (
+ p_export_files IN OUT NOCOPY tab_export_files,
+ p_name IN VARCHAR2)
+IS
+ v_index PLS_INTEGER;
+BEGIN
+ util_clob_flush_cache;
+ v_index := p_export_files.count + 1;
+ p_export_files.extend;
+ p_export_files(v_index).name := p_name;
+ p_export_files(v_index).contents := g_clob;
+ g_clob := null;
+END util_clob_add_to_export_files;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_query_to_csv (
+ p_query IN VARCHAR2,
+ p_max_rows IN NUMBER DEFAULT 1000,
+ p_delimiter IN VARCHAR2 DEFAULT ',',
+ p_quote_mark IN VARCHAR2 DEFAULT '"',
+ p_header_prefix IN VARCHAR2 DEFAULT NULL)
+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_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
+ 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 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 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;
+
+BEGIN
+ IF p_query IS NOT NULL THEN
+ v_cursor := dbms_sql.open_cursor;
+ dbms_sql.parse(
+ v_cursor,
+ regexp_replace(p_query, ';\s*$', NULL),
+ 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);
+ util_clob_append(p_header_prefix);
+ FOR i IN 1..v_col_cnt LOOP
+ IF i > 1 THEN
+ util_clob_append(p_delimiter);
+ END IF;
+ 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 i > 1 THEN
+ util_clob_append(p_delimiter);
+ END IF;
+ 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_query_to_csv;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_create_error_log (p_export_files IN OUT NOCOPY tab_export_files) IS
+BEGIN
+ IF g_errlog.count > 0 THEN
+ util_log_start(g_errlog.count || ' error' || CASE WHEN g_errlog.count != 1 THEN 's' END || ' occurred: create error log');
+ util_clob_append(
+ replace('# {{MAIN_FUNCTION}} - Error Log', '{{MAIN_FUNCTION}}', upper(g_runlog.module))
+ || c_crlf || c_crlf || c_crlf);
+ FOR i IN 1..g_errlog.count LOOP
+ util_clob_append('## ' || g_errlog(i).file_name || c_crlf || c_crlf);
+ util_clob_append(to_char(g_errlog(i).time_stamp, 'yyyy-mm-dd hh24:mi:ss.ffffff') || ': ' || g_errlog(i).error_text || c_crlf || c_crlf);
+ util_clob_append('```sql' || c_crlf || g_errlog(i).call_stack || '```' || c_crlf || c_crlf || c_crlf);
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => p_export_files,
+ p_name => 'plex_error_log.md');
+ util_log_stop;
+ END IF;
+END util_clob_create_error_log;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_clob_create_runtime_log (p_export_files IN OUT NOCOPY tab_export_files) IS
+BEGIN
+ util_log_calc_runtimes;
+ util_clob_append(util_multi_replace('# {{MAIN_FUNCTION}} - Runtime Log
+
+- Export started at {{START_TIME}} and took {{RUN_TIME}} seconds to finish with {{ERRORS}}
+- Unmeasured execution time because of system waits, missing log calls or log overhead was {{UNMEASURED_TIME}} seconds
+- The used PLEX version was {{PLEX_VERSION}}
+- More infos here: [PLEX on GitHub]({{PLEX_URL}})
+
+' ,
+ '{{MAIN_FUNCTION}}', upper(g_runlog.module),
+ '{{START_TIME}}', TO_CHAR(g_runlog.start_time, 'yyyy-mm-dd hh24:mi:ss'),
+ '{{RUN_TIME}}', trim(TO_CHAR(g_runlog.run_time, '999G990D000')),
+ '{{UNMEASURED_TIME}}', trim(TO_CHAR(g_runlog.unmeasured_time, '999G990D000')),
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{ERRORS}}', g_errlog.count || ' error' || CASE WHEN g_errlog.count != 1 THEN 's' END));
+ util_clob_append('
+| Step | Elapsed | Execution | Action |
+|------:|----------:|------------:|:-----------------------------------------------------------------|
+' );
+ FOR i IN 1..g_runlog.data.count LOOP
+ util_clob_append(util_multi_replace(
+ '| {{STEP}} | {{ELAPSED}} | {{EXECUTION}} | {{ACTION}} |' || c_lf,
+ '{{STEP}}', lpad(TO_CHAR(i), 5),
+ '{{ELAPSED}}', lpad(trim(TO_CHAR(g_runlog.data(i).elapsed, '99990D000')), 9),
+ '{{EXECUTION}}', lpad(trim(TO_CHAR(g_runlog.data(i).execution, '9990D000000')), 11),
+ '{{ACTION}}', rpad(g_runlog.data(i).action, 64)));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => p_export_files,
+ p_name => 'plex_runtime_log.md');
+END util_clob_create_runtime_log;
+
+
+
+------------------------------------------------------------------------------------------------------------------------------
+-- MAIN CODE
+------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION backapp (
+ $if $$apex_installed $then
+ p_app_id IN NUMBER DEFAULT NULL,
+ p_app_date IN BOOLEAN DEFAULT true,
+ p_app_public_reports IN BOOLEAN DEFAULT true,
+ p_app_private_reports IN BOOLEAN DEFAULT false,
+ p_app_notifications IN BOOLEAN DEFAULT false,
+ p_app_translations IN BOOLEAN DEFAULT true,
+ p_app_pkg_app_mapping IN BOOLEAN DEFAULT false,
+ p_app_original_ids IN BOOLEAN DEFAULT false,
+ p_app_subscriptions IN BOOLEAN DEFAULT true,
+ p_app_comments IN BOOLEAN DEFAULT true,
+ 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
+ $if $$ords_installed $then
+ p_include_ords_modules IN BOOLEAN DEFAULT false,
+ $end
+ p_include_object_ddl IN BOOLEAN DEFAULT false,
+ p_object_type_like IN VARCHAR2 DEFAULT NULL,
+ 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_include_data IN BOOLEAN DEFAULT false,
+ p_data_as_of_minutes_ago IN NUMBER DEFAULT 0,
+ p_data_max_rows IN NUMBER DEFAULT 1000,
+ p_data_table_name_like IN VARCHAR2 DEFAULT NULL,
+ p_data_table_name_not_like IN VARCHAR2 DEFAULT NULL,
+ p_include_templates IN BOOLEAN DEFAULT true,
+ p_include_runtime_log IN BOOLEAN DEFAULT true,
+ p_include_error_log IN BOOLEAN DEFAULT true,
+ p_base_path_backend IN VARCHAR2 DEFAULT 'app_backend',
+ p_base_path_frontend IN VARCHAR2 DEFAULT 'app_frontend',
+ p_base_path_web_services IN VARCHAR2 DEFAULT 'app_web_services',
+ p_base_path_data IN VARCHAR2 DEFAULT 'app_data')
+RETURN tab_export_files IS
+ v_apex_version NUMBER;
+ v_data_timestamp TIMESTAMP;
+ v_data_scn NUMBER;
+ v_file_path VARCHAR2(255);
+ v_current_user user_objects.object_name%TYPE;
+ v_app_workspace user_objects.object_name%TYPE;
+ v_app_owner user_objects.object_name%TYPE;
+ v_app_alias user_objects.object_name%TYPE;
+ v_ddl_files rec_ddl_files;
+ v_contents CLOB;
+ v_export_files tab_export_files;
+ v_file_list_lookup tab_file_list_lookup;
+ TYPE obj_cur_typ IS REF CURSOR;
+ v_cur obj_cur_typ;
+ v_query VARCHAR2(32767);
+
+ FUNCTION util_get_script_line (p_file_path VARCHAR2) RETURN VARCHAR2 IS
+ BEGIN
+ RETURN 'prompt --' || replace(p_file_path, '.sql', NULL)
+ || c_lf || '@' || '../' || p_file_path || c_lf || c_lf;
+ END util_get_script_line;
+
+ PROCEDURE init IS
+ BEGIN
+ util_log_init(
+ p_module => 'plex.backapp'
+ $if $$apex_installed $then
+ || CASE WHEN p_app_id IS NOT NULL THEN '(' || TO_CHAR(p_app_id) || ')' END
+ $end);
+ util_log_start('init');
+ v_export_files := NEW tab_export_files();
+ v_current_user := sys_context('USERENV', 'CURRENT_USER');
+ util_log_stop;
+ END init;
+
+ $if $$apex_installed $then
+ PROCEDURE check_owner IS
+ CURSOR cur_owner IS
+ SELECT workspace,
+ owner,
+ alias
+ FROM apex_applications t
+ WHERE t.application_id = p_app_id;
+ BEGIN
+ util_log_start('check_owner');
+ IF p_app_id IS NOT NULL THEN
+ OPEN cur_owner;
+ FETCH cur_owner INTO
+ v_app_workspace,
+ v_app_owner,
+ v_app_alias;
+ CLOSE cur_owner;
+ END IF;
+ IF p_app_id IS NOT NULL AND v_app_owner IS NULL THEN
+ raise_application_error(
+ -20101,
+ 'Could not find owner for application - are you sure you provided the right app_id?');
+ ELSIF p_app_id IS NOT NULL AND v_app_owner != v_current_user THEN
+ raise_application_error(
+ -20102,
+ 'You are not the owner of the app - please login as the owner.');
+ END IF;
+ util_log_stop;
+ END check_owner;
+ $end
+
+ $if $$apex_installed $then
+ PROCEDURE process_apex_app IS
+ v_apex_files apex_t_export_files;
+ BEGIN
+ -- save as individual files
+ util_log_start(p_base_path_frontend || '/APEX_EXPORT:individual_files');
+ v_apex_files := apex_export.get_application(
+ p_application_id => p_app_id,
+ p_split => true,
+ p_with_date => p_app_date,
+ p_with_ir_public_reports => p_app_public_reports,
+ p_with_ir_private_reports => p_app_private_reports,
+ p_with_ir_notifications => p_app_notifications,
+ p_with_translations => p_app_translations,
+ p_with_pkg_app_mapping => p_app_pkg_app_mapping,
+ p_with_original_ids => p_app_original_ids,
+ p_with_no_subscriptions => CASE WHEN p_app_subscriptions THEN false ELSE true END,
+ p_with_comments => p_app_comments,
+ p_with_supporting_objects => p_app_supporting_objects);
+ FOR i IN 1..v_apex_files.count LOOP
+ v_export_files.extend;
+ -- relocate files to own project structure
+ v_export_files(i).name := replace(
+ v_apex_files(i).name,
+ 'f' || p_app_id || '/application/',
+ p_base_path_frontend || '/');
+ -- correct prompts for relocation
+ v_export_files(i).contents := replace(
+ v_apex_files(i).contents,
+ 'prompt --application/',
+ 'prompt --' || p_base_path_frontend || '/');
+ -- special handling for install file
+ IF v_export_files(i).name = 'f' || p_app_id || '/install.sql' THEN
+ v_export_files(i).name := 'scripts/install_frontend_generated_by_apex.sql';
+ v_export_files(i).contents := '-- DO NOT TOUCH THIS FILE - IT WILL BE OVERWRITTEN ON NEXT PLEX BACKAPP CALL'
+ || c_lf || c_lf
+ || replace(replace(v_export_files(i).contents,
+ '@application/', '@../' || p_base_path_frontend || '/'),
+ 'prompt --install', 'prompt --install_frontend_generated_by_apex');
+ END IF;
+ -- handle build status RUN_ONLY
+ IF v_export_files(i).name = p_base_path_frontend || '/create_application.sql' AND p_app_build_status_run_only THEN
+ v_export_files(i).contents := util_set_build_status_run_only(v_export_files(i).contents);
+ END IF;
+ v_apex_files.DELETE(i);
+ END LOOP;
+ util_log_stop;
+ --
+ IF p_app_include_single_file THEN
+ -- save as single file
+ v_apex_files.DELETE;
+ util_log_start(p_base_path_frontend || '/APEX_EXPORT:single_file');
+ v_apex_files := apex_export.get_application(
+ p_application_id => p_app_id,
+ p_split => false,
+ p_with_date => p_app_date,
+ p_with_ir_public_reports => p_app_public_reports,
+ p_with_ir_private_reports => p_app_private_reports,
+ p_with_ir_notifications => p_app_notifications,
+ p_with_translations => p_app_translations,
+ p_with_pkg_app_mapping => p_app_pkg_app_mapping,
+ p_with_original_ids => p_app_original_ids,
+ p_with_no_subscriptions => CASE WHEN p_app_subscriptions THEN false ELSE true END,
+ p_with_comments => p_app_comments,
+ p_with_supporting_objects => p_app_supporting_objects);
+ IF p_app_build_status_run_only THEN
+ v_apex_files(1).contents := util_set_build_status_run_only(v_apex_files(1).contents);
+ END IF;
+ util_clob_append(v_apex_files(1).contents);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => p_base_path_frontend || '/' || v_apex_files(1).name);
+ v_apex_files.DELETE;
+ util_log_stop;
+ END IF;
+ END process_apex_app;
+ $end
+
+ PROCEDURE replace_query_like_expressions (
+ p_like_list VARCHAR2,
+ p_not_like_list VARCHAR2,
+ p_placeholder_prefix VARCHAR2,
+ p_column_name VARCHAR2)
+ IS
+ v_expression_table tab_vc32k;
+ BEGIN
+ -- process filter "like"
+ v_expression_table := util_split(p_like_list, ',');
+ FOR i IN 1..v_expression_table.count LOOP
+ v_expression_table(i) := p_column_name
+ || ' like '''
+ || trim(v_expression_table(i))
+ || ''' escape ''\''';
+ END LOOP;
+ v_query := replace(
+ v_query,
+ '#' || p_placeholder_prefix || '_LIKE_EXPRESSIONS#',
+ nvl(util_join(v_expression_table, ' or '), '1 = 1'));
+ -- process filter "not like"
+ v_expression_table := util_split(p_not_like_list, ',');
+ FOR i IN 1..v_expression_table.count LOOP
+ v_expression_table(i) := p_column_name
+ || ' not like '''
+ || trim(v_expression_table (i))
+ || ''' escape ''\''';
+ END LOOP;
+ v_query := replace(
+ v_query,
+ '#' || p_placeholder_prefix || '_NOT_LIKE_EXPRESSIONS#',
+ nvl(util_join(v_expression_table, ' and '), '1 = 1'));
+ $if $$debug_on $then
+ dbms_output.put_line(v_query);
+ $end
+ END replace_query_like_expressions;
+
+ PROCEDURE process_user_ddl IS
+
+ PROCEDURE process_user IS
+ BEGIN
+ v_file_path := p_base_path_backend || '/_user/' || v_current_user || '.sql';
+ util_log_start(v_file_path);
+ util_setup_dbms_metadata(p_sqlterminator => false);
+ util_clob_append(util_multi_replace(q'^BEGIN
+ FOR i IN (SELECT '{{CURRENT_USER}}' AS username FROM dual
+ MINUS
+ SELECT username FROM dba_users) LOOP
+ EXECUTE IMMEDIATE q'[
+--------------------------------------------------------------------------------
+{{DDL}}
+--------------------------------------------------------------------------------
+ ]'
+ END LOOP;
+END;
+{{/}}
+^' ,
+ '{{CURRENT_USER}}', v_current_user,
+ '{{DDL}}', ltrim(dbms_metadata.get_ddl('USER', v_current_user), c_space_crlf),
+ '{{/}}', c_slash));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_setup_dbms_metadata;
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_setup_dbms_metadata;
+ util_log_error(v_file_path);
+ END process_user;
+
+ PROCEDURE process_roles IS
+ BEGIN
+ v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_roles.sql';
+ util_log_start(v_file_path);
+ FOR i IN (SELECT DISTINCT username FROM user_role_privs) LOOP
+ util_clob_append(regexp_replace(
+ --source string
+ dbms_metadata.get_granted_ddl('ROLE_GRANT', v_current_user),
+ --replace all leading whitespace
+ '^\s*', NULL, 1, 0, 'm'));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END process_roles;
+
+ PROCEDURE process_system_privileges IS
+ BEGIN
+ v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_system_privileges.sql';
+ util_log_start(v_file_path);
+ FOR i IN (SELECT DISTINCT username FROM user_sys_privs) LOOP
+ util_clob_append(regexp_replace(
+ --source string
+ dbms_metadata.get_granted_ddl('SYSTEM_GRANT', v_current_user),
+ --replace all leading whitespace
+ '^\s*', NULL, 1, 0, 'm'));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END process_system_privileges;
+
+ PROCEDURE process_object_privileges IS
+ BEGIN
+ v_file_path := p_base_path_backend || '/_user/' || v_current_user || '_object_privileges.sql';
+ util_log_start(v_file_path);
+ FOR i IN (SELECT DISTINCT grantee FROM user_tab_privs WHERE grantee = v_current_user) LOOP
+ util_clob_append(regexp_replace(
+ --source string
+ dbms_metadata.get_granted_ddl('OBJECT_GRANT', v_current_user),
+ --replace all leading whitespace
+ '^\s*', NULL, 1, 0, 'm'));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END process_object_privileges;
+
+ BEGIN
+ process_user;
+ process_roles;
+ process_system_privileges;
+ process_object_privileges;
+ END process_user_ddl;
+
+ PROCEDURE process_object_ddl IS
+ TYPE obj_rec_typ IS RECORD (
+ object_type VARCHAR2(128),
+ object_name VARCHAR2(256),
+ file_path VARCHAR2(512));
+ v_rec obj_rec_typ;
+ no_comments_found EXCEPTION;
+ PRAGMA EXCEPTION_INIT(no_comments_found, -31608);
+ BEGIN
+ util_log_start(p_base_path_backend || '/open_objects_cursor');
+ v_query := q'^
+--https://stackoverflow.com/questions/10886450/how-to-generate-entire-ddl-of-an-oracle-schema-scriptable
+--https://stackoverflow.com/questions/3235300/oracles-dbms-metadata-get-ddl-for-object-type-job
+WITH t AS (
+ SELECT CASE object_type
+ --http://psoug.org/reference/dbms_metadata.html
+ WHEN 'UNIFIED AUDIT POLICY' THEN 'AUDIT_OBJ'
+ WHEN 'CONSUMER GROUP' THEN 'RMGR_CONSUMER_GROUP'
+ WHEN 'DATABASE LINK' THEN 'DB_LINK'
+ WHEN 'EVALUATION CONTEXT' THEN 'PROCOBJ'
+ WHEN 'JAVA CLASS' THEN 'JAVA_CLASS'
+ WHEN 'JAVA RESOURCE' THEN 'JAVA_RESOURCE'
+ WHEN 'JAVA SOURCE' THEN 'JAVA_SOURCE'
+ WHEN 'JAVA TYPE' THEN 'JAVA_TYPE'
+ WHEN 'JOB' THEN 'PROCOBJ'
+ WHEN 'JOB CLASS' THEN 'PROCOBJ'
+ WHEN 'MATERIALIZED VIEW' THEN 'MATERIALIZED_VIEW'
+ WHEN 'PACKAGE BODY' THEN 'PACKAGE_BODY'
+ WHEN 'PACKAGE' THEN 'PACKAGE_SPEC'
+ WHEN 'PROGRAM' THEN 'PROCOBJ'
+ WHEN 'QUEUE' THEN 'AQ_QUEUE'
+ WHEN 'RESOURCE PLAN' THEN 'RMGR_PLAN'
+ WHEN 'RULE SET' THEN 'PROCOBJ'
+ WHEN 'RULE' THEN 'PROCOBJ'
+ WHEN 'SCHEDULE' THEN 'PROCOBJ'
+ WHEN 'SCHEDULER GROUP' THEN 'PROCOBJ'
+ WHEN 'TYPE BODY' THEN 'TYPE_BODY'
+ WHEN 'TYPE' THEN 'TYPE_SPEC'
+ ELSE object_type
+ END AS object_type,
+ CASE
+ WHEN object_type like 'JAVA%' AND substr(object_name, 1, 1) = '/' THEN
+ dbms_java.longname(object_name)
+ ELSE
+ object_name
+ END AS object_name
+ FROM ^'
+$if NOT $$debug_on
+$then || 'user_objects'
+$else || '(SELECT MIN(object_name) AS object_name, object_type FROM user_objects GROUP BY object_type)'
+$end || q'^
+ WHERE 1 = 1
+ --- Ignore invalid object types:
+ AND object_type NOT IN ('UNDEFINED','DESTINATION','EDITION','JAVA DATA','WINDOW')
+ --- These objects are included within other object types:
+ AND object_type NOT IN ('INDEX PARTITION','INDEX SUBPARTITION','LOB','LOB PARTITION','TABLE PARTITION','TABLE SUBPARTITION')
+ --- Ignore system-generated types for collection processing:
+ AND NOT (object_type = 'TYPE' AND object_name LIKE 'SYS_PLSQL_%')
+ --- Ignore system-generated sequences for identity columns:
+ AND NOT (object_type = 'SEQUENCE' AND object_name LIKE 'ISEQ$$_%')
+ --- Ignore LOB indices, their DDL is part of the table:
+ AND object_name NOT IN (SELECT index_name FROM user_lobs)
+ --- Ignore nested tables, their DDL is part of their parent table:
+ AND object_name NOT IN (SELECT table_name FROM user_nested_tables)
+ --- Ignore materialized view tables, their DDL is part of the materialized view
+ AND (object_type != 'TABLE' or object_type = 'TABLE' and not exists (select 1 from user_mviews where mview_name = object_name ))
+ --- Ignore indices for materialized view tables
+ AND NOT (object_type = 'INDEX' AND object_name LIKE 'SYS_C_SNAP$_%')
+ --- Set user specific like filters:
+ AND (#TYPE_LIKE_EXPRESSIONS#)
+ AND (#TYPE_NOT_LIKE_EXPRESSIONS#)
+ AND (#NAME_LIKE_EXPRESSIONS#)
+ AND (#NAME_NOT_LIKE_EXPRESSIONS#)
+ ORDER BY
+ object_type,
+ object_name
+)
+SELECT object_type,
+ object_name,
+ '{{BASE_PATH_APP_BACKEND}}/'
+ || replace(lower(
+ CASE
+ WHEN object_type LIKE '%S' THEN object_type || 'ES'
+ WHEN object_type LIKE '%EX' THEN regexp_replace(object_type, 'EX$', 'ICES', 1, 0, 'i')
+ WHEN object_type LIKE '%Y' THEN regexp_replace(object_type, 'Y$', 'IES', 1, 0, 'i')
+ ELSE object_type || 'S'
+ END), ' ', '_')
+ || '/' || object_name
+ || CASE object_type
+ WHEN 'FUNCTION' THEN '.fnc'
+ WHEN 'PACKAGE BODY' THEN '.pkb'
+ WHEN 'PACKAGE' THEN '.pks'
+ WHEN 'PROCEDURE' THEN '.prc'
+ WHEN 'TRIGGER' THEN '.trg'
+ WHEN 'TYPE BODY' THEN '.tpb'
+ WHEN 'TYPE' THEN '.tps'
+ ELSE '.sql'
+ END AS file_path
+ FROM t
+^' ;
+ v_query := replace(
+ v_query,
+ '{{BASE_PATH_APP_BACKEND}}',
+ p_base_path_backend);
+ replace_query_like_expressions(
+ p_like_list => p_object_type_like,
+ p_not_like_list => p_object_type_not_like,
+ p_placeholder_prefix => 'TYPE',
+ p_column_name => 'object_type');
+ replace_query_like_expressions(
+ p_like_list => p_object_name_like,
+ p_not_like_list => p_object_name_not_like,
+ p_placeholder_prefix => 'NAME',
+ p_column_name => 'object_name');
+ util_setup_dbms_metadata;
+ OPEN v_cur FOR v_query;
+ util_log_stop;
+ LOOP
+ FETCH v_cur INTO v_rec;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ util_log_start(v_rec.file_path);
+ CASE v_rec.object_type
+ WHEN 'SEQUENCE' THEN
+ v_ddl_files.sequences_(v_ddl_files.sequences_.count + 1) := v_rec.file_path;
+ WHEN 'TABLE' THEN
+ v_ddl_files.tables_(v_ddl_files.tables_.count + 1) := v_rec.file_path;
+ WHEN 'INDEX' THEN
+ v_ddl_files.indices_(v_ddl_files.indices_.count + 1) := v_rec.file_path;
+ WHEN 'VIEW' THEN
+ v_ddl_files.views_(v_ddl_files.views_.count + 1) := v_rec.file_path;
+ WHEN 'MATERIALIZED_VIEW' THEN
+ v_ddl_files.mviews_(v_ddl_files.mviews_.count + 1) := v_rec.file_path;
+ WHEN 'TYPE_SPEC' THEN
+ v_ddl_files.types_(v_ddl_files.types_.count + 1) := v_rec.file_path;
+ WHEN 'TYPE_BODY' THEN
+ v_ddl_files.type_bodies_(v_ddl_files.type_bodies_.count + 1) := v_rec.file_path;
+ WHEN 'TRIGGER' THEN
+ v_ddl_files.triggers_(v_ddl_files.triggers_.count + 1) := v_rec.file_path;
+ WHEN 'FUNCTION' THEN
+ v_ddl_files.functions_(v_ddl_files.functions_.count + 1) := v_rec.file_path;
+ WHEN 'PROCEDURE' THEN
+ v_ddl_files.procedures_(v_ddl_files.procedures_.count + 1) := v_rec.file_path;
+ WHEN 'PACKAGE_SPEC' THEN
+ v_ddl_files.packages_(v_ddl_files.packages_.count + 1) := v_rec.file_path;
+ WHEN 'PACKAGE_BODY' THEN
+ v_ddl_files.package_bodies_(v_ddl_files.package_bodies_.count + 1) := v_rec.file_path;
+ ELSE
+ v_ddl_files.other_objects_(v_ddl_files.other_objects_.count + 1) := v_rec.file_path;
+ END CASE;
+ CASE
+ WHEN v_rec.object_type = 'VIEW' AND p_object_view_remove_col_list THEN
+ util_clob_append(regexp_replace(
+ -- source string
+ ltrim(dbms_metadata.get_ddl(v_rec.object_type, v_rec.object_name), c_space_crlf),
+ -- regex replace: remove additional column list from the compiler
+ '\(.*\) ', NULL, 1, 1));
+ WHEN v_rec.object_type IN ('TABLE', 'MATERIALIZED_VIEW', 'INDEX', 'SEQUENCE') THEN
+ util_setup_dbms_metadata(p_sqlterminator => false);
+ util_clob_append(replace(q'^BEGIN
+ FOR i IN (SELECT '{{OBJECT_NAME}}' AS object_name FROM dual
+ MINUS
+ SELECT object_name FROM user_objects) LOOP
+ EXECUTE IMMEDIATE q'[
+--------------------------------------------------------------------------------
+^' ,
+ '{{OBJECT_NAME}}',
+ v_rec.object_name)
+ || ltrim(dbms_metadata.get_ddl(v_rec.object_type, v_rec.object_name), c_space_crlf)
+ || replace(q'^
+--------------------------------------------------------------------------------
+ ]';
+ END LOOP;
+END;
+{{/}}
+^' ,
+ '{{/}}',
+ c_slash));
+ util_setup_dbms_metadata;
+ ELSE
+ util_clob_append(ltrim(dbms_metadata.get_ddl(v_rec.object_type, v_rec.object_name), c_space_crlf));
+ END CASE;
+ IF v_rec.object_type IN ('TABLE', 'VIEW', 'MATERIALIZED_VIEW') THEN
+ BEGIN
+ util_clob_append(c_lf || c_lf || regexp_replace(
+ --source string
+ dbms_metadata.get_dependent_ddl('COMMENT', v_rec.object_name),
+ --replace all leading whitespace
+ '^\s*', NULL, 1, 0, 'm'));
+ EXCEPTION
+ WHEN no_comments_found THEN NULL;
+ END;
+ END IF;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_rec.file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_setup_dbms_metadata;
+ util_log_error(v_rec.file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END process_object_ddl;
+
+ PROCEDURE process_object_grants IS
+ TYPE obj_rec_typ IS RECORD (
+ grantor VARCHAR2(128),
+ privilege VARCHAR2(128),
+ object_name VARCHAR2(256),
+ file_path VARCHAR2(512));
+ v_rec obj_rec_typ;
+ BEGIN
+ util_log_start(p_base_path_backend || '/grants:open_cursor');
+ v_query := q'^
+SELECT DISTINCT
+ p.grantor,
+ p.privilege,
+ p.table_name as object_name,
+ '{{BASE_PATH_APP_BACKEND}}/grants/' || p.privilege || '_on_' || p.table_name || '.sql' AS file_path
+FROM user_tab_privs p
+JOIN user_objects o ON p.table_name = o.object_name
+WHERE (#NAME_LIKE_EXPRESSIONS#)
+ AND (#NAME_NOT_LIKE_EXPRESSIONS#)
+ORDER BY
+ privilege,
+ object_name
+^' ;
+ v_query := replace(
+ v_query,
+ '{{BASE_PATH_APP_BACKEND}}',
+ p_base_path_backend);
+ replace_query_like_expressions(
+ p_like_list => p_object_name_like,
+ p_not_like_list => p_object_name_not_like,
+ p_placeholder_prefix => 'NAME',
+ p_column_name => 'o.object_name');
+ OPEN v_cur FOR v_query;
+ util_log_stop;
+ LOOP
+ FETCH v_cur INTO v_rec;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ util_log_start(v_rec.file_path);
+ util_clob_append(ltrim(dbms_metadata.get_dependent_ddl('OBJECT_GRANT', v_rec.object_name, v_rec.grantor), c_space_crlf));
+ v_ddl_files.grants_(v_ddl_files.grants_.count + 1) := v_rec.file_path;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_rec.file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_rec.file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END process_object_grants;
+
+ PROCEDURE process_ref_constraints IS
+ TYPE obj_rec_typ IS RECORD (
+ table_name VARCHAR2(256),
+ constraint_name VARCHAR2(256),
+ file_path VARCHAR2(512));
+ v_rec obj_rec_typ;
+ BEGIN
+ util_log_start(p_base_path_backend || '/ref_constraints:open_cursor');
+ v_query := q'^
+SELECT table_name,
+ constraint_name,
+ '{{BASE_PATH_APP_BACKEND}}/ref_constraints/' || constraint_name || '.sql' AS file_path
+FROM user_constraints
+WHERE constraint_type = 'R'
+ AND (#NAME_LIKE_EXPRESSIONS#)
+ AND (#NAME_NOT_LIKE_EXPRESSIONS#)
+ORDER BY
+ table_name,
+ constraint_name
+^' ;
+ v_query := replace(
+ v_query,
+ '{{BASE_PATH_APP_BACKEND}}',
+ p_base_path_backend);
+ replace_query_like_expressions(
+ p_like_list => p_object_name_like,
+ p_not_like_list => p_object_name_not_like,
+ p_placeholder_prefix => 'NAME',
+ p_column_name => 'table_name');
+ OPEN v_cur FOR v_query;
+ util_log_stop;
+ LOOP
+ FETCH v_cur INTO v_rec;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ util_log_start(v_rec.file_path);
+ util_setup_dbms_metadata(p_sqlterminator => false);
+ util_clob_append(replace(q'^
+BEGIN
+FOR i IN (SELECT '{{CONSTRAINT_NAME}}' AS constraint_name FROM dual
+ MINUS
+ SELECT constraint_name FROM user_constraints) LOOP
+ EXECUTE IMMEDIATE q'[
+--------------------------------------------------------------------------------
+^' ,
+ '{{CONSTRAINT_NAME}}',
+ v_rec.constraint_name)
+ || ltrim(dbms_metadata.get_ddl('REF_CONSTRAINT', v_rec.constraint_name), c_space_crlf)
+ || replace(q'^
+--------------------------------------------------------------------------------
+ ]';
+END LOOP;
+END;
+{{/}}
+^' ,
+ '{{/}}',
+ c_slash));
+ util_setup_dbms_metadata;
+ v_ddl_files.ref_constraints_(v_ddl_files.ref_constraints_.count + 1) := v_rec.file_path;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_rec.file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_setup_dbms_metadata;
+ util_log_error(v_rec.file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END process_ref_constraints;
+
+ PROCEDURE create_backend_install_file IS
+ BEGIN
+ v_file_path := 'scripts/install_backend_generated_by_plex.sql';
+ util_log_start(v_file_path);
+ util_clob_append('/* A T T E N T I O N
+DO NOT TOUCH THIS FILE or set the PLEX.BackApp parameter p_include_object_ddl
+to false - otherwise your changes would be overwritten on next PLEX.BackApp
+call. It is recommended to export your object DDL only ones on initial
+repository creation and then start to use the "files first approach".
+*/
+
+set define off verify off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+
+prompt --install_backend_generated_by_plex
+
+' );
+ FOR i IN 1..v_ddl_files.sequences_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.sequences_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.tables_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.tables_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.ref_constraints_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.ref_constraints_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.types_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.types_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.packages_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.packages_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.views_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.views_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.mviews_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.mviews_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.indices_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.indices_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.type_bodies_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.type_bodies_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.functions_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.functions_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.procedures_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.procedures_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.package_bodies_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.package_bodies_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.triggers_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.triggers_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.grants_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.grants_(i)));
+ END LOOP;
+ FOR i IN 1..v_ddl_files.other_objects_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.other_objects_(i)));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END create_backend_install_file;
+
+ $if $$ords_installed $then
+ PROCEDURE process_ords_modules IS
+ v_module_name user_ords_modules.name%type;
+ --
+ PROCEDURE export_ords_modules IS
+ BEGIN
+ util_log_start(p_base_path_web_services || '/open_modules_cursor');
+ OPEN v_cur FOR 'select name from user_ords_modules';
+ util_log_stop;
+ --
+ LOOP
+ FETCH v_cur INTO v_module_name;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ v_file_path := p_base_path_web_services || '/' || v_module_name || '.sql';
+ util_log_start(v_file_path);
+ util_clob_append(ords_export.export_module(p_module_name => v_module_name) || chr(10) || '/');
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ v_ddl_files.ords_modules_(v_ddl_files.ords_modules_.count + 1) := v_file_path;
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END export_ords_modules;
+ --
+ PROCEDURE create_ords_install_file IS
+ BEGIN
+ v_file_path := 'scripts/install_web_services_generated_by_ords.sql';
+ util_log_start(v_file_path);
+ util_clob_append('/* A T T E N T I O N
+DO NOT TOUCH THIS FILE or set the PLEX.BackApp parameter p_include_ords_modules
+to false - otherwise your changes would be overwritten on next PLEX.BackApp
+call.
+*/
+
+set define off verify off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+
+prompt --install_web_services_generated_by_ords
+
+' );
+ FOR i IN 1..v_ddl_files.ords_modules_.count LOOP
+ util_clob_append(util_get_script_line(v_ddl_files.ords_modules_(i)));
+ END LOOP;
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END create_ords_install_file;
+
+ BEGIN
+ export_ords_modules;
+ IF v_ddl_files.ords_modules_.count > 0 THEN
+ create_ords_install_file;
+ END IF;
+ END process_ords_modules;
+ $end
+
+ PROCEDURE process_data IS
+ TYPE obj_rec_typ IS RECORD (
+ table_name VARCHAR2(256),
+ pk_columns VARCHAR2(4000));
+ v_rec obj_rec_typ;
+ BEGIN
+ util_log_start(p_base_path_data || '/open_tables_cursor');
+ v_query := q'^
+SELECT table_name,
+ (SELECT LISTAGG(column_name, ', ') WITHIN GROUP(ORDER BY position)
+ FROM user_cons_columns
+ WHERE constraint_name = (SELECT constraint_name
+ FROM user_constraints c
+ WHERE constraint_type = 'P'
+ AND c.table_name = t.table_name)
+ ) AS pk_columns
+ FROM user_tables t
+ WHERE table_name IN (SELECT table_name FROM user_tables
+ MINUS
+ SELECT table_name FROM user_external_tables)
+ AND (#NAME_LIKE_EXPRESSIONS#)
+ AND (#NAME_NOT_LIKE_EXPRESSIONS#)
+ ORDER BY
+ table_name
+^' ;
+ replace_query_like_expressions(
+ p_like_list => p_data_table_name_like,
+ p_not_like_list => p_data_table_name_not_like,
+ p_placeholder_prefix => 'NAME',
+ p_column_name => 'table_name');
+ OPEN v_cur FOR v_query;
+ util_log_stop;
+ --
+ util_log_start(p_base_path_data || '/get_scn');
+ v_data_timestamp := util_calc_data_timestamp(nvl(p_data_as_of_minutes_ago, 0));
+ v_data_scn := timestamp_to_scn(v_data_timestamp);
+ util_log_stop;
+ LOOP
+ FETCH v_cur INTO v_rec;
+ EXIT WHEN v_cur%notfound;
+ BEGIN
+ v_file_path := p_base_path_data || '/' || v_rec.table_name || '.csv';
+ util_log_start(v_file_path);
+ util_clob_query_to_csv(
+ p_query => 'SELECT * FROM ' || v_rec.table_name || ' AS OF SCN ' || v_data_scn ||
+ CASE
+ WHEN v_rec.pk_columns IS NOT NULL
+ THEN ' ORDER BY ' || v_rec.pk_columns
+ ELSE NULL
+ END,
+ p_max_rows => p_data_max_rows);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(v_file_path);
+ END;
+ END LOOP;
+ CLOSE v_cur;
+ END process_data;
+
+ PROCEDURE create_template_files IS
+ v_file_template VARCHAR2(32767 CHAR);
+ PROCEDURE readme_file IS
+ BEGIN
+ v_file_template := q'^Your Global README File
+=======================
+
+It is a good practice to have a README file in the root of your project with
+a high level overview of your application. Put the more detailed docs in the
+docs folder.
+
+You can start with a copy of this file. Rename it to README.md and try to use
+Markdown when writing your content - this has many benefits and you don't waist
+time by formatting your docs. If you are unsure have a look at some projects at
+[Github](https://github.com) or any other code hosting platform.
+
+Depending on your options when calling `plex.backapp` these files are generated
+for you:
+
+- scripts/install_backend_generated_by_plex.sql
+- scripts/install_frontend_generated_by_apex.sql
+- scripts/install_web_services_generated_by_ords.sql
+
+Do not touch these generated install files. They will be overwritten on each
+plex call. Depending on your call parameters it would be ok to modify the file
+install_backend_generated_by_plex - especially when you follow the files first
+approach and export your schema DDL only ones to have a starting point for you
+repository.
+
+If you need to do modifications for the install process then have a look at the
+following templates - they call the generated files and you can do your own
+stuff before or after the calls.
+
+- scripts/templates/1_export_app_from_DEV.bat
+- scripts/templates/2_install_app_into_INT.bat
+- scripts/templates/3_install_app_into_PROD.bat
+- scripts/templates/export_app_custom_code.sql
+- scripts/templates/install_app_custom_code.sql
+
+If you want to use these files please make a copy into the scripts directory
+and modify it to your needs. Doing it this way your changes are overwrite save.
+
+[Feedback is welcome]({{PLEX_URL}}/issues/new)
+^' ;
+ v_file_path := 'plex_README.md';
+ util_log_start(v_file_path);
+ util_clob_append(replace(
+ v_file_template,
+ '{{PLEX_URL}}',
+ c_plex_url));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END readme_file;
+
+ PROCEDURE export_batch_file IS
+ BEGIN
+ v_file_template := q'^rem Template generated by PLEX version {{PLEX_VERSION}}
+rem More infos here: {{PLEX_URL}}
+
+{{@}}echo off
+setlocal
+set "areyousure=N"
+
+rem ### BEGIN CONFIG ###########################################################
+rem Align substrings to your operating system locale: (how it works: https://stackoverflow.com/a/23558738)
+set "datetime=%DATE:~6,4%%DATE:~3,2%%DATE:~0,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
+set "datetime=%datetime: =0%"
+set "systemrole={{SYSTEMROLE}}"
+set "connection=localhost:1521/xepdb1"
+set "app_id={{APP_ID}}"
+set "app_schema={{APP_OWNER}}"
+set "scriptfile=export_app_custom_code.sql"
+set "logfile=logs/%datetime%_export_app_%app_id%_from_%app_schema%_at_%systemrole%.log"
+set "zipfile=BackApp_%app_id%_from_%app_schema%_at_%systemrole%_%datetime%.zip"
+rem ### END CONFIG #############################################################
+
+:PROMPT
+echo(
+echo(
+set /p "areyousure=Run %scriptfile% on %app_schema%@%systemrole%(%connection%) [Y/N]? " || set "areyousure=N"
+if /i %areyousure% neq y goto END
+set NLS_LANG=AMERICAN_AMERICA.UTF8
+set /p "password=Please enter password for %app_schema% [default = oracle]: " || set "password=oracle"
+echo This is the runlog for %scriptfile% on %app_schema%@%systemrole%(%connection%) > %logfile%
+echo exit | sqlplus -S %app_schema%/%password%@%connection% ^
+{{@}}%scriptfile% ^
+%logfile% ^
+%zipfile% ^
+%app_id%
+
+if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-( >> %logfile%
+if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-(
+if %errorlevel% neq 0 goto END
+
+echo Start Post Processing on Client >> %logfile%
+echo Start Post Processing on Client
+echo ========================================================================= >> %logfile%
+echo =========================================================================
+
+echo Decode file %zipfile%.base64 >> %logfile%
+echo Decode file %zipfile%.base64
+certutil -decode %zipfile%.base64 %zipfile% >> %logfile%
+if %errorlevel% neq 0 echo ERROR: Unable to decode %zipfile%.base64 :-( >> %logfile%
+if %errorlevel% neq 0 echo ERROR: Unable to decode %zipfile%.base64 :-(
+if %errorlevel% neq 0 goto END
+del %zipfile%.base64
+
+echo Unzip file %zipfile% >> %logfile%
+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%
+if %errorlevel% neq 0 echo ERROR: Unable to unzip %zipfile% :-(
+if %errorlevel% neq 0 goto END
+
+echo Delete file %zipfile% >> %logfile%
+echo Delete file %zipfile%
+del %zipfile%
+
+echo ========================================================================= >> %logfile%
+echo =========================================================================
+echo Post Processing DONE >> %logfile%
+echo Post Processing DONE
+echo( >> %logfile%
+echo(
+
+:END
+rem Remove "pause" for fully automated setup:
+pause
+if %errorlevel% neq 0 exit /b %errorlevel%
+^' ;
+ v_file_path := 'scripts/templates/1_export_app_from_DEV.bat';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{SYSTEMROLE}}', 'DEV',
+ $if $$apex_installed $then
+ '{{APP_OWNER}}', v_app_owner,
+ '{{APP_ID}}', p_app_id,
+ $end
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END export_batch_file;
+
+ PROCEDURE export_sq_file IS
+ BEGIN
+ v_file_template := q'^/*******************************************************************************
+Template generated by PLEX version {{PLEX_VERSION}}
+More infos here: {{PLEX_URL}}
+
+You need to provide three parameters:
+- logfile: path to the logfile for the script console output
+- zipfile: path to the export zip file - this will be created with the spool command
+- app_id: the APEX app ID you want to export - only relevant when you have APEX installed
+
+Example call for Windows:
+
+ echo exit | sqlplus -S app_schema/password@connection ^
+ {{@}}export_app_custom_code.sql ^
+ my_logfile.log ^
+ my_zipfile.zip ^
+ 100
+
+*******************************************************************************/
+
+set timing on
+timing start EXPORT_APP
+set timing off verify off feedback off heading off
+set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
+whenever sqlerror exit sql.sqlcode rollback
+-- whenever oserror exit failure rollback
+define logfile = "&1."
+define zipfile = "&2..base64"
+spool "&logfile" append
+variable contents clob
+variable app_id number
+BEGIN
+ :app_id := &3;
+END;
+{{/}}
+
+prompt
+prompt Start Export from Database
+prompt =========================================================================
+
+prompt Do the app export and save to zip file
+prompt ATTENTION: Depending on your options this could take some time ...
+BEGIN
+ :contents := plex.to_base64(plex.to_zip(plex.backapp(
+ -- These are the defaults - align it to your needs:^';
+ $if $$apex_installed $then
+ v_file_template := v_file_template || q'^
+ p_app_id => :app_id,
+ p_app_date => true,
+ p_app_public_reports => true,
+ p_app_private_reports => false,
+ p_app_notifications => false,
+ p_app_translations => true,
+ p_app_pkg_app_mapping => false,
+ p_app_original_ids => false,
+ p_app_subscriptions => true,
+ p_app_comments => true,
+ p_app_supporting_objects => null,
+ p_app_include_single_file => false,
+ p_app_build_status_run_only => false,^';
+ $end
+ $if $$ords_installed $then
+ v_file_template := v_file_template || q'^
+
+ p_include_ords_modules => false,^';
+ $end
+ v_file_template := v_file_template || q'^
+
+ p_include_object_ddl => true,
+ p_object_type_like => null,
+ p_object_type_not_like => null,
+ p_object_name_like => null,
+ p_object_name_not_like => null,
+
+ p_include_data => false,
+ p_data_as_of_minutes_ago => 0,
+ p_data_max_rows => 1000,
+ p_data_table_name_like => null,
+ p_data_table_name_not_like => null,
+
+ p_include_templates => true,
+ p_include_runtime_log => true,
+ p_include_error_log => true,
+ p_base_path_backend => 'app_backend',
+ p_base_path_frontend => 'app_frontend',
+ p_base_path_data => 'app_data')));
+END;
+{{/}}
+
+prompt Spool the resulting base64 encoded zip file to client disk
+spool off
+set termout off
+spool "&zipfile"
+print contents
+set termout on
+spool "&logfile." append
+timing stop EXPORT_APP
+prompt =========================================================================
+prompt Export DONE :-)
+prompt
+^' ;
+ v_file_path := 'scripts/templates/export_app_custom_code.sql';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{/}}', c_slash,
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END export_sq_file;
+
+ PROCEDURE install_batch_files IS
+ BEGIN
+ v_file_template := q'^rem Template generated by PLEX version {{PLEX_VERSION}}
+rem More infos here: {{PLEX_URL}}
+
+{{@}}echo off
+setlocal
+set "areyousure=N"
+
+rem ### BEGIN CONFIG ###########################################################
+rem Align substrings to your operating system locale: (how it works: https://stackoverflow.com/a/23558738)
+set "datetime=%DATE:~6,4%%DATE:~3,2%%DATE:~0,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
+set "datetime=%datetime: =0%"
+set "systemrole={{SYSTEMROLE}}"
+set "connection=localhost:1521/xepdb1"
+set "app_id={{APP_ID}}"
+set "app_alias={{APP_ALIAS}}"
+set "app_schema={{APP_OWNER}}"
+set "app_workspace={{APP_WORKSPACE}}"
+set "scriptfile=install_app_custom_code.sql"
+set "logfile=logs/%datetime%_install_app_%app_id%_into_%app_schema%_at_%systemrole%.log"
+rem ### END CONFIG #############################################################
+
+:PROMPT
+echo.
+echo.
+set /p "areyousure=Run %scriptfile% on %app_schema%@%systemrole%(%connection%) [Y/N]? " || set "areyousure=N"
+if /i %areyousure% neq y goto END
+set NLS_LANG=AMERICAN_AMERICA.UTF8
+set /p "password=Please enter password for %app_schema% [default = oracle]: " || set "password=oracle"
+echo This is the runlog for %scriptfile% on %app_schema%@%systemrole%(%connection%) > %logfile%
+echo exit | sqlplus -S %app_schema%/%password%@%connection% ^
+{{@}}%scriptfile% ^
+%logfile% ^
+%app_id% ^
+%app_alias% ^
+%app_schema% ^
+%app_workspace%
+
+if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-( >> %logfile%
+if %errorlevel% neq 0 echo ERROR: SQL script finished with return code %errorlevel% :-(
+
+:END
+rem Remove "pause" for fully automated setup:
+pause
+if %errorlevel% neq 0 exit /b %errorlevel%
+^' ;
+ v_file_path := 'scripts/templates/2_install_app_into_INT.bat';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{SYSTEMROLE}}', 'INT',
+ $if $$apex_installed $then
+ '{{APP_ID}}', p_app_id,
+ '{{APP_ALIAS}}', v_app_alias,
+ '{{APP_OWNER}}', v_app_owner,
+ '{{APP_WORKSPACE}}', v_app_workspace,
+ $end
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+
+ v_file_path := 'scripts/templates/3_install_app_into_PROD.bat';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{SYSTEMROLE}}', 'PROD',
+ $if $$apex_installed $then
+ '{{APP_ID}}', p_app_id,
+ '{{APP_ALIAS}}', v_app_alias,
+ '{{APP_OWNER}}', v_app_owner,
+ '{{APP_WORKSPACE}}', v_app_workspace,
+ $end
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END install_batch_files;
+
+ PROCEDURE install_sql_file IS
+ BEGIN
+ v_file_template := q'^/*******************************************************************************
+Template generated by PLEX version {{PLEX_VERSION}}
+More infos here: {{PLEX_URL}}
+
+You need to provide five parameters:
+- logfile: path to the logfile for the script console output
+- app_id: the APEX app ID you want to have for the installation
+- app_alias: the alias you want to have for your app
+- app_schema: the parsing schema for your app
+- app_workspace: the workspace where your app should be installed
+
+Example call for Windows:
+
+ echo exit | sqlplus -S app_schema/password@connection ^
+ {{@}}install_app_custom_code.sql ^
+ my_logfile.log ^
+ 100 ^
+ MY_APP_ALIAS ^
+ MY_APP_SCHEMA ^
+ MY_APP_WORKSPACE
+
+*******************************************************************************/
+
+set timing on define on
+timing start INSTALL_APP
+set timing off verify off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+-- whenever oserror exit failure rollback
+define logfile = "&1"
+spool "&logfile" append
+variable app_id varchar2(100)
+variable app_alias varchar2(100)
+variable app_schema varchar2(100)
+variable app_workspace varchar2(100)
+BEGIN
+ :app_id := &2;
+ :app_alias := '&3';
+ :app_schema := '&4';
+ :app_workspace := '&5';
+END;
+{{/}}
+set define off
+
+
+prompt
+prompt Start Installation
+prompt =========================================================================
+
+prompt Install Backend
+{{@}}install_backend_generated_by_plex.sql
+
+prompt Compile Invalid Objects
+BEGIN
+ dbms_utility.compile_schema(
+ schema => user,
+ compile_all => false,
+ reuse_settings => true);
+END;
+{{/}}
+
+prompt Check Invalid Objects
+DECLARE
+ v_count PLS_INTEGER;
+ v_objects VARCHAR2(4000);
+BEGIN
+ SELECT COUNT(*), chr(10) ||
+ listagg('- ' || object_name || ' (' || object_type || ')', chr(10)) within GROUP(ORDER BY object_name)
+ INTO v_count, v_objects
+ FROM user_objects
+ WHERE status = 'INVALID';
+ IF v_count > 0 THEN
+ raise_application_error(-20000, chr(10) || chr(10) ||
+ 'Found ' || v_count || ' invalid object' || CASE WHEN v_count > 1 THEN 's' END ||
+ ' :-( ' || chr(10) || '=============================' || v_objects || chr(10) || chr(10) );
+ END IF;
+END;
+{{/}}
+
+prompt Install Web Services
+{{@}}install_web_services_generated_by_ords.sql
+
+prompt Install Frontend
+BEGIN
+ apex_application_install.set_workspace_id(APEX_UTIL.find_security_group_id(:app_workspace));
+ apex_application_install.set_application_alias(:app_alias);
+ apex_application_install.set_application_id(:app_id);
+ apex_application_install.set_schema(:app_schema);
+ apex_application_install.generate_offset;
+END;
+{{/}}
+{{@}}install_frontend_generated_by_apex.sql
+
+timing stop INSTALL_APP
+prompt =========================================================================
+prompt Installation DONE :-)
+prompt
+^' ;
+ v_file_path := 'scripts/templates/install_app_custom_code.sql';
+ util_log_start(v_file_path);
+ util_clob_append(util_multi_replace(
+ v_file_template,
+ '{{PLEX_VERSION}}', c_plex_version,
+ '{{PLEX_URL}}', c_plex_url,
+ '{{/}}', c_slash,
+ '{{@}}', c_at));
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END install_sql_file;
+
+ BEGIN
+ readme_file;
+ export_batch_file;
+ export_sq_file;
+ install_batch_files;
+ install_sql_file;
+ END create_template_files;
+
+ PROCEDURE create_directory_keepers IS
+ v_the_point VARCHAR2(30) := '. < this is the point ;-)';
+ BEGIN
+ v_file_path := 'docs/_save_your_docs_here.txt';
+ util_log_start(v_file_path);
+ util_clob_append(v_the_point);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ --
+ v_file_path := 'scripts/logs/_spool_your_script_logs_here.txt';
+ util_log_start(v_file_path);
+ util_clob_append(v_the_point);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ --
+ v_file_path := 'tests/_save_your_tests_here.txt';
+ util_log_start(v_file_path);
+ util_clob_append(v_the_point);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => v_file_path);
+ util_log_stop;
+ END create_directory_keepers;
+
+ PROCEDURE finish IS
+ BEGIN
+ util_ensure_unique_file_names(v_export_files);
+ IF p_include_error_log THEN
+ util_clob_create_error_log(v_export_files);
+ END IF;
+ IF p_include_runtime_log THEN
+ util_clob_create_runtime_log(v_export_files);
+ END IF;
+ END finish;
+
+BEGIN
+ init;
+ $if $$apex_installed $then
+ check_owner;
+ IF p_app_id IS NOT NULL THEN
+ process_apex_app;
+ END IF;
+ $end
+ IF p_include_object_ddl THEN
+ process_user_ddl;
+ process_object_ddl;
+ $if NOT $$debug_on $then
+ -- excluded in debug mode (potential long running object types)
+ process_object_grants;
+ process_ref_constraints;
+ $end
+ create_backend_install_file;
+ END IF;
+ $if $$ords_installed $then
+ IF p_include_ords_modules THEN
+ process_ords_modules;
+ END IF;
+ $end
+ IF p_include_data THEN
+ process_data;
+ END IF;
+ IF p_include_templates THEN
+ create_template_files;
+ create_directory_keepers;
+ END IF;
+ finish;
+ RETURN v_export_files;
+END backapp;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE add_query (
+ p_query VARCHAR2,
+ p_file_name VARCHAR2,
+ p_max_rows NUMBER DEFAULT 1000)
+IS
+ v_index PLS_INTEGER;
+BEGIN
+ v_index := g_queries.count + 1;
+ g_queries(v_index).query := p_query;
+ g_queries(v_index).file_name := p_file_name;
+ g_queries(v_index).max_rows := p_max_rows;
+END add_query;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION queries_to_csv (
+ p_delimiter IN VARCHAR2 DEFAULT ',',
+ p_quote_mark IN VARCHAR2 DEFAULT '"',
+ p_header_prefix IN VARCHAR2 DEFAULT NULL,
+ p_include_runtime_log IN BOOLEAN DEFAULT true,
+ p_include_error_log IN BOOLEAN DEFAULT true)
+RETURN tab_export_files IS
+ v_export_files tab_export_files;
+
+ PROCEDURE init IS
+ BEGIN
+ IF g_queries.count = 0 THEN
+ raise_application_error(
+ -20201,
+ 'You need first to add queries by using plex.add_query. Calling plex.queries_to_csv clears the global queries array for subsequent processing.');
+ END IF;
+ util_log_init(p_module => 'plex.queries_to_csv');
+ util_log_start('init');
+ v_export_files := NEW tab_export_files();
+ util_log_stop;
+ END init;
+
+ PROCEDURE process_queries IS
+ BEGIN
+ FOR i IN g_queries.first..g_queries.last LOOP
+ BEGIN
+ util_log_start('process_query ' || TO_CHAR(i) || ': ' || g_queries(i).file_name);
+ util_clob_query_to_csv(
+ p_query => g_queries(i).query,
+ p_max_rows => g_queries(i).max_rows,
+ p_delimiter => p_delimiter,
+ p_quote_mark => p_quote_mark,
+ p_header_prefix => p_header_prefix);
+ util_clob_add_to_export_files(
+ p_export_files => v_export_files,
+ p_name => g_queries(i).file_name || '.csv');
+ util_log_stop;
+ EXCEPTION
+ WHEN OTHERS THEN
+ util_log_error(g_queries(i).file_name);
+ END;
+ END LOOP;
+ END process_queries;
+
+ PROCEDURE finish IS
+ BEGIN
+ g_queries.DELETE;
+ util_ensure_unique_file_names(v_export_files);
+ IF p_include_error_log THEN
+ util_clob_create_error_log(v_export_files);
+ END IF;
+ IF p_include_runtime_log THEN
+ util_clob_create_runtime_log(v_export_files);
+ END IF;
+ END finish;
+
+BEGIN
+ init;
+ process_queries;
+ finish;
+ RETURN v_export_files;
+EXCEPTION
+ WHEN others THEN
+ g_queries.DELETE;
+END queries_to_csv;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION to_zip (p_file_collection IN tab_export_files) RETURN BLOB IS
+ v_zip BLOB;
+BEGIN
+ dbms_lob.createtemporary(v_zip, true);
+ util_log_start('post processing with to_zip: ' || p_file_collection.count || ' files');
+ FOR i IN 1..p_file_collection.count LOOP
+ util_zip_add_file(
+ p_zipped_blob => v_zip,
+ p_name => p_file_collection(i).name,
+ p_content => util_clob_to_blob(p_file_collection(i).contents));
+ END LOOP;
+ util_zip_finish(v_zip);
+ util_log_stop;
+ util_log_calc_runtimes;
+ RETURN v_zip;
+END to_zip;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+-- copyright by Tim Hall, see https://oracle-base.com/dba/miscellaneous/base64encode.sql
+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
+BEGIN
+ util_log_start('post processing with to_base64');
+ FOR i IN 0 .. TRUNC((DBMS_LOB.getlength(p_blob) - 1 ) / v_step) LOOP
+ v_bas64 := v_bas64 || UTL_RAW.cast_to_varchar2(UTL_ENCODE.base64_encode(DBMS_LOB.substr(p_blob, v_step, i * v_step + 1)));
+ END LOOP;
+ util_log_stop;
+ util_log_calc_runtimes;
+ RETURN v_bas64;
+END;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION view_error_log RETURN tab_error_log PIPELINED IS
+BEGIN
+ FOR i IN 1..g_errlog.count LOOP
+ PIPE ROW (g_errlog(i));
+ END LOOP;
+END view_error_log;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION view_runtime_log RETURN tab_runtime_log PIPELINED IS
+ v_return rec_runtime_log;
+BEGIN
+ v_return.overall_start_time := g_runlog.start_time;
+ v_return.overall_run_time := round(g_runlog.run_time, 3);
+ FOR i IN 1..g_runlog.data.count LOOP
+ v_return.step := i;
+ v_return.elapsed := round(g_runlog.data(i).elapsed, 3);
+ v_return.execution := round(g_runlog.data(i).execution, 6);
+ v_return.module := g_runlog.module;
+ v_return.action := g_runlog.data(i).action;
+ PIPE ROW (v_return);
+ END LOOP;
+END view_runtime_log;
+
+--------------------------------------------------------------------------------------------------------------------------------
+
+BEGIN
+ IF dbms_lob.istemporary(g_clob) = 0 THEN
+ dbms_lob.createtemporary(g_clob, true);
+ END IF;
+END plex;
/
\ No newline at end of file
diff --git a/PLEX.pks b/src/PLEX.pks
old mode 100755
new mode 100644
similarity index 52%
rename from PLEX.pks
rename to src/PLEX.pks
index 6e1bf61..aa56ee4
--- a/PLEX.pks
+++ b/src/PLEX.pks
@@ -1,502 +1,575 @@
-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.0.2';
-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';
-c_plex_author CONSTANT VARCHAR2(20 CHAR) := 'Ottmar Gobrecht';
-/**
-PL/SQL Export Utilities
-=======================
-
-PLEX was created to be able to quickstart version control for existing (APEX) apps and has currently two main functions called **BackApp** and **Queries_to_CSV**. Queries_to_CSV is used by BackApp as a helper function, but its functionality is also useful standalone.
-
-See also this resources for more information:
-
-- [Blog post on how to getting started](https://ogobrecht.github.io/posts/2018-08-26-plex-plsql-export-utilities)
-- [PLEX project page on GitHub](https://github.com/ogobrecht/plex)
-- [Give feedback on GitHub](https://github.com/ogobrecht/plex/issues/new).
-
-
-DEPENDENCIES
-
-The package itself is independend, but functionality varies on the following conditions:
-
-- For APEX app export: APEX >= 5.1.4 installed
-- NOT YET IMPLEMENTED: For ORDS REST service export: ORDS >= FIXME installed
-
-
-INSTALLATION
-
-- Download the [latest version](https://github.com/ogobrecht/plex/releases/latest)
-- Unzip it, open a shell and go into the root directory
-- Start SQL*Plus (or another tool which can run SQL scripts)
-- To install PLEX run the provided install script `plex_install.sql` (script provides compiler flags)
-- To uninstall PLEX run the provided script `plex_uninstall.sql` or drop the package manually
-
-
-CHANGELOG
-
-- 2.0.2 (2019-08-16)
- - Fixed: Function BackApp throws error on large APEX UI install files (ORA-06502: PL/SQL: numeric or value error: character string buffer too small)
-- 2.0.1 (2019-07-09)
- - Fixed: Compile error when DB version is lower then 18.1 (PLS-00306: wrong number or types of arguments in call to 'REC_EXPORT_FILE')
-- 2.0.0 (2019-06-20)
- - Package is now independend from APEX to be able to export schema object DDL and table data without an APEX installation
- - ATTENTION: The return type of functions BackApp and Queries_to_CSV has changed from `apex_t_export_files` to `plex.tab_export_files`
- - New parameters to filter for object types
- - New parameters to change base paths for backend, frontend and data
-- 1.2.1 (2019-03-13)
- - Fix script templates: Change old parameters in plex.backapp call
- - Add install and uninstall scripts for PLEX itself
-- 1.2.0 (2018-10-31)
- - New: All like/not like parameters are now translated internally with the escape character set to backslash like so `... like 'YourExpression' escape '\'`
- - Fixed: Binary data type columns (raw, long_raw, blob, bfile) should no longer break the export data to CSV functionality
-- 1.1.0 (2018-09-23)
- - Change filter parameter from regular expression to list of like expressions for easier handling
-- 1.0.0 (2018-08-26)
- - First public release
-**/
-
-
---------------------------------------------------------------------------------------------------------------------------------
--- CONSTANTS, TYPES
---------------------------------------------------------------------------------------------------------------------------------
-
-c_app_info_length CONSTANT PLS_INTEGER := 64;
-SUBTYPE app_info_text IS VARCHAR2(64 CHAR);
-
-TYPE rec_error_log IS RECORD (
- time_stamp TIMESTAMP,
- file_name VARCHAR2(255),
- error_text VARCHAR2(200),
- call_stack VARCHAR2(500));
-TYPE tab_error_log IS TABLE OF rec_error_log;
-
-TYPE rec_runtime_log IS RECORD (
- overall_start_time TIMESTAMP,
- overall_run_time NUMBER,
- step INTEGER,
- elapsed NUMBER,
- execution NUMBER,
- module app_info_text,
- action app_info_text);
-TYPE tab_runtime_log IS TABLE OF rec_runtime_log;
-
-TYPE rec_export_file IS RECORD (
- name VARCHAR2(255),
- contents CLOB);
-TYPE tab_export_files IS TABLE OF rec_export_file;
-
-TYPE tab_vc32k IS TABLE OF varchar2(32767);
-TYPE tab_vc1k IS TABLE OF VARCHAR2(1024) INDEX BY BINARY_INTEGER;
-
-
---------------------------------------------------------------------------------------------------------------------------------
--- MAIN METHODS
---------------------------------------------------------------------------------------------------------------------------------
-
-FUNCTION backapp (
- $if $$apex_installed $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.
- p_app_public_reports IN BOOLEAN DEFAULT true, -- If true, include public reports that a user saved.
- p_app_private_reports IN BOOLEAN DEFAULT false, -- If true, include private reports that a user saved.
- p_app_notifications IN BOOLEAN DEFAULT false, -- If true, include report notifications.
- p_app_translations IN BOOLEAN DEFAULT true, -- If true, include application translation mappings and all text from the translation repository.
- p_app_pkg_app_mapping IN BOOLEAN DEFAULT false, -- If true, export installed packaged applications with references to the packaged application definition. If FALSE, export them as normal applications.
- p_app_original_ids IN BOOLEAN DEFAULT false, -- If true, export with the IDs as they were when the application was imported.
- p_app_subscriptions IN BOOLEAN DEFAULT true, -- If true, components contain subscription references.
- p_app_comments IN BOOLEAN DEFAULT true, -- If true, include developer comments.
- 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_type_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type like '%BODY' escape '\' or object_type like 'JAVA%' escape '\').
- p_object_type_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type not like '%BODY' escape '\' and object_type not like 'JAVA%' escape '\').
- 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: ... from user_objects where ... and (object_name like 'EMP%' escape '\' or object_name like 'DEPT%' escape '\').
- p_object_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: 'EMP%,DEPT%' will be translated to: ... from user_objects where ... and (object_name not like 'EMP%' escape '\' and object_name not like 'DEPT%' escape '\').
- -- Data related options:
- p_include_data IN BOOLEAN DEFAULT false, -- If true, include CSV data of each table.
- p_data_as_of_minutes_ago IN NUMBER DEFAULT 0, -- Read consistent data with the resulting timestamp(SCN).
- p_data_max_rows IN NUMBER DEFAULT 1000, -- Maximum number of rows per table.
- p_data_table_name_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name like 'EMP%' escape '\' or table_name like 'DEPT%' escape '\').
- p_data_table_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name not like 'EMP%' escape '\' and table_name not like 'DEPT%' escape '\').
- -- Miscellaneous options:
- p_include_templates IN BOOLEAN DEFAULT true, -- If true, include templates for README.md, export and install scripts.
- p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with detailed runtime infos.
- p_include_error_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_error_log.md with detailed error messages.
- p_base_path_backend IN VARCHAR2 DEFAULT 'app_backend', -- The base path in the project root for the database DDL files.
- p_base_path_frontend IN VARCHAR2 DEFAULT 'app_frontend', -- The base path in the project root for the APEX UI install files.
- p_base_path_data IN VARCHAR2 DEFAULT 'app_data') -- The base path in the project root for the data files.
-RETURN tab_export_files;
-/**
-Get a file collection of an APEX application (or the current user/schema only) including:
-
-- The app export SQL files splitted ready to use for version control and deployment
-- Optional the DDL scripts for all objects and grants
-- Optional the data in CSV files (this option was implemented to track catalog tables, can be used as logical backup, has the typical CSV limitations...)
-- Everything in a (hopefully) nice directory structure
-
-EXAMPLE BASIC USAGE
-
-```sql
-DECLARE
- l_file_collection plex.tab_export_files;
-BEGIN
- l_file_collection := plex.backapp(
- p_app_id => 100, -- parameter only available when APEX installed
- p_include_object_ddl => false,
- p_include_data => false);
-
- -- do something with the file collection
- FOR i IN 1..l_file_collection.count LOOP
- dbms_output.put_line(i || ' | '
- || lpad(round(length(l_file_collection(i).contents) / 1024), 3) || ' kB' || ' | '
- || l_file_collection(i).name);
- END LOOP;
-END;
-{{/}}
-```
-
-EXAMPLE ZIP FILE PL/SQL
-
-```sql
-DECLARE
- l_zip_file BLOB;
-BEGIN
- l_zip_file := plex.to_zip(plex.backapp(
- p_app_id => 100, -- parameter only available when APEX installed
- p_include_object_ddl => true,
- p_include_data => false));
- -- do something with the zip file
- -- Your code here...
-END;
-{{/}}
-```
-
-EXAMPLE ZIP FILE SQL
-
-```sql
--- Inline function because of boolean parameters (needs Oracle 12c or higher).
--- Alternative create a helper function and call that in a SQL context.
-WITH
- FUNCTION backapp RETURN BLOB IS
- BEGIN
- RETURN plex.to_zip(plex.backapp(
- -- All parameters are optional and shown with their defaults
- -- App related options (only available, when APEX is installed):
- p_app_id => NULL,
- p_app_date => true,
- p_app_public_reports => true,
- p_app_private_reports => false,
- p_app_notifications => false,
- p_app_translations => true,
- p_app_pkg_app_mapping => false,
- p_app_original_ids => false,
- p_app_subscriptions => true,
- p_app_comments => true,
- p_app_supporting_objects => NULL,
- p_app_include_single_file => false,
- p_app_build_status_run_only => false,
- -- Object related options:
- p_include_object_ddl => false,
- p_object_type_like => NULL,
- p_object_type_not_like => NULL,
- p_object_name_like => NULL,
- p_object_name_not_like => NULL,
- -- Data related options:
- p_include_data => false,
- p_data_as_of_minutes_ago => 0,
- p_data_max_rows => 1000,
- p_data_table_name_like => NULL,
- p_data_table_name_not_like => NULL,
- -- Miscellaneous options:
- p_include_templates => true,
- p_include_runtime_log => true,
- p_include_error_log => true,
- p_base_path_backend => 'app_backend',
- p_base_path_frontend => 'app_frontend',
- p_base_path_data => 'app_data'));
- END backapp;
-SELECT backapp FROM dual;
-```
-**/
-
-
-
-PROCEDURE add_query (
- p_query IN VARCHAR2, -- The query itself
- p_file_name IN VARCHAR2, -- File name like 'Path/to/your/file-without-extension'.
- p_max_rows IN NUMBER DEFAULT 1000); -- The maximum number of rows to be included in your file.
-/**
-Add a query to be processed by the method queries_to_csv. You can add as many queries as you like.
-
-EXAMPLE
-
-```sql
-BEGIN
- plex.add_query(
- p_query => 'select * from user_tables',
- p_file_name => 'user_tables');
-END;
-{{/}}
-```
-**/
-
-
-
-FUNCTION queries_to_csv (
- p_delimiter IN VARCHAR2 DEFAULT ',', -- The column delimiter.
- p_quote_mark IN VARCHAR2 DEFAULT '"', -- Used when the data contains the delimiter character.
- p_header_prefix IN VARCHAR2 DEFAULT NULL, -- Prefix the header line with this text.
- p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with runtime statistics.
- p_include_error_log IN BOOLEAN DEFAULT true) -- If true, generate file plex_error_log.md with detailed error messages.
-RETURN tab_export_files;
-/**
-Export one or more queries as CSV data within a file collection.
-
-EXAMPLE BASIC USAGE
-
-```sql
-DECLARE
- l_file_collection plex.tab_export_files;
-BEGIN
- --fill the queries array
- plex.add_query(
- p_query => 'select * from user_tables',
- p_file_name => 'user_tables');
- plex.add_query(
- p_query => 'select * from user_tab_columns',
- p_file_name => 'user_tab_columns',
- p_max_rows => 10000);
- -- process the queries
- l_file_collection := plex.queries_to_csv;
- -- do something with the file collection
- FOR i IN 1..l_file_collection.count LOOP
- dbms_output.put_line(i || ' | '
- || lpad(round(length(l_file_collection(i).contents) / 1024), 3) || ' kB' || ' | '
- || l_file_collection(i).name);
- END LOOP;
-END;
-{{/}}
-```
-
-EXPORT EXPORT ZIP FILE PL/SQL
-
-```sql
-DECLARE
- l_zip_file BLOB;
-BEGIN
- --fill the queries array
- plex.add_query(
- p_query => 'select * from user_tables',
- p_file_name => 'user_tables');
- plex.add_query(
- p_query => 'select * from user_tab_columns',
- p_file_name => 'user_tab_columns',
- p_max_rows => 10000);
- -- process the queries
- l_zip_file := plex.to_zip(plex.queries_to_csv);
- -- do something with the zip file
- -- Your code here...
-END;
-{{/}}
-```
-
-EXAMPLE EXPORT ZIP FILE SQL
-
-```sql
-WITH
- FUNCTION queries_to_csv_zip RETURN BLOB IS
- v_return BLOB;
- BEGIN
- plex.add_query(
- p_query => 'select * from user_tables',
- p_file_name => 'user_tables');
- plex.add_query(
- p_query => 'select * from user_tab_columns',
- p_file_name => 'user_tab_columns',
- p_max_rows => 10000);
- v_return := plex.to_zip(plex.queries_to_csv);
- RETURN v_return;
- END queries_to_csv_zip;
-SELECT queries_to_csv_zip FROM dual;
-```
-**/
-
-
-
-FUNCTION to_zip (
- p_file_collection IN tab_export_files) -- The file collection to zip.
-RETURN BLOB;
-/**
-Convert a file collection to a zip file.
-
-EXAMPLE
-
-```sql
-DECLARE
- l_zip BLOB;
-BEGIN
- l_zip := plex.to_zip(plex.backapp(
- p_app_id => 100,
- p_include_object_ddl => true));
- -- do something with the zip file...
-END;
-```
-***/
-
-FUNCTION view_error_log RETURN tab_error_log PIPELINED;
-/**
-View the error log from the last plex run. The internal array for the error log is cleared on each call of BackApp or Queries_to_CSV.
-
-EXAMPLE
-
-```sql
-SELECT * FROM TABLE(plex.view_error_log);
-```
-**/
-
-FUNCTION view_runtime_log RETURN tab_runtime_log PIPELINED;
-/**
-View the runtime log from the last plex run. The internal array for the runtime log is cleared on each call of BackApp or Queries_to_CSV.
-
-EXAMPLE
-
-```sql
-SELECT * FROM TABLE(plex.view_runtime_log);
-```
-**/
-
-
---------------------------------------------------------------------------------------------------------------------------------
--- UTILITIES (only available when v_utils_public is set to 'true' in install script plex_install.sql)
---------------------------------------------------------------------------------------------------------------------------------
-
-$if $$utils_public $then
-
-FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2;
-
-FUNCTION util_string_to_bool (
- p_bool_string IN VARCHAR2,
- p_default IN BOOLEAN)
-RETURN BOOLEAN;
-
-FUNCTION util_split (
- p_string IN VARCHAR2,
- p_delimiter IN VARCHAR2 DEFAULT ',')
-RETURN tab_vc32k;
-
-FUNCTION util_join (
- p_array IN tab_vc32k,
- p_delimiter IN VARCHAR2 DEFAULT ',')
-RETURN VARCHAR2;
-
-FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB;
-
-/*
-ZIP UTILS
-- The following four zip utilities are copied from this article:
- - Blog: https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/
- - Source: https://technology.amis.nl/wp-content/uploads/2010/06/as_zip10.txt
-- Copyright (c) 2010, 2011 by Anton Scheffer (MIT license)
-- Thank you for sharing this Anton :-)
-*/
-FUNCTION util_zip_blob_to_num (
- p_blob IN BLOB,
- p_len IN INTEGER,
- p_pos IN INTEGER)
-RETURN NUMBER;
-FUNCTION util_zip_little_endian (
- p_big IN NUMBER,
- p_bytes IN PLS_INTEGER := 4)
-RETURN RAW;
-PROCEDURE util_zip_add_file (
- p_zipped_blob IN OUT BLOB,
- p_name IN VARCHAR2,
- p_content IN BLOB);
-
-PROCEDURE util_zip_finish (p_zipped_blob IN OUT BLOB);
-
-FUNCTION util_multi_replace (
- p_source_string VARCHAR2,
- p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
- p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
- p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
- p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
- p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
- p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
- p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
- p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
- p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
- p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
- p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
- p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
-RETURN VARCHAR2;
-
-FUNCTION util_set_build_status_run_only (p_app_export_sql IN CLOB) RETURN CLOB;
-
-FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP;
-
-PROCEDURE util_setup_dbms_metadata (
- p_pretty IN BOOLEAN DEFAULT true,
- p_constraints IN BOOLEAN DEFAULT true,
- p_ref_constraints IN BOOLEAN DEFAULT false,
- p_partitioning IN BOOLEAN DEFAULT true,
- p_tablespace IN BOOLEAN DEFAULT false,
- p_storage IN BOOLEAN DEFAULT false,
- 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);
-
-PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files);
-
---------------------------------------------------------------------------------------------------------------------------------
--- The following tools are working on global private package variables
---------------------------------------------------------------------------------------------------------------------------------
-
-PROCEDURE util_log_init (p_module IN VARCHAR2);
-
-PROCEDURE util_log_start (p_action IN VARCHAR2);
-
-PROCEDURE util_log_error (p_name VARCHAR2);
-
-PROCEDURE util_log_stop;
-
-FUNCTION util_log_get_runtime (
- p_start IN TIMESTAMP,
- p_stop IN TIMESTAMP)
-RETURN NUMBER;
-
-PROCEDURE util_log_calc_runtimes;
-
-PROCEDURE util_clob_append (p_content IN VARCHAR2);
-
-PROCEDURE util_clob_append (p_content IN CLOB);
-
-PROCEDURE util_clob_flush_cache;
-
-PROCEDURE util_clob_add_to_export_files (
- p_export_files IN OUT NOCOPY tab_export_files,
- p_name IN VARCHAR2);
-
-PROCEDURE util_clob_query_to_csv (
- p_query IN VARCHAR2,
- p_max_rows IN NUMBER DEFAULT 1000,
- p_delimiter IN VARCHAR2 DEFAULT ',',
- p_quote_mark IN VARCHAR2 DEFAULT '"',
- p_header_prefix IN VARCHAR2 DEFAULT NULL);
-
-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);
-
-$end
-
-END plex;
+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_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';
+c_plex_author CONSTANT VARCHAR2(20 CHAR) := 'Ottmar Gobrecht';
+/**
+PL/SQL Export Utilities
+=======================
+
+PLEX was created to be able to quickstart version control for existing Oracle DB projects and has currently two main functions called **BackApp** and **Queries_to_CSV**. Queries_to_CSV is used by BackApp as a helper function, but its functionality is also useful standalone.
+
+Also see this resources for more information:
+
+- [Blog post on how to getting started](https://ogobrecht.github.io/posts/2018-08-26-plex-plsql-export-utilities)
+- [PLEX project page on GitHub](https://github.com/ogobrecht/plex)
+- [Give feedback on GitHub](https://github.com/ogobrecht/plex/issues/new).
+
+
+DEPENDENCIES
+
+The package itself is independend, but functionality varies on the following conditions:
+
+- For APEX app export: APEX >= 5.1.4 installed
+- For ORDS modules export: ORDS >= 18.3 installed (I think package ords_export is included since this version, but I don't know it)
+ - ATTENTION: There seems to be a bug in ORDS 19.2 which prevents you to export ORDS modules via the package ords_export: https://community.oracle.com/thread/4292776; please see plex_error_log.md, if you miss your ORDS modules after an export - this is no problem of PLEX
+
+
+INSTALLATION
+
+- Download the [latest version](https://github.com/ogobrecht/plex/releases/latest)
+- Unzip it, open a shell and go into the root directory
+- Start SQL*Plus (or another tool which can run SQL scripts)
+- To install PLEX run the provided install script `plex_install.sql` (script provides compiler flags)
+- To uninstall PLEX run the provided script `plex_uninstall.sql` or drop the package manually
+
+
+CHANGELOG
+
+- 2.1.0 (2019-12-30)
+ - Function BackApp:
+ - New parameter to include ORDS modules (p_include_ords_modules)
+ - New parameter to remove the outer column list on views, which is added by the compiler (p_object_view_remove_col_list); this was done in the past implicitly and can now be switched off; thanks to twitter.com/JKaschuba for the hint
+ - Object DDL: Comments for tables and views are now included
+ - Script templates: Improved export speed by using a base64 encoded zip file instead of a global temporary table to unload the files
+ - Fixed: Unable to export JAVA objects on systems with 30 character object names; thanks to twitter.com/JKaschuba for the hint
+ - Fixed: Views appears two times in resulting collection, each double file is postfixed with "_2" and empty
+ - Fixed: Tables and indices of materialized view definitions are exported (should be hidden)
+ - New function to_base64: convert BLOB into base64 encoded CLOB - this is helpful to download a BLOB file (like a zip file) with SQL*Plus
+- 2.0.2 (2019-08-16)
+ - Fixed: Function BackApp throws error on large APEX UI install files (ORA-06502: PL/SQL: numeric or value error: character string buffer too small)
+- 2.0.1 (2019-07-09)
+ - Fixed: Compile error when DB version is lower then 18.1 (PLS-00306: wrong number or types of arguments in call to 'REC_EXPORT_FILE')
+- 2.0.0 (2019-06-20)
+ - Package is now independend from APEX to be able to export schema object DDL and table data without an APEX installation
+ - ATTENTION: The return type of functions BackApp and Queries_to_CSV has changed from `apex_t_export_files` to `plex.tab_export_files`
+ - Function BackApp:
+ - New parameters to filter for object types
+ - New parameters to change base paths for backend, frontend and data
+- 1.2.1 (2019-03-13)
+ - Fixed: Script templates for function BackApp used old/invalid parameters
+ - Add install and uninstall scripts for PLEX itself
+- 1.2.0 (2018-10-31)
+ - Function BackApp: All like/not like parameters are now translated internally with the escape character set to backslash like so `... like 'YourExpression' escape '\'`
+ - Function Queries_to_CSV: Binary data type columns (raw, long_raw, blob, bfile) should no longer break the export
+- 1.1.0 (2018-09-23)
+ - Function BackApp: Change filter parameter from regular expression to list of like expressions for easier handling
+- 1.0.0 (2018-08-26)
+ - First public release
+**/
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- CONSTANTS, TYPES
+--------------------------------------------------------------------------------------------------------------------------------
+
+c_app_info_length CONSTANT PLS_INTEGER := 64;
+SUBTYPE app_info_text IS VARCHAR2(64 CHAR);
+
+TYPE rec_error_log IS RECORD (
+ time_stamp TIMESTAMP,
+ file_name VARCHAR2(255),
+ error_text VARCHAR2(200),
+ call_stack VARCHAR2(500));
+TYPE tab_error_log IS TABLE OF rec_error_log;
+
+TYPE rec_runtime_log IS RECORD (
+ overall_start_time TIMESTAMP,
+ overall_run_time NUMBER,
+ step INTEGER,
+ elapsed NUMBER,
+ execution NUMBER,
+ module app_info_text,
+ action app_info_text);
+TYPE tab_runtime_log IS TABLE OF rec_runtime_log;
+
+TYPE rec_export_file IS RECORD (
+ name VARCHAR2(255),
+ contents CLOB);
+TYPE tab_export_files IS TABLE OF rec_export_file;
+
+TYPE tab_vc32k IS TABLE OF varchar2(32767);
+TYPE tab_vc1k IS TABLE OF VARCHAR2(1024) INDEX BY BINARY_INTEGER;
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- MAIN METHODS
+--------------------------------------------------------------------------------------------------------------------------------
+
+FUNCTION backapp (
+ $if $$apex_installed $then
+ -- APEX App:
+ 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.
+ p_app_public_reports IN BOOLEAN DEFAULT true, -- If true, include public reports that a user saved.
+ p_app_private_reports IN BOOLEAN DEFAULT false, -- If true, include private reports that a user saved.
+ p_app_notifications IN BOOLEAN DEFAULT false, -- If true, include report notifications.
+ p_app_translations IN BOOLEAN DEFAULT true, -- If true, include application translation mappings and all text from the translation repository.
+ p_app_pkg_app_mapping IN BOOLEAN DEFAULT false, -- If true, export installed packaged applications with references to the packaged application definition. If FALSE, export them as normal applications.
+ p_app_original_ids IN BOOLEAN DEFAULT false, -- If true, export with the IDs as they were when the application was imported.
+ p_app_subscriptions IN BOOLEAN DEFAULT true, -- If true, components contain subscription references.
+ p_app_comments IN BOOLEAN DEFAULT true, -- If true, include developer comments.
+ 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
+ $if $$ords_installed $then
+ -- ORDS Modules:
+ p_include_ords_modules IN BOOLEAN DEFAULT false, -- If true, include ORDS modules of current user/schema.
+ $end
+ -- Schema Objects:
+ p_include_object_ddl IN BOOLEAN DEFAULT false, -- If true, include DDL of current user/schema and all its objects.
+ p_object_type_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type like '%BODY' escape '\' or object_type like 'JAVA%' escape '\').
+ p_object_type_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: '%BODY,JAVA%' will be translated to: ... from user_objects where ... and (object_type not like '%BODY' escape '\' and object_type not like 'JAVA%' escape '\').
+ 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: ... from user_objects where ... and (object_name like 'EMP%' escape '\' or object_name like 'DEPT%' escape '\').
+ p_object_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the objects - example: 'EMP%,DEPT%' will be translated to: ... from user_objects where ... and (object_name not like 'EMP%' escape '\' and object_name not like 'DEPT%' escape '\').
+ p_object_view_remove_col_list IN BOOLEAN DEFAULT true, -- If true, the outer column list, added by Oracle on views during compilation, is removed
+ -- Table Data:
+ p_include_data IN BOOLEAN DEFAULT false, -- If true, include CSV data of each table.
+ p_data_as_of_minutes_ago IN NUMBER DEFAULT 0, -- Read consistent data with the resulting timestamp(SCN).
+ p_data_max_rows IN NUMBER DEFAULT 1000, -- Maximum number of rows per table.
+ p_data_table_name_like IN VARCHAR2 DEFAULT null, -- A comma separated list of like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name like 'EMP%' escape '\' or table_name like 'DEPT%' escape '\').
+ p_data_table_name_not_like IN VARCHAR2 DEFAULT null, -- A comma separated list of not like expressions to filter the tables - example: 'EMP%,DEPT%' will be translated to: where ... and (table_name not like 'EMP%' escape '\' and table_name not like 'DEPT%' escape '\').
+ -- General Options:
+ p_include_templates IN BOOLEAN DEFAULT true, -- If true, include templates for README.md, export and install scripts.
+ p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with detailed runtime infos.
+ p_include_error_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_error_log.md with detailed error messages.
+ p_base_path_backend IN VARCHAR2 DEFAULT 'app_backend', -- The base path in the project root for the Schema objects.
+ p_base_path_frontend IN VARCHAR2 DEFAULT 'app_frontend', -- The base path in the project root for the APEX app.
+ p_base_path_web_services IN VARCHAR2 DEFAULT 'app_web_services', -- The base path in the project root for the ORDS modules.
+ p_base_path_data IN VARCHAR2 DEFAULT 'app_data') -- The base path in the project root for the table data.
+RETURN tab_export_files;
+/**
+Get a file collection of an APEX application (or the current user/schema only) including:
+
+- The app export SQL files splitted ready to use for version control and deployment
+- Optional the DDL scripts for all objects and grants
+- Optional the data in CSV files (this option was implemented to track catalog tables, can be used as logical backup, has the typical CSV limitations...)
+- Everything in a (hopefully) nice directory structure
+
+EXAMPLE BASIC USAGE
+
+```sql
+DECLARE
+ l_file_collection plex.tab_export_files;
+BEGIN
+ l_file_collection := plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => false,
+ p_include_data => false,
+ p_include_templates => false);
+
+ -- do something with the file collection
+ FOR i IN 1..l_file_collection.count LOOP
+ dbms_output.put_line(i || ' | '
+ || lpad(round(length(l_file_collection(i).contents) / 1024), 3) || ' kB' || ' | '
+ || l_file_collection(i).name);
+ END LOOP;
+END;
+{{/}}
+```
+
+EXAMPLE ZIP FILE PL/SQL
+
+```sql
+DECLARE
+ l_zip_file BLOB;
+BEGIN
+ l_zip_file := plex.to_zip(plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true));
+ -- do something with the zip file
+ -- Your code here...
+END;
+{{/}}
+```
+
+EXAMPLE ZIP FILE SQL
+
+```sql
+-- Inline function because of boolean parameters (needs Oracle 12c or higher).
+-- Alternative create a helper function and call that in a SQL context.
+WITH
+ FUNCTION backapp RETURN BLOB IS
+ BEGIN
+ RETURN plex.to_zip(plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true));
+ END backapp;
+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
+-- 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
+-- Example Mac: base64 -D -i app_100.zip.base64 -o app_100.zip
+-- Example Linux: base64 -d app_100.zip.base64 > app_100.zip
+set verify off feedback off heading off termout off
+set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
+whenever sqlerror exit sql.sqlcode rollback
+variable contents clob
+BEGIN
+ :contents := plex.to_base64(plex.to_zip(plex.backapp(
+ p_app_id => 100, -- parameter only available when APEX is installed
+ p_include_ords_modules => true, -- parameter only available when ORDS is installed
+ p_include_object_ddl => true,
+ p_include_data => false,
+ p_include_templates => true)));
+END;
+{{/}}
+spool "app_100.zip.base64"
+print contents
+spool off
+```
+**/
+
+
+
+PROCEDURE add_query (
+ p_query IN VARCHAR2, -- The query itself
+ p_file_name IN VARCHAR2, -- File name like 'Path/to/your/file-without-extension'.
+ p_max_rows IN NUMBER DEFAULT 1000); -- The maximum number of rows to be included in your file.
+/**
+Add a query to be processed by the method queries_to_csv. You can add as many queries as you like.
+
+EXAMPLE
+
+```sql
+BEGIN
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+END;
+{{/}}
+```
+**/
+
+
+
+FUNCTION queries_to_csv (
+ p_delimiter IN VARCHAR2 DEFAULT ',', -- The column delimiter.
+ p_quote_mark IN VARCHAR2 DEFAULT '"', -- Used when the data contains the delimiter character.
+ p_header_prefix IN VARCHAR2 DEFAULT NULL, -- Prefix the header line with this text.
+ p_include_runtime_log IN BOOLEAN DEFAULT true, -- If true, generate file plex_runtime_log.md with runtime statistics.
+ p_include_error_log IN BOOLEAN DEFAULT true) -- If true, generate file plex_error_log.md with detailed error messages.
+RETURN tab_export_files;
+/**
+Export one or more queries as CSV data within a file collection.
+
+EXAMPLE BASIC USAGE
+
+```sql
+DECLARE
+ l_file_collection plex.tab_export_files;
+BEGIN
+ --fill the queries array
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ -- process the queries
+ l_file_collection := plex.queries_to_csv;
+ -- do something with the file collection
+ FOR i IN 1..l_file_collection.count LOOP
+ dbms_output.put_line(i || ' | '
+ || lpad(round(length(l_file_collection(i).contents) / 1024), 3) || ' kB' || ' | '
+ || l_file_collection(i).name);
+ END LOOP;
+END;
+{{/}}
+```
+
+EXAMPLE EXPORT ZIP FILE PL/SQL
+
+```sql
+DECLARE
+ l_zip_file BLOB;
+BEGIN
+ --fill the queries array
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ -- process the queries
+ l_zip_file := plex.to_zip(plex.queries_to_csv);
+ -- do something with the zip file
+ -- Your code here...
+END;
+{{/}}
+```
+
+EXAMPLE EXPORT ZIP FILE SQL
+
+```sql
+WITH
+ FUNCTION queries_to_csv_zip RETURN BLOB IS
+ v_return BLOB;
+ BEGIN
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ v_return := plex.to_zip(plex.queries_to_csv);
+ RETURN v_return;
+ END queries_to_csv_zip;
+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
+-- 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
+-- Example Mac: base64 -D -i metadata.zip.base64 -o metadata.zip
+-- Example Linux: base64 -d metadata.zip.base64 > metadata.zip
+set verify off feedback off heading off termout off
+set trimout on trimspool on pagesize 0 linesize 5000 long 100000000 longchunksize 32767
+whenever sqlerror exit sql.sqlcode rollback
+variable contents clob
+BEGIN
+ --fill the queries array
+ plex.add_query(
+ p_query => 'select * from user_tables',
+ p_file_name => 'user_tables');
+ plex.add_query(
+ p_query => 'select * from user_tab_columns',
+ p_file_name => 'user_tab_columns',
+ p_max_rows => 10000);
+ -- process the queries
+ :contents := plex.to_base64(plex.to_zip(plex.queries_to_csv));
+END;
+{{/}}
+spool "metadata.zip.base64"
+print contents
+spool off
+```
+**/
+
+
+
+FUNCTION to_zip (
+ p_file_collection IN tab_export_files) -- The file collection to zip.
+RETURN BLOB;
+/**
+Convert a file collection to a zip file.
+
+EXAMPLE
+
+```sql
+DECLARE
+ l_zip BLOB;
+BEGIN
+ l_zip := plex.to_zip(plex.backapp(
+ p_app_id => 100,
+ p_include_object_ddl => true));
+ -- do something with the zip file...
+END;
+```
+**/
+
+FUNCTION to_base64(
+ p_blob IN BLOB) -- The BLOB to convert.
+RETURN CLOB;
+/**
+Encodes a BLOB into a Base64 CLOB for transfers over a network (like with SQL*Plus). For encoding on the client side see [this blog article](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/).
+
+```sql
+DECLARE
+ l_clob CLOB;
+BEGIN
+ l_clob := plex.to_base64(plex.to_zip(plex.backapp(
+ p_app_id => 100,
+ p_include_object_ddl => true)));
+ -- do something with the clob...
+END;
+```
+**/
+
+FUNCTION view_error_log RETURN tab_error_log PIPELINED;
+/**
+View the error log from the last plex run. The internal array for the error log is cleared on each call of BackApp or Queries_to_CSV.
+
+EXAMPLE
+
+```sql
+SELECT * FROM TABLE(plex.view_error_log);
+```
+**/
+
+FUNCTION view_runtime_log RETURN tab_runtime_log PIPELINED;
+/**
+View the runtime log from the last plex run. The internal array for the runtime log is cleared on each call of BackApp or Queries_to_CSV.
+
+EXAMPLE
+
+```sql
+SELECT * FROM TABLE(plex.view_runtime_log);
+```
+**/
+
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- UTILITIES (only available when v_utils_public is set to 'true' in install script plex_install.sql)
+--------------------------------------------------------------------------------------------------------------------------------
+
+$if $$utils_public $then
+
+FUNCTION util_bool_to_string (p_bool IN BOOLEAN) RETURN VARCHAR2;
+
+FUNCTION util_string_to_bool (
+ p_bool_string IN VARCHAR2,
+ p_default IN BOOLEAN)
+RETURN BOOLEAN;
+
+FUNCTION util_split (
+ p_string IN VARCHAR2,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN tab_vc32k;
+
+FUNCTION util_join (
+ p_array IN tab_vc32k,
+ p_delimiter IN VARCHAR2 DEFAULT ',')
+RETURN VARCHAR2;
+
+FUNCTION util_clob_to_blob (p_clob CLOB) RETURN BLOB;
+
+/*
+ZIP UTILS
+- The following four zip utilities are copied from this article:
+ - Blog: https://technology.amis.nl/2010/03/13/utl_compress-gzip-and-zlib/
+ - Source: https://technology.amis.nl/wp-content/uploads/2010/06/as_zip10.txt
+- Copyright (c) 2010, 2011 by Anton Scheffer (MIT license)
+- Thank you for sharing this Anton :-)
+*/
+FUNCTION util_zip_blob_to_num (
+ p_blob IN BLOB,
+ p_len IN INTEGER,
+ p_pos IN INTEGER)
+RETURN NUMBER;
+FUNCTION util_zip_little_endian (
+ p_big IN NUMBER,
+ p_bytes IN PLS_INTEGER := 4)
+RETURN RAW;
+PROCEDURE util_zip_add_file (
+ p_zipped_blob IN OUT BLOB,
+ p_name IN VARCHAR2,
+ p_content IN BLOB);
+
+PROCEDURE util_zip_finish (p_zipped_blob IN OUT BLOB);
+
+FUNCTION util_multi_replace (
+ p_source_string VARCHAR2,
+ p_01_find VARCHAR2 DEFAULT NULL, p_01_replace VARCHAR2 DEFAULT NULL,
+ p_02_find VARCHAR2 DEFAULT NULL, p_02_replace VARCHAR2 DEFAULT NULL,
+ p_03_find VARCHAR2 DEFAULT NULL, p_03_replace VARCHAR2 DEFAULT NULL,
+ p_04_find VARCHAR2 DEFAULT NULL, p_04_replace VARCHAR2 DEFAULT NULL,
+ p_05_find VARCHAR2 DEFAULT NULL, p_05_replace VARCHAR2 DEFAULT NULL,
+ p_06_find VARCHAR2 DEFAULT NULL, p_06_replace VARCHAR2 DEFAULT NULL,
+ p_07_find VARCHAR2 DEFAULT NULL, p_07_replace VARCHAR2 DEFAULT NULL,
+ p_08_find VARCHAR2 DEFAULT NULL, p_08_replace VARCHAR2 DEFAULT NULL,
+ p_09_find VARCHAR2 DEFAULT NULL, p_09_replace VARCHAR2 DEFAULT NULL,
+ p_10_find VARCHAR2 DEFAULT NULL, p_10_replace VARCHAR2 DEFAULT NULL,
+ p_11_find VARCHAR2 DEFAULT NULL, p_11_replace VARCHAR2 DEFAULT NULL,
+ p_12_find VARCHAR2 DEFAULT NULL, p_12_replace VARCHAR2 DEFAULT NULL)
+RETURN VARCHAR2;
+
+FUNCTION util_set_build_status_run_only (p_app_export_sql IN CLOB) RETURN CLOB;
+
+FUNCTION util_calc_data_timestamp (p_as_of_minutes_ago IN NUMBER) RETURN TIMESTAMP;
+
+PROCEDURE util_setup_dbms_metadata (
+ p_pretty IN BOOLEAN DEFAULT true,
+ p_constraints IN BOOLEAN DEFAULT true,
+ p_ref_constraints IN BOOLEAN DEFAULT false,
+ p_partitioning IN BOOLEAN DEFAULT true,
+ p_tablespace IN BOOLEAN DEFAULT false,
+ p_storage IN BOOLEAN DEFAULT false,
+ 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);
+
+PROCEDURE util_ensure_unique_file_names (p_export_files IN OUT tab_export_files);
+
+--------------------------------------------------------------------------------------------------------------------------------
+-- The following tools are working on global private package variables
+--------------------------------------------------------------------------------------------------------------------------------
+
+PROCEDURE util_log_init (p_module IN VARCHAR2);
+
+PROCEDURE util_log_start (p_action IN VARCHAR2);
+
+PROCEDURE util_log_error (p_name VARCHAR2);
+
+PROCEDURE util_log_stop;
+
+FUNCTION util_log_get_runtime (
+ p_start IN TIMESTAMP,
+ p_stop IN TIMESTAMP)
+RETURN NUMBER;
+
+PROCEDURE util_log_calc_runtimes;
+
+PROCEDURE util_clob_append (p_content IN VARCHAR2);
+
+PROCEDURE util_clob_append (p_content IN CLOB);
+
+PROCEDURE util_clob_flush_cache;
+
+PROCEDURE util_clob_add_to_export_files (
+ p_export_files IN OUT NOCOPY tab_export_files,
+ p_name IN VARCHAR2);
+
+PROCEDURE util_clob_query_to_csv (
+ p_query IN VARCHAR2,
+ p_max_rows IN NUMBER DEFAULT 1000,
+ p_delimiter IN VARCHAR2 DEFAULT ',',
+ p_quote_mark IN VARCHAR2 DEFAULT '"',
+ p_header_prefix IN VARCHAR2 DEFAULT NULL);
+
+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);
+
+$end
+
+END plex;
/
\ No newline at end of file
diff --git a/src/build.js b/src/build.js
new file mode 100644
index 0000000..18eff9e
--- /dev/null
+++ b/src/build.js
@@ -0,0 +1,17 @@
+var fs = require('fs');
+var spec =
+
+fs.writeFileSync(
+ 'plex_install.sql',
+ fs.readFileSync('src/plex_install.sql', 'utf8')
+ .replace('@plex.pks', function(){return fs.readFileSync('src/plex.pks', 'utf8')})
+ .replace('@plex.pkb', function(){return fs.readFileSync('src/plex.pkb', 'utf8')})
+ // Read what this function thing is doing, without it we get wrong results.
+ // We have dollar signs in our package body text - the last answer explains:
+ // https://stackoverflow.com/questions/9423722/string-replace-weird-behavior-when-using-dollar-sign-as-replacement
+);
+
+fs.copyFileSync(
+ 'src/plex_uninstall.sql',
+ 'plex_uninstall.sql'
+);
\ No newline at end of file
diff --git a/lib/as_zip.sql b/src/lib/as_zip.sql
similarity index 100%
rename from lib/as_zip.sql
rename to src/lib/as_zip.sql
diff --git a/lib/as_zip.txt b/src/lib/as_zip.txt
similarity index 100%
rename from lib/as_zip.txt
rename to src/lib/as_zip.txt
diff --git a/src/plex_install.sql b/src/plex_install.sql
new file mode 100644
index 0000000..cc8cacc
--- /dev/null
+++ b/src/plex_install.sql
@@ -0,0 +1,48 @@
+set define off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+
+prompt
+prompt Installing PL/SQL Export Utilities
+prompt ==================================
+
+prompt Set compiler flags
+DECLARE
+ v_apex_installed VARCHAR2(5) := 'FALSE'; -- Do not change (is set dynamically).
+ v_ords_installed VARCHAR2(5) := 'FALSE'; -- Do not change (is set dynamically).
+ v_utils_public VARCHAR2(5) := 'FALSE'; -- Make utilities public available (for testing or other usages).
+ v_debug_on VARCHAR2(5) := 'FALSE'; -- Object DDL: extract only one object per type to find problematic ones and save time in big schemas like APEX_XXX.
+BEGIN
+ FOR i IN (SELECT *
+ FROM all_objects
+ WHERE object_type = 'SYNONYM'
+ AND object_name = 'APEX_EXPORT') LOOP
+ v_apex_installed := 'TRUE';
+ END LOOP;
+ FOR i IN (SELECT *
+ FROM all_objects
+ WHERE object_type = 'SYNONYM'
+ AND object_name = 'ORDS_EXPORT') LOOP
+ v_ords_installed := 'TRUE';
+ END LOOP;
+ -- Show unset compiler flags as errors (results for example in errors like "PLW-06003: unknown inquiry directive '$$UTILS_PUBLIC'")
+ EXECUTE IMMEDIATE 'alter session set plsql_warnings = ''ENABLE:6003''';
+ -- Finally set compiler flags
+ EXECUTE IMMEDIATE 'alter session set plsql_ccflags = '''
+ || 'apex_installed:' || v_apex_installed || ','
+ || 'ords_installed:' || v_ords_installed || ','
+ || 'utils_public:' || v_utils_public || ','
+ || 'debug_on:' || v_debug_on || '''';
+END;
+/
+
+prompt Compile package plex (spec)
+@plex.pks
+show errors
+
+prompt Compile package plex (body)
+@plex.pkb
+show errors
+
+prompt ==================================
+prompt Installation Done
+prompt
diff --git a/src/plex_install_dev_test_ccflags.sql b/src/plex_install_dev_test_ccflags.sql
new file mode 100644
index 0000000..63890b5
--- /dev/null
+++ b/src/plex_install_dev_test_ccflags.sql
@@ -0,0 +1,57 @@
+set define off feedback off
+whenever sqlerror exit sql.sqlcode rollback
+
+prompt
+prompt Installing PL/SQL Export Utilities
+prompt ==================================
+
+prompt Show unset compiler flags as errors (results for example in errors like "PLW-06003: unknown inquiry directive '$$UTILS_PUBLIC'"
+alter session set plsql_warnings = 'ENABLE:6003';
+
+prompt ---
+
+prompt Set compiler flags to apex_installed:false, ords_installed:false, utils_public:false, debug_on:false
+alter session set plsql_ccflags = 'apex_installed:false, ords_installed:false, utils_public:false, debug_on:false';
+prompt Compile package plex (spec)
+@plex.pks
+show errors
+prompt Compile package plex (body)
+@plex.pkb
+show errors
+
+prompt ---
+
+prompt Set compiler flags: apex_installed:true, ords_installed:false, utils_public:false, debug_on:false
+alter session set plsql_ccflags = 'apex_installed:true, ords_installed:false, utils_public:false, debug_on:false';
+prompt Compile package plex (spec)
+@plex.pks
+show errors
+prompt Compile package plex (body)
+@plex.pkb
+show errors
+
+prompt ---
+
+prompt Set compiler flags: apex_installed:false, ords_installed:true, utils_public:false, debug_on:false
+alter session set plsql_ccflags = 'apex_installed:false, ords_installed:true, utils_public:false, debug_on:false';
+prompt Compile package plex (spec)
+@plex.pks
+show errors
+prompt Compile package plex (body)
+@plex.pkb
+show errors
+
+prompt ---
+
+prompt Set compiler flags: apex_installed:true, ords_installed:true, utils_public:false, debug_on:false
+alter session set plsql_ccflags = 'apex_installed:true, ords_installed:true, utils_public:false, debug_on:false';
+prompt Compile package plex (spec)
+@plex.pks
+show errors
+prompt Compile package plex (body)
+@plex.pkb
+show errors
+
+prompt ==================================
+prompt Installation Done
+prompt
diff --git a/src/plex_test.sql b/src/plex_test.sql
new file mode 100644
index 0000000..1c0ed6e
--- /dev/null
+++ b/src/plex_test.sql
@@ -0,0 +1,18 @@
+-- Inline function because of boolean parameters (needs Oracle 12c or higher).
+-- Alternative create a helper function and call that in a SQL context.
+WITH
+ FUNCTION backapp RETURN BLOB IS
+ BEGIN
+ RETURN plex.to_zip(plex.backapp(
+ p_app_id => 100,
+ p_include_object_ddl => true,
+ p_include_ords_modules => true,
+ p_include_data => true,
+ p_include_templates => true));
+ END backapp;
+SELECT backapp FROM dual;
+/
+
+select * from table(plex.view_runtime_log);
+
+
diff --git a/src/plex_uninstall.sql b/src/plex_uninstall.sql
new file mode 100644
index 0000000..f85d4e2
--- /dev/null
+++ b/src/plex_uninstall.sql
@@ -0,0 +1,30 @@
+SET DEFINE OFF FEEDBACK OFF
+WHENEVER SQLERROR EXIT sql.sqlcode ROLLBACK
+prompt
+prompt Uninstalling PL/SQL Export Utilities
+prompt ====================================
+prompt Drop package plex if exists (body)
+BEGIN
+ FOR i IN (SELECT object_type,
+ object_name
+ FROM user_objects
+ WHERE object_type = 'PACKAGE BODY'
+ AND object_name = 'PLEX') LOOP
+ EXECUTE IMMEDIATE 'DROP ' || i.object_type || ' ' || i.object_name;
+ END LOOP;
+END;
+/
+prompt Drop package plex if exists (spec)
+BEGIN
+ FOR i IN (SELECT object_type,
+ object_name
+ FROM user_objects
+ WHERE object_type = 'PACKAGE'
+ AND object_name = 'PLEX') LOOP
+ EXECUTE IMMEDIATE 'DROP ' || i.object_type || ' ' || i.object_name;
+ END LOOP;
+END;
+/
+prompt ====================================
+prompt Uninstallation Done
+prompt
\ No newline at end of file