How To: Custom Backup and Restore using Oracle Flashback Database for PDB

Overview

The steps below perform a backup and restore of the REF database using Oracle's flashback technology for a Pluggable Database (PDB).

The functionality performs the following:

  • Backup
    1. deletes any existing PDB restore point called DATICAL_BACKUP
    2. creates a new PDB restore point called DATICAL_BACKUP
  • Restore
    1. closes the pluggable database
    2. flashback pluggable database to restore point DATICAL_BACKUP
    3. opens pluggable database
    4. built-in WAIT period (to ensure flashback is complete)
    5. drops the restore point called DATICAL_BACKUP

Step 1 Configure the Database

These are the Oracle Database prerequisites required for flashback database.

  • The recovery file destination and size are required for flashback. Database must be restarted after applying these changes.
  • Enabling archivelog must be done in "mount" mode, hence "startup mount"
  • Enabling archivelog is required for flashback
  • Enabling flashback must be done at CDB level
  • Creating the user to perform flashback must be done at the CDB level
  • All CDB level users require the username to begin with C##
  • Grant of SYSBACKUP must be done BOTH in CDB and in PDB
  • This configuration must be in place prior to creating the first restore point.


  1.  CDB Setup: run one-time as the oracle user. 

    SAMPLE CODE ONLY

    setup db for flashback
    $>export ORACLE_HOME=/opt/oracle/product/12.2.0.1/dbhome_1/
    $>export ORACLE_SID=ORCLCDB
    $>sqlplus / as sysdba
    
    SQL>alter system set db_recovery_file_dest='/opt/oracle/product/12.2.0.1/dbhome_1/dbs/flashback' scope=spfile;
    SQL>alter system set db_recovery_file_dest_size=10g scope=spfile;
    
    SQL>shutdown immediate;
    SQL>startup mount;
    SQL>alter database archivelog;
    SQL>alter database flashback on;
    SQL>alter database open;
    
    SQL>CREATE USER C##BBB_BACKUP_USER IDENTIFIED BY BBB_BACKUP_USER_PW;
    SQL>GRANT CREATE SESSION to C##BBB_BACKUP_USER;
    SQL>GRANT SYSBACKUP to C##BBB_BACKUP_USER;
    SQL>ALTER SESSION SET CONTAINER=BUCKET_02;
    SQL>GRANT SYSBACKUP to C##BBB_BACKUP_USER;
    
    SQL>exit;

Step 2 Configure Custom Backup/Restore

Reference this documentation: Custom Backup and Restore Methods

Notes

  • The convert packageMethod should not be used for the ddl folder(s).
    • Projects should use SqlParser
    • DDL folder(s) should be setup with ddl_direct packageMethod
  • There is a built-in WAIT to ensure the flashback operation completes before dropping the restore point
    • To increate/decrease the WAIT, update the value: int dropRestoreWaitTime = 30000


The below scripts will execute Oracle commands via sqlplus to create a restore point and flashback the database in the event of errors.

Instructions

  1. Place the below scripts in a folder in the DDB repo called BackupRestoreScripts
  2. Update the deployPackager.properties file to include:
    1. deployPackager.properties
      databaseBackupRestoreMethod=CustomBackupRestoreOraclePDBFlashback
      extensionsPath=./BackupRestoreScripts
      databaseBackupMode=always
    2. If you wish to share the scripts for all Oracle projects, place the scripts in the <DaticalDB install>/repl/scripts/extensions folder instead of a BackupRestoreScripts folder at the project level. In this case, do not include the extensionsPath line in the deployPackager.properties.
  3. Set the permissions on the .sh and .bat files to be executable (permissions should be 765).

    1. For Git you can use these commands run from inside the BackupRestoreScripts folder. Please note scripts must already be committed to git and then re-committed after running below:

      Git Permissions
      git update-index --chmod=+x *.sh
      git update-index --chmod=+x *.bat

CustomBackupRestoreOraclePDBFlashback.groovy

CustomBackupRestoreOraclePDBFlashback.groovy
package com.datical.hammer.scripts.backup
  
