Saturday, October 25, 2025

#!/usr/bin/env bash # dp_migrate.sh - Oracle 19c Data Pump migration & DDL extractor (menu-driven) # Requirements: bash 4+, sqlplus, expdp, impdp in PATH. Run on a host with Oracle client. set -euo pipefail #------------------------ Bootstrap & Paths ------------------------------------ CONFIG_FILE="${1:-dp_migrate.conf}" # pass a different conf path as first arg SCRIPT_NAME="$(basename "$0")" RUN_ID="$(date +%Y%m%d_%H%M%S)" WORK_DIR="${WORK_DIR:-/tmp/dp_migrate_${RUN_ID}}" LOG_DIR="${LOG_DIR:-${WORK_DIR}/logs}" PAR_DIR="${PAR_DIR:-${WORK_DIR}/parfiles}" DDL_DIR="${DDL_DIR:-${WORK_DIR}/ddls}" COMMON_DIR_NAME="${COMMON_DIR_NAME:-DP_DIR}" # DB DIRECTORY object name on both src/tgt mkdir -p "$WORK_DIR" "$LOG_DIR" "$PAR_DIR" "$DDL_DIR" #------------------------ Pretty print helpers -------------------------------- ce() { printf "%b\n" "$*"; } ok() { ce "\e[32m✔ $*\e[0m"; } warn() { ce "\e[33m! $*\e[0m"; } err() { ce "\e[31m✘ $*\e[0m"; } #------------------------ Load Config ----------------------------------------- [[ -f "$CONFIG_FILE" ]] || { err "Config file not found: $CONFIG_FILE"; exit 1; } # shellcheck disable=SC1090 source "$CONFIG_FILE" # Required variables need_vars=( SRC_EZCONNECT TGT_EZCONNECT SYS_PASSWORD NAS_PATH DUMPFILE_PREFIX ) for v in "${need_vars[@]}"; do [[ -n "${!v:-}" ]] || { err "Missing required config variable: $v"; exit 1; } done #------------------------ Defaults / Tunables --------------------------------- PARALLEL="${PARALLEL:-4}" COMPRESSION="${COMPRESSION:-METADATA_ONLY}" # ALL|DATA_ONLY|METADATA_ONLY|NONE ENCRYPTION_PASSWORD="${ENCRYPTION_PASSWORD:-}"# optional dump encryption password TABLE_EXISTS_ACTION="${TABLE_EXISTS_ACTION:-APPEND}" # impdp behavior REMAP_SCHEMA="${REMAP_SCHEMA:-}" REMAP_TABLESPACE="${REMAP_TABLESPACE:-}" INCLUDE="${INCLUDE:-}" EXCLUDE="${EXCLUDE:-}" FLASHBACK_SCN="${FLASHBACK_SCN:-}" FLASHBACK_TIME="${FLASHBACK_TIME:-}" ESTIMATE_ONLY="${ESTIMATE_ONLY:-N}" # Optional schema lists for user/conf option SCHEMAS_LIST_EXP="${SCHEMAS_LIST_EXP:-}" # e.g., "HR,SCOTT" SCHEMAS_LIST_IMP="${SCHEMAS_LIST_IMP:-}" # if empty, falls back to SCHEMAS_LIST_EXP # Auto-discovery skip list (names or LIKE patterns; case-insensitive) # Example: SKIP_SCHEMAS="APEX_%,ORDS%,SYS%,XDB,APP_%,%_STG" SKIP_SCHEMAS="${SKIP_SCHEMAS:-}" # For the “All DDLs” sweep (adjust as needed) DDL_OBJECT_TYPES="${DDL_OBJECT_TYPES:-TABLE,INDEX,VIEW,SEQUENCE,TRIGGER,FUNCTION,PROCEDURE,PACKAGE,PACKAGE_BODY,MATERIALIZED_VIEW,TYPE,SYNONYM}" # Tablespaces to skip in tablespace DDL export SKIP_TABLESPACES="${SKIP_TABLESPACES:-SYSTEM,SYSAUX,TEMP,UNDOTBS1,UNDOTBS2}" # NEW: Global dry-run switch for destructive IMPORT cleanup actions DRY_RUN_ONLY="${DRY_RUN_ONLY:-N}" # Y=show preview reports only, do NOT execute #------------------------ Pre-flight checks ----------------------------------- for b in sqlplus expdp impdp; do command -v "$b" >/devnull 2>&1 || { err "Missing required binary: $b"; exit 1; } done [[ -d "$NAS_PATH" ]] || { err "NAS mount path not found on this host: $NAS_PATH"; exit 1; } ok "Using config: $CONFIG_FILE" ok "Work: $WORK_DIR | Logs: $LOG_DIR | Parfiles: $PAR_DIR | DDLs: $DDL_DIR" #------------------------ Utility helpers ------------------------------------- mask_pwd() { sed 's#[^/"]\{1,\}@#***@#g' | sed 's#sys/[^@]*@#sys/****@#g'; } run_sql() { # $1=ezconnect, $2=tag, $3+=SQL local ez="$1"; shift local tag="${1:-sql}"; shift || true local sql="$*" local conn="sys/${SYS_PASSWORD}@${ez} as sysdba" local logf="${LOG_DIR}/${tag}_${RUN_ID}.log" sqlplus -s "$conn" <"$logf" 2>&1 SET PAGES 0 FEEDBACK OFF LINES 32767 VERIFY OFF HEADING OFF ECHO OFF ${sql} EXIT SQL if grep -qi "ORA-" "$logf"; then err "SQL error: ${tag} (see $logf)" tail -n 120 "$logf" | mask_pwd | sed 's/^/ /' exit 1 fi ok "SQL ok: ${tag}" } print_log() { local tag="$1" local logf="${LOG_DIR}/${tag}_${RUN_ID}.log" [[ -f "$logf" ]] && { echo "----- ${tag} (dry-run) -----"; cat "$logf"; echo "----- end (${tag}) -----"; } || true } ensure_directory_object() { local ez="$1" host_tag="$2" dir_name="${3:-$COMMON_DIR_NAME}" dir_path="$NAS_PATH" run_sql "$ez" "create_dir_${host_tag}" " DECLARE n INTEGER; BEGIN SELECT COUNT(*) INTO n FROM all_directories WHERE directory_name = '${dir_name}'; EXECUTE IMMEDIATE 'CREATE OR REPLACE DIRECTORY ${dir_name} AS ''${dir_path}'''; BEGIN EXECUTE IMMEDIATE 'GRANT READ,WRITE ON DIRECTORY ${dir_name} TO PUBLIC'; EXCEPTION WHEN OTHERS THEN NULL; END; END; / " } par_common() { local mode="$1" tag="$2" local pf="${PAR_DIR}/${tag}_${RUN_ID}.par" { echo "directory=${COMMON_DIR_NAME}" echo "logfile=${DUMPFILE_PREFIX}_${tag}_${RUN_ID}.log" echo "parallel=${PARALLEL}" } > "$pf" if [[ "$mode" == "expdp" ]]; then echo "dumpfile=${DUMPFILE_PREFIX}_${tag}_${RUN_ID}_%U.dmp" >> "$pf" echo "compression=${COMPRESSION}" >> "$pf" [[ -n "$FLASHBACK_SCN" ]] && echo "flashback_scn=${FLASHBACK_SCN}" >> "$pf" [[ -n "$FLASHBACK_TIME" ]] && echo "flashback_time=${FLASHBACK_TIME}" >> "$pf" [[ -n "$INCLUDE" ]] && echo "include=${INCLUDE}" >> "$pf" [[ -n "$EXCLUDE" ]] && echo "exclude=${EXCLUDE}" >> "$pf" [[ "${ESTIMATE_ONLY^^}" == "Y" ]] && echo "estimate_only=Y" >> "$pf" [[ -n "$ENCRYPTION_PASSWORD" ]] && { echo "encryption=encrypt_password"; echo "encryption_password=${ENCRYPTION_PASSWORD}"; } >> "$pf" else echo "table_exists_action=${TABLE_EXISTS_ACTION}" >> "$pf" [[ -n "$REMAP_SCHEMA" ]] && echo "remap_schema=${REMAP_SCHEMA}" >> "$pf" [[ -n "$REMAP_TABLESPACE" ]] && echo "remap_tablespace=${REMAP_TABLESPACE}" >> "$pf" [[ -n "$INCLUDE" ]] && echo "include=${INCLUDE}" >> "$pf" [[ -n "$EXCLUDE" ]] && echo "exclude=${EXCLUDE}" >> "$pf" [[ -n "$ENCRYPTION_PASSWORD" ]] && echo "encryption_password=${ENCRYPTION_PASSWORD}" >> "$pf" fi echo "$pf" } #------------------------ Schema discovery & confirmation ---------------------- schemas_skip_predicate() { local out="" item trimmed IFS=',' read -r -a arr <<< "${SKIP_SCHEMAS}" for item in "${arr[@]}"; do trimmed="$(echo "$item" | awk '{$1=$1;print}')" [[ -z "$trimmed" ]] && continue trimmed="${trimmed//\'/\'\'}" out+=" AND UPPER(username) NOT LIKE UPPER('${trimmed}')"$'\n' done printf "%s" "$out" } get_nonmaintained_schemas() { local tmp_pred; tmp_pred="$(schemas_skip_predicate)" run_sql "$SRC_EZCONNECT" "list_nonmaint_users" " SET PAGES 0 FEEDBACK OFF HEADING OFF WITH base AS ( SELECT username FROM dba_users WHERE oracle_maintained='N' $(printf "%s" "${tmp_pred}") ) SELECT LISTAGG(username, ',') WITHIN GROUP (ORDER BY username) FROM base; / " awk 'NF{line=$0} END{print line}' "${LOG_DIR}/list_nonmaint_users_${RUN_ID}.log" } get_nonmaintained_schemas_tgt() { local tmp_pred; tmp_pred="$(schemas_skip_predicate)" run_sql "$TGT_EZCONNECT" "tgt_nonmaint_users" " SET PAGES 0 FEEDBACK OFF HEADING OFF WITH base AS ( SELECT username FROM dba_users WHERE oracle_maintained='N' $(printf "%s" "${tmp_pred}") ) SELECT LISTAGG(username, ',') WITHIN GROUP (ORDER BY username) FROM base; / " awk 'NF{line=$0} END{print line}' "${LOG_DIR}/tgt_nonmaint_users_${RUN_ID}.log" } confirm_edit_value() { local label="$1" val="${2:-}" ans echo "${label}: ${val}" read -rp "Use this value? (Y to accept, N to edit) [Y/N]: " ans if [[ "${ans^^}" == "N" ]]; then read -rp "Enter new ${label}: " val fi echo "$val" } csv_to_inlist() { local csv="${1:-}" out="" tok IFS=',' read -r -a arr <<< "$csv" for tok in "${arr[@]}"; do tok="$(echo "$tok" | awk '{$1=$1;print}')" [[ -z "$tok" ]] && continue tok="${tok//\'/\'\'}" out+="${out:+,}''${tok^^}''" done echo "$out" } #------------------------ Dry-run / Preview reports (TARGET) ------------------- report_user_list() { local tag="$1" inlist="${2:-}" pred q if [[ -z "$inlist" ]]; then pred="$(schemas_skip_predicate)" q=" WITH base AS (SELECT username FROM dba_users WHERE oracle_maintained='N' ${pred} ) SELECT username FROM base ORDER BY username; " else q=" SELECT username FROM dba_users WHERE oracle_maintained='N' AND UPPER(username) IN (${inlist}) ORDER BY username; " fi run_sql "$TGT_EZCONNECT" "${tag}" " SET HEADING ON PAGES 200 LINES 200 FEEDBACK ON COLUMN USERNAME FORMAT A30 PROMPT === USER LIST (TARGET) === ${q} PROMPT === TOTAL USERS === SELECT COUNT(*) AS cnt FROM ( ${q} ); / " print_log "${tag}" } report_object_counts() { local tag="$1" inlist="${2:-}" pred owner_pred if [[ -z "$inlist" ]]; then pred="$(schemas_skip_predicate)" owner_pred="owner IN (SELECT username FROM dba_users WHERE oracle_maintained='N' $(printf "%s" "${pred}"))" else owner_pred="UPPER(owner) IN (${inlist})" fi run_sql "$TGT_EZCONNECT" "${tag}" " SET HEADING ON PAGES 200 LINES 200 FEEDBACK ON COLUMN OWNER FORMAT A30 COLUMN OBJECT_TYPE FORMAT A25 PROMPT === OBJECT COUNTS BY OWNER & TYPE (TARGET) === SELECT owner, object_type, COUNT(*) AS cnt FROM dba_objects WHERE ${owner_pred} AND object_name NOT LIKE 'BIN$%' GROUP BY owner, object_type ORDER BY owner, object_type; PROMPT === TOTAL OBJECTS (TARGET) === SELECT COUNT(*) AS total_objects FROM dba_objects WHERE ${owner_pred} AND object_name NOT LIKE 'BIN$%'; / " print_log "${tag}" } #------------------------ EXPDP: Menus & actions ------------------------------- exp_full_menu() { while true; do cat <<'EOS' Export FULL (choose content): 1) metadata_only (CONTENT=METADATA_ONLY) 2) full (CONTENT=ALL) 3) Back EOS read -rp "Choose: " c case "$c" in 1) ensure_directory_object "$SRC_EZCONNECT" "src" local pf; pf=$(par_common expdp "exp_full_meta") { echo "full=Y"; echo "content=METADATA_ONLY"; } >> "$pf" ok "Parfile: $pf"; expdp "sys/${SYS_PASSWORD}@${SRC_EZCONNECT} as sysdba" parfile="$pf" ;; 2) ensure_directory_object "$SRC_EZCONNECT" "src" local pf; pf=$(par_common expdp "exp_full_all") { echo "full=Y"; echo "content=ALL"; } >> "$pf" ok "Parfile: $pf"; expdp "sys/${SYS_PASSWORD}@${SRC_EZCONNECT} as sysdba" parfile="$pf" ;; 3) break ;; *) warn "Invalid choice" ;; esac done } exp_schemas_menu() { while true; do cat <<'EOS' Export SCHEMAS: 1) All accounts (exclude Oracle-maintained; honors SKIP_SCHEMAS) 2) User input or value from conf (SCHEMAS_LIST_EXP) with confirmation 3) Back EOS read -rp "Choose: " c case "$c" in 1) ensure_directory_object "$SRC_EZCONNECT" "src" local schemas; schemas="$(get_nonmaintained_schemas)" schemas="$(confirm_edit_value "Schemas" "$schemas")" local pf; pf=$(par_common expdp "exp_schemas_auto") { echo "schemas=${schemas}"; echo "content=ALL"; } >> "$pf" ok "Parfile: $pf"; expdp "sys/${SYS_PASSWORD}@${SRC_EZCONNECT} as sysdba" parfile="$pf" ;; 2) ensure_directory_object "$SRC_EZCONNECT" "src" local init="${SCHEMAS_LIST_EXP:-}"; [[ -z "$init" ]] && read -rp "Enter schemas (comma-separated): " init local schemas; schemas="$(confirm_edit_value "Schemas" "$init")" local pf; pf=$(par_common expdp "exp_schemas_user") { echo "schemas=${schemas}"; echo "content=ALL"; } >> "$pf" ok "Parfile: $pf"; expdp "sys/${SYS_PASSWORD}@${SRC_EZCONNECT} as sysdba" parfile="$pf" ;; 3) break ;; *) warn "Invalid choice" ;; esac done } exp_tablespaces() { ensure_directory_object "$SRC_EZCONNECT" "src" read -rp "Tablespaces (comma-separated): " tbs local pf; pf=$(par_common expdp "exp_tbs") echo "transport_tablespaces=${tbs}" >> "$pf" ok "Parfile: $pf"; expdp "sys/${SYS_PASSWORD}@${SRC_EZCONNECT} as sysdba" parfile="$pf" } exp_tables() { ensure_directory_object "$SRC_EZCONNECT" "src" read -rp "Tables (SCHEMA.TAB,SCHEMA2.TAB2,...): " tabs local pf; pf=$(par_common expdp "exp_tables") echo "tables=${tabs}" >> "$pf" ok "Parfile: $pf"; expdp "sys/${SYS_PASSWORD}@${SRC_EZCONNECT} as sysdba" parfile="$pf" } #------------------------ IMPDP: Menus & actions ------------------------------- imp_full_menu() { while true; do cat <<'EOS' Import FULL (choose content): 1) metadata_only (CONTENT=METADATA_ONLY) 2) full (CONTENT=ALL) 3) Back EOS read -rp "Choose: " c case "$c" in 1) ensure_directory_object "$TGT_EZCONNECT" "tgt" local pf; pf=$(par_common impdp "imp_full_meta") { echo "full=Y"; echo "content=METADATA_ONLY"; } >> "$pf" ok "Parfile: $pf"; impdp "sys/${SYS_PASSWORD}@${TGT_EZCONNECT} as sysdba" parfile="$pf" ;; 2) ensure_directory_object "$TGT_EZCONNECT" "tgt" local pf; pf=$(par_common impdp "imp_full_all") { echo "full=Y"; echo "content=ALL"; } >> "$pf" ok "Parfile: $pf"; impdp "sys/${SYS_PASSWORD}@${TGT_EZCONNECT} as sysdba" parfile="$pf" ;; 3) break ;; *) warn "Invalid choice" ;; esac done } imp_schemas_menu() { while true; do cat <<'EOS' Import SCHEMAS: 1) All accounts (exclude Oracle-maintained; honors SKIP_SCHEMAS) 2) User input or value from conf (SCHEMAS_LIST_IMP / SCHEMAS_LIST_EXP) with confirmation 3) Back EOS read -rp "Choose: " c case "$c" in 1) ensure_directory_object "$TGT_EZCONNECT" "tgt" local schemas; schemas="$(get_nonmaintained_schemas)" schemas="$(confirm_edit_value "Schemas" "$schemas")" local pf; pf=$(par_common impdp "imp_schemas_auto") { echo "schemas=${schemas}"; echo "content=ALL"; } >> "$pf" ok "Parfile: $pf"; impdp "sys/${SYS_PASSWORD}@${TGT_EZCONNECT} as sysdba" parfile="$pf" ;; 2) ensure_directory_object "$TGT_EZCONNECT" "tgt" local base="${SCHEMAS_LIST_IMP:-${SCHEMAS_LIST_EXP:-}}" [[ -z "$base" ]] && read -rp "Enter schemas (comma-separated): " base local schemas; schemas="$(confirm_edit_value "Schemas" "$base")" local pf; pf=$(par_common impdp "imp_schemas_user") { echo "schemas=${schemas}"; echo "content=ALL"; } >> "$pf" ok "Parfile: $pf"; impdp "sys/${SYS_PASSWORD}@${TGT_EZCONNECT} as sysdba" parfile="$pf" ;; 3) break ;; *) warn "Invalid choice" ;; esac done } imp_tablespaces() { ensure_directory_object "$TGT_EZCONNECT" "tgt" read -rp "Transported tablespaces (comma-separated): " tbs local pf; pf=$(par_common impdp "imp_tbs") echo "transport_tablespaces=${tbs}" >> "$pf" ok "Parfile: $pf"; impdp "sys/${SYS_PASSWORD}@${TGT_EZCONNECT} as sysdba" parfile="$pf" } imp_tables() { ensure_directory_object "$TGT_EZCONNECT" "tgt" read -rp "Tables (SCHEMA.TAB,SCHEMA2.TAB2,...): " tabs local pf; pf=$(par_common impdp "imp_tables") echo "tables=${tabs}" >> "$pf" ok "Parfile: $pf"; impdp "sys/${SYS_PASSWORD}@${TGT_EZCONNECT} as sysdba" parfile="$pf" } #------------------------ IMPORT CLEANUP (Drop/Reset) -------------------------- # All options now generate dry-run reports; if DRY_RUN_ONLY=Y, execution is skipped. drop_users_cascade_all_nonmaint() { local users_csv; users_csv="$(get_nonmaintained_schemas_tgt)" local inlist; inlist="$(csv_to_inlist "$users_csv")" report_user_list "dry_users_all" "$inlist" report_object_counts "dry_objs_all" "$inlist" if [[ "${DRY_RUN_ONLY^^}" == "Y" ]]; then warn "DRY_RUN_ONLY=Y: Skipping DROP USER execution." return fi local confirm echo "This will DROP ALL above users (CASCADE) on TARGET: ${TGT_EZCONNECT}" read -rp "Type YES to proceed (anything else cancels): " confirm [[ "$confirm" == "YES" ]] || { warn "Cancelled."; return; } run_sql "$TGT_EZCONNECT" "drop_users_all" " SET SERVEROUTPUT ON DECLARE v_stmt VARCHAR2(4000); BEGIN FOR r IN (SELECT username FROM dba_users WHERE oracle_maintained='N' AND UPPER(username) IN (${inlist}) ORDER BY username) LOOP v_stmt := 'DROP USER '||r.username||' CASCADE'; BEGIN EXECUTE IMMEDIATE v_stmt; DBMS_OUTPUT.PUT_LINE('DROPPED USER: '||r.username); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('FAILED: '||v_stmt||' - '||SQLERRM); END; END LOOP; END; / " } drop_objects_all_nonmaint() { local users_csv; users_csv="$(get_nonmaintained_schemas_tgt)" local inlist; inlist="$(csv_to_inlist "$users_csv")" report_user_list "dry_users_obj_all" "$inlist" report_object_counts "dry_objs_obj_all" "$inlist" if [[ "${DRY_RUN_ONLY^^}" == "Y" ]]; then warn "DRY_RUN_ONLY=Y: Skipping DROP OBJECTS execution." return fi local confirm echo "This will DROP ALL OBJECTS for the above owners on TARGET (users remain)." read -rp "Type YES to proceed (anything else cancels): " confirm [[ "$confirm" == "YES" ]] || { warn "Cancelled."; return; } run_sql "$TGT_EZCONNECT" "drop_objs_all" " SET SERVEROUTPUT ON DECLARE PROCEDURE exec_ddl(p_sql IN VARCHAR2) IS BEGIN EXECUTE IMMEDIATE p_sql; EXCEPTION WHEN OTHERS THEN NULL; END; PROCEDURE drop_for_owner(p_owner IN VARCHAR2) IS BEGIN FOR r IN (SELECT object_name FROM dba_objects WHERE owner=p_owner AND object_type='SYNONYM') LOOP exec_ddl('DROP SYNONYM '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT mview_name object_name FROM dba_mviews WHERE owner=p_owner) LOOP exec_ddl('DROP MATERIALIZED VIEW '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT view_name object_name FROM dba_views WHERE owner=p_owner) LOOP exec_ddl('DROP VIEW '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT trigger_name object_name FROM dba_triggers WHERE table_owner=p_owner) LOOP exec_ddl('DROP TRIGGER '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT object_name FROM dba_objects WHERE owner=p_owner AND object_type='PACKAGE BODY') LOOP exec_ddl('DROP PACKAGE BODY '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT object_name FROM dba_objects WHERE owner=p_owner AND object_type='PACKAGE') LOOP exec_ddl('DROP PACKAGE '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT procedure_name object_name FROM dba_procedures WHERE owner=p_owner AND object_type='PROCEDURE') LOOP exec_ddl('DROP PROCEDURE '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT object_name FROM dba_objects WHERE owner=p_owner AND object_type='FUNCTION') LOOP exec_ddl('DROP FUNCTION '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT type_name object_name FROM dba_types WHERE owner=p_owner) LOOP exec_ddl('DROP TYPE '||p_owner||'.\"'||r.object_name||'\" FORCE'); END LOOP; FOR r IN (SELECT sequence_name object_name FROM dba_sequences WHERE sequence_owner=p_owner) LOOP exec_ddl('DROP SEQUENCE '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT index_name object_name FROM dba_indexes WHERE owner=p_owner AND table_owner=p_owner) LOOP exec_ddl('DROP INDEX '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT table_name object_name FROM dba_tables WHERE owner=p_owner) LOOP exec_ddl('DROP TABLE '||p_owner||'.\"'||r.object_name||'\" CASCADE CONSTRAINTS PURGE'); END LOOP; END; BEGIN FOR u IN (SELECT username FROM dba_users WHERE oracle_maintained='N' AND UPPER(username) IN (${inlist}) ORDER BY username) LOOP DBMS_OUTPUT.PUT_LINE('Dropping objects for '||u.username); drop_for_owner(u.username); END LOOP; END; / " } drop_users_cascade_listed() { local base="${SCHEMAS_LIST_IMP:-${SCHEMAS_LIST_EXP:-}}" [[ -z "$base" ]] && read -rp "Enter schemas to DROP (comma-separated): " base local schemas; schemas="$(confirm_edit_value "Drop these users" "$base")" local inlist; inlist="$(csv_to_inlist "$schemas")" report_user_list "dry_users_listed" "$inlist" report_object_counts "dry_objs_listed" "$inlist" if [[ "${DRY_RUN_ONLY^^}" == "Y" ]]; then warn "DRY_RUN_ONLY=Y: Skipping DROP USER execution." return fi local confirm echo "This will DROP these users CASCADE on TARGET: ${schemas}" read -rp "Type YES to proceed (anything else cancels): " confirm [[ "$confirm" == "YES" ]] || { warn "Cancelled."; return; } run_sql "$TGT_EZCONNECT" "drop_users_listed" " SET SERVEROUTPUT ON DECLARE v_stmt VARCHAR2(4000); BEGIN FOR r IN ( SELECT username FROM dba_users WHERE oracle_maintained='N' AND UPPER(username) IN (${inlist}) ORDER BY username ) LOOP v_stmt := 'DROP USER '||r.username||' CASCADE'; BEGIN EXECUTE IMMEDIATE v_stmt; DBMS_OUTPUT.PUT_LINE('DROPPED USER: '||r.username); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('FAILED: '||v_stmt||' - '||SQLERRM); END; END LOOP; END; / " } drop_objects_listed() { local base="${SCHEMAS_LIST_IMP:-${SCHEMAS_LIST_EXP:-}}" [[ -z "$base" ]] && read -rp "Enter schemas (owners) to purge objects for (comma-separated): " base local owners; owners="$(confirm_edit_value "Purge objects for owners" "$base")" local inlist; inlist="$(csv_to_inlist "$owners")" report_user_list "dry_users_obj_listed" "$inlist" report_object_counts "dry_objs_obj_listed" "$inlist" if [[ "${DRY_RUN_ONLY^^}" == "Y" ]]; then warn "DRY_RUN_ONLY=Y: Skipping DROP OBJECTS execution." return fi local confirm echo "This will DROP ALL OBJECTS for these owners on TARGET (users remain): ${owners}" read -rp "Type YES to proceed (anything else cancels): " confirm [[ "$confirm" == "YES" ]] || { warn "Cancelled."; return; } run_sql "$TGT_EZCONNECT" "drop_objs_listed" " SET SERVEROUTPUT ON DECLARE PROCEDURE exec_ddl(p_sql IN VARCHAR2) IS BEGIN EXECUTE IMMEDIATE p_sql; EXCEPTION WHEN OTHERS THEN NULL; END; PROCEDURE drop_for_owner(p_owner IN VARCHAR2) IS BEGIN FOR r IN (SELECT object_name FROM dba_objects WHERE owner=p_owner AND object_type='SYNONYM') LOOP exec_ddl('DROP SYNONYM '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT mview_name object_name FROM dba_mviews WHERE owner=p_owner) LOOP exec_ddl('DROP MATERIALIZED VIEW '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT view_name object_name FROM dba_views WHERE owner=p_owner) LOOP exec_ddl('DROP VIEW '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT trigger_name object_name FROM dba_triggers WHERE table_owner=p_owner) LOOP exec_ddl('DROP TRIGGER '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT object_name FROM dba_objects WHERE owner=p_owner AND object_type='PACKAGE BODY') LOOP exec_ddl('DROP PACKAGE BODY '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT object_name FROM dba_objects WHERE owner=p_owner AND object_type='PACKAGE') LOOP exec_ddl('DROP PACKAGE '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT procedure_name object_name FROM dba_procedures WHERE owner=p_owner AND object_type='PROCEDURE') LOOP exec_ddl('DROP PROCEDURE '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT object_name FROM dba_objects WHERE owner=p_owner AND object_type='FUNCTION') LOOP exec_ddl('DROP FUNCTION '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT type_name object_name FROM dba_types WHERE owner=p_owner) LOOP exec_ddl('DROP TYPE '||p_owner||'.\"'||r.object_name||'\" FORCE'); END LOOP; FOR r IN (SELECT sequence_name object_name FROM dba_sequences WHERE sequence_owner=p_owner) LOOP exec_ddl('DROP SEQUENCE '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT index_name object_name FROM dba_indexes WHERE owner=p_owner AND table_owner=p_owner) LOOP exec_ddl('DROP INDEX '||p_owner||'.\"'||r.object_name||'\"'); END LOOP; FOR r IN (SELECT table_name object_name FROM dba_tables WHERE owner=p_owner) LOOP exec_ddl('DROP TABLE '||p_owner||'.\"'||r.object_name||'\" CASCADE CONSTRAINTS PURGE'); END LOOP; END; BEGIN FOR u IN (SELECT username FROM dba_users WHERE oracle_maintained='N' AND UPPER(username) IN (${inlist}) ORDER BY username) LOOP DBMS_OUTPUT.PUT_LINE('Dropping objects for '||u.username); drop_for_owner(u.username); END LOOP; END; / " } import_cleanup_menu() { while true; do cat <"$out" 2>"${out}.log" ${ddl_sql_prolog} ${body} EXIT SQL if grep -qi "ORA-" "${out}.log"; then err "DDL extract error (see ${out}.log) for $(basename "$out")" tail -n 120 "${out}.log" | mask_pwd | sed 's/^/ /' exit 1 fi ok "Wrote: $out" } ddl_users() { local f="${DDL_DIR}/01_users_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''USER'', username) FROM dba_users WHERE oracle_maintained = ''N'' ORDER BY username;"; } ddl_profiles() { local f="${DDL_DIR}/02_profiles_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''PROFILE'', profile) FROM (SELECT DISTINCT profile FROM dba_profiles ORDER BY 1);"; } ddl_roles() { local f="${DDL_DIR}/03_roles_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''ROLE'', role) FROM dba_roles WHERE NVL(oracle_maintained, 'N') = 'N' ORDER BY role;"; } ddl_privs_to_roles() { local f="${DDL_DIR}/04_role_privs_${RUN_ID}.sql"; ddl_spool "$f" " WITH roles AS (SELECT role FROM dba_roles WHERE NVL(oracle_maintained,'N')='N') SELECT DBMS_METADATA.GET_GRANTED_DDL(''SYSTEM_GRANT'', role) FROM roles UNION ALL SELECT DBMS_METADATA.GET_GRANTED_DDL(''OBJECT_GRANT'', role) FROM roles;"; } ddl_sysprivs_to_users() { local f="${DDL_DIR}/05_user_sysprivs_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_GRANTED_DDL(''SYSTEM_GRANT'', username) FROM dba_users WHERE oracle_maintained='N' ORDER BY username;"; } ddl_sequences_all_users() { local f="${DDL_DIR}/06_sequences_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''SEQUENCE'', sequence_name, owner) FROM dba_sequences WHERE owner IN (SELECT username FROM dba_users WHERE oracle_maintained='N') ORDER BY owner, sequence_name;"; } ddl_public_synonyms() { local f="${DDL_DIR}/07_public_synonyms_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''SYNONYM'', synonym_name, ''PUBLIC'') FROM dba_synonyms WHERE owner = ''PUBLIC'' ORDER BY synonym_name;"; } ddl_private_synonyms_all_users() { local f="${DDL_DIR}/08_private_synonyms_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''SYNONYM'', synonym_name, owner) FROM dba_synonyms WHERE owner <> ''PUBLIC'' AND owner IN (SELECT username FROM dba_users WHERE oracle_maintained='N') ORDER BY owner, synonym_name;"; } ddl_all_ddls_all_users() { local f="${DDL_DIR}/09_all_ddls_${RUN_ID}.sql" local types_clause; types_clause="$(echo "$DDL_OBJECT_TYPES" | awk -F',' '{for(i=1;i<=NF;i++){gsub(/^[ \t]+|[ \t]+$/, "", $i); printf("%s''%s''", (i>1?",":""), $i)} }')" ddl_spool "$f" " WITH owners AS ( SELECT username AS owner FROM dba_users WHERE oracle_maintained='N' ), objs AS ( SELECT owner, object_type, object_name FROM dba_objects WHERE owner IN (SELECT owner FROM owners) AND object_type IN (${types_clause}) AND object_name NOT LIKE 'BIN$%%' AND temporary = 'N' ) SELECT DBMS_METADATA.GET_DDL(object_type, object_name, owner) FROM objs ORDER BY owner, object_type, object_name;"; } ddl_tablespaces() { local f="${DDL_DIR}/10_tablespaces_${RUN_ID}.sql" local skip_clause; skip_clause="$(echo "$SKIP_TABLESPACES" | awk -F',' '{for(i=1;i<=NF;i++){gsub(/^[ \t]+|[ \t]+$/, "", $i); printf("%s''%s''", (i>1?",":""), toupper($i))} }')" ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''TABLESPACE'', tablespace_name) FROM dba_tablespaces WHERE UPPER(tablespace_name) NOT IN (${skip_clause}) ORDER BY tablespace_name;"; } ddl_role_grants_to_users() { local f="${DDL_DIR}/11_role_grants_to_users_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_GRANTED_DDL(''ROLE_GRANT'', grantee) FROM (SELECT username AS grantee FROM dba_users WHERE oracle_maintained='N') ORDER BY 1;"; } ddl_default_roles() { local f="${DDL_DIR}/12_default_roles_${RUN_ID}.sql"; ddl_spool "$f" " WITH u AS (SELECT username FROM dba_users WHERE oracle_maintained='N'), r AS ( SELECT grantee AS username, LISTAGG(role, ',') WITHIN GROUP (ORDER BY role) AS roles FROM dba_role_privs WHERE default_role = 'YES' AND grantee IN (SELECT username FROM u) GROUP BY grantee ) SELECT 'ALTER USER '||username||' DEFAULT ROLE '|| CASE WHEN roles IS NULL THEN 'ALL' ELSE roles END || ';' FROM u LEFT JOIN r USING (username) ORDER BY username;"; } ddl_directories() { local f="${DDL_DIR}/13_directories_${RUN_ID}.sql"; ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''DIRECTORY'', directory_name) FROM (SELECT DISTINCT directory_name FROM dba_directories ORDER BY 1);"; } ddl_db_links_by_owner() { read -rp "Enter owner for DB links (schema name): " owner local f="${DDL_DIR}/14_db_links_${owner^^}_${RUN_ID}.sql" ddl_spool "$f" " SELECT DBMS_METADATA.GET_DDL(''DB_LINK'', db_link, owner) FROM dba_db_links WHERE owner = UPPER('${owner}') ORDER BY db_link;" warn "Note: DB link passwords may be masked/omitted depending on version/security." } ddl_menu() { while true; do cat <<'EOS' DDL Extraction (Source DB): 1) USERS (exclude Oracle-maintained) 2) PROFILES 3) ROLES (exclude Oracle-maintained) 4) PRIVILEGES -> ROLES 5) SYSTEM PRIVILEGES -> USERS (exclude Oracle-maintained) 6) SEQUENCES for USERS (exclude Oracle-maintained) 7) PUBLIC SYNONYMS 8) PRIVATE SYNONYMS for USERS (exclude Oracle-maintained) 9) ALL OBJECT DDLs for USERS (exclude Oracle-maintained) [heavy] 10) TABLESPACE DDLs (skip system/temp/undo) 11) ROLE GRANTS -> USERS 12) DEFAULT ROLES per USER (ALTER USER DEFAULT ROLE ...) 13) DIRECTORY OBJECTS 14) DB LINKS by OWNER (prompt) B) Back EOS read -rp "Choose: " c case "${c^^}" in 1) ddl_users ;; 2) ddl_profiles ;; 3) ddl_roles ;; 4) ddl_privs_to_roles ;; 5) ddl_sysprivs_to_users ;; 6) ddl_sequences_all_users ;; 7) ddl_public_synonyms ;; 8) ddl_private_synonyms_all_users ;; 9) ddl_all_ddls_all_users ;; 10) ddl_tablespaces ;; 11) ddl_role_grants_to_users ;; 12) ddl_default_roles ;; 13) ddl_directories ;; 14) ddl_db_links_by_owner ;; B) break ;; *) warn "Invalid choice" ;; esac done } #------------------------ Menus (Export/Import/Main) --------------------------- export_menu() { while true; do cat <<'EOS' Export Menu: 1) FULL database (submenu: metadata_only / full) 2) SCHEMAS (submenu: all non-maintained / user|conf) 3) TABLESPACES (transport) 4) TABLES 5) Back EOS read -rp "Choose: " c case "$c" in 1) exp_full_menu ;; 2) exp_schemas_menu ;; 3) exp_tablespaces ;; 4) exp_tables ;; 5) break ;; *) warn "Invalid choice" ;; esac done } import_menu() { while true; do cat < submenu (with preview/dry-run) 6) Back EOS read -rp "Choose: " c case "$c" in 1) imp_full_menu ;; 2) imp_schemas_menu ;; 3) imp_tablespaces ;; 4) imp_tables ;; 5) import_cleanup_menu ;; 6) break ;; *) warn "Invalid choice" ;; esac done } main_menu() { while true; do cat < sub menu 3) Import (Data Pump) -> sub menu 4) Monitor/Status -> DBA_DATAPUMP_JOBS + tail logs 5) Drop DIRECTORY objects -> cleanup 6) DDL Extraction (Source DB) -> sub menu 7) Quit EOS read -rp "Choose: " choice case "$choice" in 1) ensure_directory_object "$SRC_EZCONNECT" "src"; ensure_directory_object "$TGT_EZCONNECT" "tgt"; ok "DIRECTORY ${COMMON_DIR_NAME} ready on both." ;; 2) export_menu ;; 3) import_menu ;; 4) show_jobs ;; 5) cleanup_dirs ;; 6) ddl_menu ;; 7) ok "Bye."; exit 0 ;; *) warn "Invalid choice." ;; esac done } main_menu