import com.datical.db.project.DatabaseDef
import com.datical.db.project.Project
import com.datical.hammer.repl.command.DaticalDBHelper
import com.datical.hammer.scripts.util.PackagerProperties
import com.datical.hammer.scripts.util.ScriptLogging
import com.datical.hammer.scripts.util.ScriptUtils
import groovy.sql.Sql
import org.apache.commons.lang3.StringUtils
  
import java.sql.Connection
import java.text.SimpleDateFormat
import java.util.regex.Pattern
  
/*********************************************************************************************************************
 * Has methods to perform Oracle database backup and restore using Guaranteed Restore points
 * and flashback database.
 * SQLplus must be available on the OS Path. The database must be in archivelog mode, and the database account used to
 * connect to the database must have SYSDBA privileges.
 * SQL operations are performed by shell scripts. The connect string is passed as a parameter to the shell scripts.
 *
 ********************************************************************************************************************/
class CustomBackupRestoreOraclePDBFlashback implements BackupRestore {
  private ScriptLogging logger = new ScriptLogging('CustomBackupRestoreOraclePDBFlashback')
  private ScriptUtils scriptUtils = new ScriptUtils()
  private DaticalDBHelper daticalDB
  String defaultExportFilename
  String defaultExportLogFilename
  String jobNameBase
    
  Boolean windowsOS = false;
  
  CustomBackupRestoreOraclePDBFlashback(DaticalDBHelper daticalDB) {
    this.daticalDB = daticalDB
    def startTime = new Date()
    def timestamp            = new SimpleDateFormat("yyyyMMdd_HHmmss_S").format(startTime)
    defaultExportFilename    = ""
    defaultExportLogFilename = ""
    def jobnameTimestamp     = new SimpleDateFormat("yyyyMMdd_HHmmss").format(startTime)
    // The jobNameBase cannot be longer than 30 characters on Oracle
    jobNameBase              = "datical_pkg_${jobnameTimestamp}"
      
    // If on Windows...
    if (File.separator == "\\") {
      logger.printInfo "Detected Windows operating system"
      windowsOS = true;
    } else {
      logger.printInfo "Detected Non-Windows operating system"
    }
    logger.debug "  using CustomBackupRestoreOracleFlashback"
  }
  
  
  /***************************************************************************************************************
   * Create a Guaranteed Restore Point in dbDef. The restore point is named DATICAL_BACKUP.  It is dropped upon successful flashback.
   * Returns a list of [Integer, StringBuffer, StringBuffer], where the Integer is a return code (0 is success, non-zero is an error),
   * the first StringBuffer contains the stderr from any external process, and the second StringBuffer contains the stdout from any external process.
   *
   * @param dbDef               A DatabaseDef object that can be used to create a connection to a database server.
   *                            The dbDef will be automatically constructed based on the name supplied by the
   *                            user on the command line.
   * @param schemaList          NOT USED.  A List of Strings, where each String is the name of a schema to backup. This list
   *                            is created by examining the datical.project file, or can be supplied by the user
   *                            on the command line. It may also be calculated by deploy packager from entries in
   *                            various metadata.properties files.
   * @param filename            NOT USED.  This is a String used to name the backup file. This value is calculated in the
   *                            method getFilename(),
   * @return                    A List of [Integer, StringBuffer, StringBuffer]
   * *************************************************************************************************************/
  @Override
  List runBackup(DatabaseDef dbDef, List<String> schemaList, String filename) {
    def rc
    def stdout = new StringBuffer()
    def stderr = new StringBuffer()
    logger.printInfo "Running Backup using CustomBackupRestoreOracleFlashback"
  
    try {
      
      ProcessBuilder processBuilder = new ProcessBuilder("")
      def commands = processBuilder.command()
      commands.clear()
        
      String connectString
      String printableConnectString
        
      logger.printInfo "Connectivity based on URL ${dbDef.url}"
        
      if (StringUtils.isNotBlank(dbDef.tnsName)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\"@${dbDef.tnsName}"
         printableConnectString = "${dbDef.username}/\"***********************\"@${dbDef.tnsName}"
      }
      else if (StringUtils.isNotBlank(dbDef.sid)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\":@${dbDef.hostname}:${dbDef.port}:${dbDef.sid}"
         printableConnectString = "${dbDef.username}/\"***********************\":@${dbDef.hostname}:${dbDef.port}:${dbDef.sid}"
      }
      else if (StringUtils.isNotBlank(dbDef.serviceName)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\"@${dbDef.hostname}:${dbDef.port}/${dbDef.serviceName}"
         printableConnectString = "${dbDef.username}/\"***********************\"@${dbDef.hostname}:${dbDef.port}/${dbDef.serviceName}"
      } else {
         logger.printError "No TNSName, SID, or ServiceName was specified in the connection (${dbDef.name})."
         return [1,null,null]
      }
        
      String[] restorepointOptions = ["", ""];
      String restorepointCommand = "BackupRestoreScripts/datical_custom_create_restore_point.sh"
      if (windowsOS) {
        restorepointCommand = "BackupRestoreScripts\\datical_custom_create_restore_point.bat"
      }
        
      commands.add(restorepointCommand.toString())
      commands.add(connectString.toString())
	  commands.add(dbDef.serviceName.toString())
        
      restorepointOptions.eachWithIndex { item, index -> commands.add(item.toString()) }
  
      logger.printInfo "executing ${restorepointCommand} command:\n " + "${restorepointCommand} ${printableConnectString} ${dbDef.serviceName} ${restorepointOptions.join(" ")}"
  
      Process proc = processBuilder.start()
      proc.waitForProcessOutput(stdout, stderr)
      rc = proc.exitValue()
        
    }
    catch (Exception ex) {
      stderr = ex
      rc = 1
    }
  
    def message = """
       rc    : ${rc}
       stderr: ${stderr}
       stdout: ${stdout}
----------------------------- end of CREATE_RESTORE_POINT output ---------------------------------
"""
  
    if (rc != 0) {
      logger.printError message
    }
    else {
      logger.printInfo stdout.toString()
      logger.printInfo "  Successfully created restore point for '${dbDef.name}'"
    }
    return [rc,stderr,stdout]
  }
  
  
  /****************************************************************************************
   * Restore the database specified by the dbDef using flashback database.
   * Returns a list of [Integer, StringBuffer, StringBuffer], where the Integer is a return code
   * (0 is success, non-zero is an error), the first StringBuffer contains the stderr from any external process,
   * and the second StringBuffer contains the stdout from any external process.
   *
   * @param dbDef               A DatabaseDef object that can be used to create a connection to a database server.
   *                            The dbDef will be automatically constructed based on the name supplied by the
   *                            user on the command line.
   * @param schemaList          NOT USED.  A List of Strings, where each String is the name of a schema to backup. This list
   *                            is created by examining the datical.project file, or can be supplied by the user
   *                            on the command line. It may also be calculated by deploy packager from entries in
   *                            various metadata.properties files.
   * @param filename            NOT USED.  This is a string that should be used to find the file that will be restored. This value
   *                            is specified by the user on the command line with the option 'restore=filename'
   *
   * @return                    A List of [Integer, StringBuffer, StringBuffer]
   *
   * *************************************************************************************************************/
  @Override
  List runRestore(DatabaseDef dbDef, List<String> schemaList, String filename) {
    def rc
    def stdout = new StringBuffer()
    def stderr = new StringBuffer()
    logger.printInfo "Running Restore using CustomBackupRestoreOracleFlashback"
  
    try {
  
      ProcessBuilder processBuilder = new ProcessBuilder("")
      def commands = processBuilder.command()
      commands.clear()
         
      String connectString
      String printableConnectString
        
      logger.printInfo "Connectivity based on URL ${dbDef.url}"
        
      if (StringUtils.isNotBlank(dbDef.tnsName)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\"@${dbDef.tnsName}"
         printableConnectString = "${dbDef.username}/\"***********************\"@${dbDef.tnsName}"
      }
      else if (StringUtils.isNotBlank(dbDef.sid)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\":@${dbDef.hostname}:${dbDef.port}:${dbDef.sid}"
         printableConnectString = "${dbDef.username}/\"***********************\":@${dbDef.hostname}:${dbDef.port}:${dbDef.sid}"
      }
      else if (StringUtils.isNotBlank(dbDef.serviceName)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\"@${dbDef.hostname}:${dbDef.port}/${dbDef.serviceName}"
         printableConnectString = "${dbDef.username}/\"***********************\"@${dbDef.hostname}:${dbDef.port}/${dbDef.serviceName}"
      } else {
         logger.printError "No TNSName, SID, or ServiceName was specified in the connection (${dbDef.name})."
         return [1,null,null]
      }
  
      String[] flashbackOptions = ["", ""];
      String flashbackCommand = "BackupRestoreScripts/datical_custom_restore_point_flashback.sh"
      if (windowsOS) {
         flashbackCommand = "BackupRestoreScripts\\datical_custom_restore_point_flashback.bat"
      }
        
      commands.add(flashbackCommand.toString())
      commands.add(connectString.toString())
	  commands.add(dbDef.serviceName.toString())
        
      flashbackOptions.eachWithIndex { item, index -> commands.add(item.toString()) }
  
      //logger.printInfo "executing ${flashbackCommand} command:\n " + "${flashbackCommand} ${printableConnectString} ${dbDef.serviceName} ${flashbackOptions.join(" ")}"
  
      Process proc = processBuilder.start()
      proc.waitForProcessOutput(stdout, stderr)
      rc = proc.exitValue()
        
    }
    catch (Exception ex) {
      stderr = ex
      rc = 1
    }
  
    def message = """
      rc    : ${rc}
      stderr: ${stderr}
      stdout: ${stdout}
----------------------------- end of FLASHBACK output ---------------------------------
"""
  
    if (rc != 0) {
      logger.printError message
    }
    else {
      logger.printInfo stdout.toString()
      logger.printInfo "  Successful flashback for '${dbDef.name}'"
    }
  
    return [rc,stderr,stdout]
  }
  
  
  /***************************************************************************************************************
   * This method is called just after the runRestore method. It is used to Drop the Restore Point.
   *
   * @param project             This will be populated with the Project object that deployPackager/backup/restore
   *                            is currently working with.
   * @param dbDef               A DatabaseDef object that can be used to create a connection to a database server.
   *                            The dbDef will be automatically constructed based on the name supplied by the
   *                            user on the command line.
   *
   * returns an integer. Zero indicates success, non-zero is failure.
   * *************************************************************************************************************/
  @Override
  int postRestore(Project project, DatabaseDef dbDef) {
      
    def rc
    def stdout = new StringBuffer()
    def stderr = new StringBuffer()
      
    // Add a Sleep Component to ensure Flashback is complete
    // Update this variable to increase or decrease the wait time. Value is in milliseconds.
	int dropRestoreWaitTime = 30000
      
    logger.printInfo "\n*****************************************"
    logger.printInfo "sleeping for ${dropRestoreWaitTime} milliseconds..."
    logger.printInfo "*****************************************\n"
    sleep(dropRestoreWaitTime); 
      
    logger.printInfo "Dropping Restore Point using CustomBackupRestoreOracleFlashback"
  
    try {
  
      ProcessBuilder processBuilder = new ProcessBuilder("")
      def commands = processBuilder.command()
      commands.clear()
         
      String connectString
      String printableConnectString
        
      logger.printInfo "Connectivity based on URL ${dbDef.url}"
        
      if (StringUtils.isNotBlank(dbDef.tnsName)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\"@${dbDef.tnsName}"
         printableConnectString = "${dbDef.username}/\"***********************\"@${dbDef.tnsName}"
      }
      else if (StringUtils.isNotBlank(dbDef.sid)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\":@${dbDef.hostname}:${dbDef.port}:${dbDef.sid}"
         printableConnectString = "${dbDef.username}/\"***********************\":@${dbDef.hostname}:${dbDef.port}:${dbDef.sid}"
      }
      else if (StringUtils.isNotBlank(dbDef.serviceName)) {
         connectString          = "${dbDef.username}/\"${dbDef.password}\"@${dbDef.hostname}:${dbDef.port}/${dbDef.serviceName}"
         printableConnectString = "${dbDef.username}/\"***********************\"@${dbDef.hostname}:${dbDef.port}/${dbDef.serviceName}"
      } else {
         logger.printError "No TNSName, SID, or ServiceName was specified in the connection (${dbDef.name})."
         return 1
      }
  
      String[] flashbackOptions = ["", ""];
      String flashbackCommand = "BackupRestoreScripts/datical_custom_drop_restore_point.sh"
      if (windowsOS) {
         flashbackCommand = "BackupRestoreScripts\\datical_custom_drop_restore_point.bat"
      }
        
      commands.add(flashbackCommand.toString())
      commands.add(connectString.toString())
	  commands.add(dbDef.serviceName.toString())
        
      flashbackOptions.eachWithIndex { item, index -> commands.add(item.toString()) }
  
      logger.printInfo "executing ${flashbackCommand} command:\n " + "${flashbackCommand} ${printableConnectString} ${dbDef.serviceName} ${flashbackOptions.join(" ")}"
  
      Process proc = processBuilder.start()
      proc.waitForProcessOutput(stdout, stderr)
      rc = proc.exitValue()
  
    }
    catch (Exception ex) {
      stderr = ex
      rc = 1
    }
  
    def message = """
      rc    : ${rc}
      stderr: ${stderr}
      stdout: ${stdout}
----------------------------- end of FLASHBACK output ---------------------------------
"""
  
    if (rc != 0) {
      logger.printError message
    }
    else {
      logger.printInfo stdout.toString()
      logger.printInfo "  Successful drop of restore point for '${dbDef.name}'"
    }
  
    return rc
  }
  
  /***************************************************************************************************************
   * The DatabaseBackupRestore class calls this method to allow an engine to return the name of the file to be
   * used for backup, restore, and verify operations. This implementation does nothing.  An enhancement could be to look at
   * deployPackager.properties for the property databaseBackupRestoreLocation and use that value for the name
   * of the Guaranteed Restore point.
   *
   * @param project             This will be populated with the Project object that deployPackager/backup/restore
   *                            is currently working with.
   * @param dbDef               A DatabaseDef object that can be used to create a connection to a database server.
   *                            The dbDef will be automatically constructed based on the name supplied by the
   *                            user on the command line.
   * @param forBackup           Not used in this implementation.
   *
   * Returns an empty String.
   * *************************************************************************************************************/
  @Override
  String getFilename(Project project, DatabaseDef dbDef, boolean forBackup) {
    return ""
  }
  
  /***************************************************************************************************************
   * Verify that the backup restore class is able to perform the operations. Returns a list of [Integer, StringBuffer, StringBuffer], where
   * the Integer is a return code (0 is success, non-zero is an error), the first StringBuffer contains the
   * stderr from any external process, and the second StringBuffer contains the stdout from any external process.
   *
   * @param dbDef               A DatabaseDef object that can be used to create a connection to a database server.
   *                            The dbDef will be automatically constructed based on the name supplied by the
   *                            user on the command line.
   * @param forBackup           This will be true if the system is preparing to do a backup. It will be false if
   *                            the system is preparing to do a restore.
   * @param filename            A String that refers to the backup file. The value is specified by the user on the
   *                            command line with the option 'backup=filename' or 'restore=filename'. This name
   *
   * This implementation does nothing.  Could be enhanced to verify if the database restore point exists.
   * *************************************************************************************************************/
  @Override
  List verify(DatabaseDef dbDef, boolean forBackup, String filename) {
  int rc = 0
    StringBuffer messages = new StringBuffer()
  
    return [rc, messages.toString()]
   }
  
  
  /***************************************************************************************************************
   * This method is called just prior to the runRestore method. This implementation does nothing.  There is no need to
   * drop the schemas since we will flashback the entire database.
   *
   * @param project             This will be populated with the Project object that deployPackager/backup/restore
   *                            is currently working with.
   * @param dbDef               A DatabaseDef object that can be used to create a connection to a database server.
   *                            The dbDef will be automatically constructed based on the name supplied by the
   *                            user on the command line.
   * @param schemaList          A List of Strings, where each String is the name of a schema to backup. This list
   *                            is created by examining the datical.project file, or can be supplied by the user
   *                            on the command line. It may also be calculated by deploy packager from entries in
   *                            various metadata.properties files.
   * *************************************************************************************************************/
  @Override
  int preRestore(Project project, DatabaseDef dbDef, List<String> schemaList) {
      logger.printInfo "Skipping schema drop, will flashback the database."
      return 0
  }
  
  
} 

datical_custom_create_restore_point.bat

datical_custom_create_restore_point.bat
@echo off
 
SET CONNECT_STRING=%~1
SET PDB_NAME=%~2
echo "Running datical_custom_create_restore_point.bat for Pluggable Database %PDB_NAME%"
 
sqlplus %CONNECT_STRING% @BackupRestoreScripts\\datical_custom_create_restore_point.sql '%PDB_NAME%'

datical_custom_create_restore_point.sql

datical_custom_create_restore_point.sql
whenever sqlerror continue
drop restore point DATICAL_BACKUP FOR PLUGGABLE DATABASE &1;

whenever sqlerror exit failure
whenever oserror exit oscode

create restore point DATICAL_BACKUP FOR PLUGGABLE DATABASE &1; 
 
set serveroutput on
declare
    flag integer;
    restore_point_check EXCEPTION;
begin
    dbms_output.put_line('Info: Querying Restore Point DATICAL_BACKUP....');
 
    BEGIN
		SELECT count(*) into flag FROM V$RESTORE_POINT WHERE NAME = 'DATICAL_BACKUP';
 
        IF ( flag > 0 ) THEN
            dbms_output.put_line('Successful Restore Point Created');
        ELSE
            raise restore_point_check;
        END IF;
    END;
 
EXCEPTION
    WHEN restore_point_check THEN
        dbms_output.put_line('Error Message: Restore Point Check Failed');
        RAISE_APPLICATION_ERROR (-20000, 'The Restore Point Check Failed');
    WHEN OTHERS THEN
        dbms_output.put_line('Error Message: Other');
        RAISE_APPLICATION_ERROR (-20001, 'Unknown error from Create Restore Point script');
end;
/
 
exit;

datical_custom_create_restore_point.sh (IN PROGRESS)

datical_custom_create_restore_point.sh
#!/bin/bash
CONNECT_STRING=$1
PDB_NAME=$2
echo "Running datical_custom_create_restore_point.sh for Pluggable Database " + $PDB_NAME
sqlplus /nolog << EOF
whenever sqlerror exit failure
whenever oserror exit oscode
 
connect $CONNECT_STRING

whenever sqlerror continue
drop restore point DATICAL_BACKUP FOR PLUGGABLE DATABASE $PDB_NAME;

whenever sqlerror exit failure;
create restore point DATICAL_BACKUP FOR PLUGGABLE DATABASE $PDB_NAME;

set serveroutput on
declare
    flag integer;
    restore_point_check EXCEPTION;
begin
    dbms_output.put_line('Info: Creating Restore Point DATICAL_BACKUP to Restore Database If Needed.');
  
    BEGIN
        SELECT count(*) into flag FROM V$RESTORE_POINT WHERE NAME = 'DATICAL_BACKUP';
  
        IF ( flag > 0 ) THEN
            dbms_output.put_line('Successful Restore Point Created');
        ELSE
            raise restore_point_check;
        END IF;
    END;
  
EXCEPTION
    WHEN restore_point_check THEN
        dbms_output.put_line('Error Message: Restore Point Check Failed');
        RAISE_APPLICATION_ERROR (-20000, 'The Restore Point Check Failed');
    WHEN OTHERS THEN
        dbms_output.put_line('Error Message: Other');
        RAISE_APPLICATION_ERROR (-20001, 'Unknown error from Create Restore Point script');
end;
/
exit;
EOF

datical_custom_restore_point_flashback.bat

datical_custom_restore_point_flashback.bat
@echo off
 
SET CONNECT_STRING=%~1
SET PDB_NAME=%~2
echo "Running datical_custom_restore_point_flashback.bat for Pluggable Database %PDB_NAME%"

echo "Flashback is using CONNECT STRING: sqlplus C##BBB_BACKUP_USER/BBB_BACKUP_USER_PW@localhost:1521/%PDB_NAME% as sysbackup"

echo exit | sqlplus C##BBB_BACKUP_USER/BBB_BACKUP_USER_PW@localhost:1521/%PDB_NAME% as sysbackup @BackupRestoreScripts\\datical_custom_restore_point_flashback.sql '%PDB_NAME%'

datical_custom_restore_point_flashback.sql

datical_custom_restore_point_flashback.sql
whenever sqlerror exit failure
whenever oserror exit oscode

ALTER PLUGGABLE DATABASE &1 CLOSE IMMEDIATE;
flashback pluggable database &1 to restore point DATICAL_BACKUP;
ALTER PLUGGABLE DATABASE &1 OPEN RESETLOGS;

set serveroutput on
declare
    flag integer;
    restore_point_check EXCEPTION;
begin
    dbms_output.put_line('Info: Querying Restore Point DATICAL_BACKUP....');
 
    BEGIN
        SELECT count(*) into flag FROM V$RESTORE_POINT WHERE NAME = 'DATICAL_BACKUP';
 
        IF ( flag > 0 ) THEN
            dbms_output.put_line('Restore point DATICAL_BACKUP was used to restore database');
        ELSE
            raise restore_point_check;
        END IF;
    END;
 
EXCEPTION
    WHEN restore_point_check THEN
        dbms_output.put_line('Error: Restore Point DATICAL_BACKUP Does not Exist to Restore the Database.');
        RAISE_APPLICATION_ERROR (-20000, 'The Restore Point Check Failed');
    WHEN OTHERS THEN
        dbms_output.put_line('Error Message: Other');
        RAISE_APPLICATION_ERROR (-20001, 'Unknown error from Restore Point Flashback script');
end;
/
 
exit;

datical_custom_restore_point_flashback.sh (IN PROGRESS)

datical_custom_restore_point_flashback.sh
#!/bin/bash
CONNECT_STRING=$1
PDB_NAME=$2
echo "Running datical_custom_restore_point_flashback.sh for Pluggable Database " + $PDB_NAME
echo "Flashback is using CONNECT STRING: C##BBB_BACKUP_USER/BBB_BACKUP_USER_PW@localhost:1521/%PDB_NAME% as sysbackup"
sqlplus /nolog << EOF
whenever sqlerror exit failure
whenever oserror exit oscode
connect C##BBB_BACKUP_USER/BBB_BACKUP_USER_PW@localhost:1521/$PDB_NAME as sysbackup

ALTER PLUGGABLE DATABASE $PDB_NAME CLOSE IMMEDIATE;
flashback pluggable database $PDB_NAME to restore point DATICAL_BACKUP;
ALTER PLUGGABLE DATABASE $PDB_NAME OPEN RESETLOGS;

set serveroutput on
declare
    flag integer;
    restore_point_check EXCEPTION;
begin
    dbms_output.put_line('Info: Querying Restore Point DATICAL_BACKUP....');
 
    BEGIN
        SELECT count(*) into flag FROM V$RESTORE_POINT WHERE NAME = 'DATICAL_BACKUP';
 
        IF ( flag > 0 ) THEN
            dbms_output.put_line('Restore point DATICAL_BACKUP was used to restore database');
        ELSE
            raise restore_point_check;
        END IF;
    END;
 
EXCEPTION
    WHEN restore_point_check THEN
        dbms_output.put_line('Error: Restore Point DATICAL_BACKUP Does not Exist to Restore the Database.');
        RAISE_APPLICATION_ERROR (-20000, 'The Restore Point Check Failed');
    WHEN OTHERS THEN
        dbms_output.put_line('Error Message: Other');
        RAISE_APPLICATION_ERROR (-20001, 'Unknown error from Restore Point Flashback script');
end;
/
  
exit;
EOF

datical_custom_drop_restore_point.bat

datical_custom_drop_restore_point.bat
@echo off
 
SET CONNECT_STRING=%~1
SET PDB_NAME=%~2
echo "Running datical_custom_drop_restore_point.bat for Pluggable Database %PDB_NAME%"

sqlplus %CONNECT_STRING% @BackupRestoreScripts\\datical_custom_drop_restore_point.sql '%PDB_NAME%'

datical_custom_drop_restore_point.sql

datical_custom_drop_restore_point.sql
whenever sqlerror exit failure
whenever oserror exit oscode

drop restore point DATICAL_BACKUP FOR PLUGGABLE DATABASE &1;
 
set serveroutput on
declare
    flag integer;
    restore_point_check EXCEPTION;
begin
    dbms_output.put_line('Info: Querying Restore Point DATICAL_BACKUP....');

    BEGIN
        SELECT count(*) into flag FROM V$RESTORE_POINT WHERE NAME = 'DATICAL_BACKUP';
 
        IF ( flag = 0 ) THEN
            dbms_output.put_line('Restore Point Dropped Successfully');
        ELSE
            raise restore_point_check;
        END IF;
    END;
 
EXCEPTION
    WHEN restore_point_check THEN
        dbms_output.put_line('Error: Database Restore Failed, Please Engage DBA Support Team.');
        RAISE_APPLICATION_ERROR (-20000, 'The Restore Point Check Failed');
    WHEN OTHERS THEN
        dbms_output.put_line('Error Message: Other');
        RAISE_APPLICATION_ERROR (-20001, 'Unknown error from Drop Restore Point script');
end;
/
 
exit;

datical_custom_drop_restore_point.sh (IN PROGRESS)

datical_custom_drop_restore_point.sh
#!/bin/bash
CONNECT_STRING=$1
PDB_NAME=$2
echo "Running datical_custom_drop_restore_point.sh for Pluggable Database " + $PDB_NAME
sqlplus /nolog << EOF
whenever sqlerror exit failure
whenever oserror exit oscode
connect $CONNECT_STRING

drop restore point DATICAL_BACKUP FOR PLUGGABLE DATABASE $PDB_NAME;

set serveroutput on
declare
    flag integer;
    restore_point_check EXCEPTION;
begin
    dbms_output.put_line('Info: Querying Restore Point DATICAL_BACKUP....');

    BEGIN
        SELECT count(*) into flag FROM V$RESTORE_POINT WHERE NAME = 'DATICAL_BACKUP';
 
        IF ( flag = 0 ) THEN
            dbms_output.put_line('Restore Point Dropped Successfully');
        ELSE
            raise restore_point_check;
        END IF;
    END;
 
EXCEPTION
    WHEN restore_point_check THEN
        dbms_output.put_line('Error: Database Restore Failed, Please Engage DBA Support Team.');
        RAISE_APPLICATION_ERROR (-20000, 'The Restore Point Check Failed');
    WHEN OTHERS THEN
        dbms_output.put_line('Error Message: Other');
        RAISE_APPLICATION_ERROR (-20001, 'Unknown error from Drop Restore Point script');
end;
/
 
exit;
EOF

Test Harness

There is a test harness that can be used to test out the functionality without running packager. These commands need to be run from the DDB repo:

  • hammer groovy database_backup_restore.groovy <refdb> backup
  • hammer groovy database_backup_restore.groovy <refdb> restore


When using the Test Harness, do not run 2 restore operations back-to-back. The restore operation drops the restore point so you will get an error. 




Copyright © Datical 2012-2020 - Proprietary and Confidential