#!/usr/bin/env python
#
# Copyright (c) Greenplum Inc 2008. All Rights Reserved.
#
from gppylib.mainUtils import addMasterDirectoryOptionForSingleClusterProgram, addStandardLoggingAndHelpOptions, gp, simple_main, \
                              ExceptionNoStackTraceNeeded, ProgramArgumentValidationException, UserAbortedException
import fnmatch
import os
import stat
import sys
from optparse import OptionGroup, OptionValueError, SUPPRESS_HELP
from pygresql import pg

try:
    import gpmfr
    from tempfile import mkstemp

    from gppylib import gplog
    from gppylib import pgconf
    from gppylib import userinput
    from gppylib.commands.base import Command
    from gppylib.commands.gp import Scp
    from gppylib.commands.unix import Ping
    from gppylib.db import dbconn
    from gppylib.gparray import GpArray
    from gppylib.gpparseopts import OptParser, OptChecker
    from gppylib.operations import Operation
    from gppylib.operations.backup_utils import *
    from gppylib.operations.restore import *
    from gppylib.operations.utils import DEFAULT_NUM_WORKERS
    from gppylib.operations.unix import CheckFile, CheckRemoteFile, ListFilesByPattern, ListRemoteFilesByPattern
except ImportError, e:
    sys.exit('Cannot import modules.  Please check that you have sourced greenplum_path.sh.  Detail: ' + str(e))

# MPP-13617
import re
RE1 = re.compile('\\[([^]]+)\\]:(.+)')

WARN_MARK = '<<<<<'
logger = gplog.get_default_logger()

class GpdbRestore(Operation):
    def __init__(self, options, args):
        self.context = Context(options)
        self.options_list = " ".join(sys.argv[1:])
        self.search_for_dbname = options.search_for_dbname
        # only one of -t, -b, -R, and -s can be provided
        count = sum([1 for opt in ['timestamp', 'db_date_dir', 'db_host_path', 'search_for_dbname'] if options.__dict__[opt]])
        if count == 0:
            raise ProgramArgumentValidationException("Must supply one of -t, -b, -R, or -s.")
        elif count > 1:
            raise ProgramArgumentValidationException("Only supply one of -t, -b, -R, or -s.")

        if options.list_tables and not options.timestamp:
            raise ProgramArgumentValidationException("Need to supply -t <timestamp> for -L option")

        if options.list_backup and not options.timestamp:
            raise ProgramArgumentValidationException("Need to supply -t <timestamp> for --list-backup option")

        if options.list_backup and options.drop_db:
            raise ProgramArgumentValidationException("Cannot specify --list-backup and -e together")

        if self.context.batch_default <= 0:
            raise ProgramArgumentValidationException("-B <parallel processes> must be greater than 0")

        if not options.masterDataDirectory:
            options.masterDataDirectory = gp.get_masterdatadir()

        if self.context.restore_stats:
            self.context.no_analyze = True

        if self.context.truncate and not (self.context.restore_tables or self.context.table_file or self.context.restore_schemas):
            raise Exception('--truncate can be specified only with -S, -T, or --table-file option')

        if self.context.truncate and self.context.drop_db:
            raise Exception('Cannot specify --truncate and -e together')

        if self.context.restore_stats == "only" and self.context.drop_db:
            raise Exception('Cannot specify --restore-stats only and -e together')

        if self.context.table_file and self.context.restore_tables:
            raise Exception('Cannot specify -T and --table-file together')

        if self.context.list_backup and self.context.restore_tables:
            raise Exception('Cannot specify -T and --list-backup together')

        if self.context.metadata_only and self.context.no_plan:
            raise Exception('Cannot specify --noplan and -m together.')

        if self.context.metadata_only and self.context.no_analyze:
            raise Exception('Cannot specify --noanalyze and -m together.')

        if self.context.change_schema and len(self.context.change_schema) == 0:
            raise Exception('--change-schema name cannot be empty')

        if self.context.change_schema and not (self.context.restore_tables or self.context.table_file):
            raise Exception('-T or --table-file option must be specified with the --change-schema option')
        if self.context.change_schema and len(self.context.restore_schemas) > 0:
            raise Exception('-S option cannot be used with --change-schema option')

        if self.context.table_file:
            self.context.restore_tables = get_restore_tables_from_table_file(self.context.table_file)

        if self.context.db_host_path:
            # MPP-13617
            m = re.match(RE1, self.context.db_host_path)
            if m:
                self.context.db_host_path = m.groups()
            else:
                self.context.db_host_path = self.context.db_host_path.split(':')

        # NetBackup params
        if self.context.db_host_path and self.context.netbackup_service_host:
            raise Exception('-R is not supported for restore with NetBackup.')

        if self.search_for_dbname and self.context.netbackup_service_host:
            raise Exception('-s is not supported with NetBackup.')

        if self.context.db_date_dir and self.context.netbackup_service_host:
            raise Exception('-b is not supported with NetBackup.')

        if self.context.list_tables and self.context.netbackup_service_host:
            raise Exception('-L is not supported with NetBackup.')

        if (self.context.netbackup_block_size or self.context.netbackup_keyword or self.context.netbackup_policy or self.context.netbackup_schedule) \
                and not self.context.netbackup_service_host:
            raise Exception('The --netbackup-service-host option must be supplied to use NetBackup.')

        if self.context.netbackup_service_host:
            validate_netbackup_params({'Service Hostname':self.context.netbackup_service_host})

        if self.context.redirected_restore_db:
            check_funny_chars_in_names(self.context.redirected_restore_db, is_full_qualified_name = False)

        if self.search_for_dbname:
            check_funny_chars_in_names(self.search_for_dbname, is_full_qualified_name = False)
        # force non-interactive if list_backup
        if options.list_backup:
            self.interactive = False
        else:
            self.interactive = options.interactive

        try:
            if self.context.report_status_dir:
                check_dir_writable(self.context.report_status_dir)
        except Exception as e:
            logger.warning('Directory %s should be writable' % self.context.report_status_dir)
            raise Exception(str(e))

        if self.context.ddboost_storage_unit and not self.context.ddboost:
            raise Exception('Cannot use ddboost storage unit option without specifying --ddboost option.')

        if self.context.ddboost:
            if self.context.netbackup_service_host:
                raise Exception('--ddboost option is not supported with NetBackup')

            if self.context.db_host_path:
                raise ExceptionNoStackTraceNeeded("-R cannot be used with DDBoost parameters.")
            elif self.context.backup_dir:
                raise ExceptionNoStackTraceNeeded('-u cannot be used with DDBoost parameters.')
            dd = gpmfr.DDSystem("local")
            self.context.dump_dir = dd.DDBackupDir
        else:
            self.context.dump_dir = 'db_dumps'

    def execute(self):
        if self.context.ddboost:
            cmdline = 'gpddboost --sync --dir=%s' % self.context.master_datadir
            if self.context.ddboost_storage_unit:
                cmdline += ' --ddboost-storage-unit %s' % self.context.ddboost_storage_unit
            cmd = Command('DDBoost sync', cmdline)
            cmd.run(validateAfter=True)

        self._gather_dump_info()

        if self.context.netbackup_service_host:
            logger.info("Restoring metadata files with NetBackup")
            restore_state_files_with_nbu(self.context)
            restore_file_with_nbu(self.context, "cdatabase")
            if check_file_dumped_with_nbu(self.context, "master_config"):
                restore_config_files_with_nbu(self.context)
            if check_file_dumped_with_nbu(self.context, "global"):
                restore_file_with_nbu(self.context, "global")
            if check_file_dumped_with_nbu(self.context, "stats"):
                restore_file_with_nbu(self.context, "stats")

        if self.context.list_tables and self.context.timestamp:
            return self._list_dump_tables()

        info = self._gather_cluster_info()

        self.validate_tablename_from_filtering_options()
        if self.context.restore_stats == "only":
            restore_type = "Statistics-Only Restore"

        if self.context.metadata_only:
            restore_type = "Metadata-Only Restore"

        elif is_incremental_restore(self.context):
            if len(self.context.restore_tables) > 0:
                restore_type = "Incremental Table Restore"
            else:
                restore_type = "Incremental Restore"
        elif len(self.context.restore_tables) > 0:
            restore_type = "Table Restore"
        else:
            restore_type = "Full Database"

        if self.context.change_schema:
            check_funny_chars_in_names(self.context.change_schema, is_full_qualified_name = False)
            if not self.context.redirected_restore_db and not check_change_schema_exists(self.context, False):
                raise Exception('Cannot restore tables to schema %s: schema does not exist' % self.context.change_schema)
            elif self.context.redirected_restore_db and not check_change_schema_exists(self.context, True):
                raise Exception('Cannot restore tables to the database %s: schema %s does not exist' % (self.context.redirected_restore_db, self.context.change_schema))

        self._output_info(info, restore_type)

        if self.interactive:
            if not userinput.ask_yesno(None, "\nContinue with Greenplum restore", 'N'):
                raise UserAbortedException()

        if self.context.db_host_path:
            host, path = self.context.db_host_path
            RecoverRemoteDumps(self.context, host = host, path = path).run()

            if is_incremental_restore(self.context):
                raise Exception('-R is not supported for restore with incremental timestamp %s' % self.context.timestamp)

        if not is_incremental_restore(self.context) and self.context.list_backup:
            raise Exception('--list-backup is not supported for restore with full timestamps')

        if is_incremental_restore(self.context) and not self.context.no_plan:

            self.context.full_dump_timestamp = get_full_timestamp_for_incremental(self.context)
            if self.context.netbackup_service_host:
                restore_file_with_nbu(self.context, filetype="partition_list")
                restore_file_with_nbu(self.context, filetype="increments",
                                      timestamp=self.context.full_dump_timestamp)

            plan_file = create_restore_plan(self.context)

            if self.context.list_backup and self.context.timestamp:
                return self._list_backup_timestamps(plan_file)

        RestoreDatabase(self.context).run()

        if self.context.no_analyze and not self.context.restore_stats:
            logger.warn('--------------------------------------------------------------------------------------------------')
            logger.warn('Analyze bypassed on request; database performance may be adversely impacted until analyze is done.')
            logger.warn('--------------------------------------------------------------------------------------------------')

        os._exit(0)

    def _output_info(self, info, restore_type):
        logger.info("------------------------------------------")
        logger.info("Greenplum database restore parameters")
        logger.info("------------------------------------------")

        logger.info("Restore type               = %s" % restore_type)

        if len(self.context.restore_tables) > 0:
            logger.info("Database name              = %s" % self.context.target_db)
            logger.info("------------------------------------------")
            logger.info("Table restore list")
            logger.info("------------------------------------------")
            for restore_table in self.context.restore_tables:
                logger.info("Table                      = %s" % restore_table)
            if self.table_counts:
                logger.info("------------------------------------------")
                logger.warn("Following tables have non-zero row counts %s" % WARN_MARK)
                logger.info("------------------------------------------")
                for table, count in self.table_counts:
                    logger.warn("Table:Row count            = %s:%s" % (table, count))
                logger.info("------------------------------------------")
        else:
            logger.info("Database to be restored    = %s" % self.context.target_db)
            if self.context.drop_db:
                logger.info("Drop and re-create db      = On")
            else:
                logger.info("Drop and re-create db      = Off")

        if self.context.timestamp and not self.context.restore_tables:
            logger.info("Restore method             = Restore specific timestamp")
        if self.search_for_dbname and not self.context.restore_tables:
            logger.info("Restore method             = Search for latest")
        if self.context.redirected_restore_db:
            logger.info("Redirect Restore database  = %s" % self.context.redirected_restore_db)
        if self.context.db_date_dir and not self.context.restore_tables:
            logger.info("Restore method             = Restore specific date")
        if len(self.context.restore_tables) > 0:
            logger.info("Restore method             = Specific table restore")
        if self.context.db_host_path:
            host, path = self.context.db_host_path
            logger.info("Restore method             = Remote host")
            logger.info("Recovery hostname          = %s" % host)
            logger.info("Remote recovery path       = %s" % path)
        logger.info("Restore timestamp          = %s" % self.context.timestamp)
        if self.context.compress:
            logger.info("Restore compressed dump    = On")
        else:
            logger.info("Restore compressed dump    = Off")

        """
        TODO: These 3 lines are removed, b/c they pertain only to partial restore.
        #logger.info("DBID List or restore       = %s" % dbid_list)
        #logger.info("Content list for restore   = %s" % content_list)
        #logger.info("Restore type               = %s" % restore_type)
        """

        if self.context.restore_global:
            logger.info("Restore global objects     = On")
        else:
            logger.info("Restore global objects     = Off")
        logger.info("Array has mirrors      = %s" % ("Yes" if info['has_mirrors'] else "No"))
        logger.info("------------------------------------------")

    def _list_dump_tables(self):
        dump_tables = GetDumpTables(self.context).get_dump_tables()

        logger.info("--------------------------------------------------------------------")
        logger.info("List of database tables for dump file with time stamp %s" % self.context.timestamp)
        logger.info("--------------------------------------------------------------------")
        for schema, table, owner in dump_tables:
            logger.info("Table %s.%s Owner %s" % (schema, table, owner))
        logger.info("--------------------------------------------------------------------")

    def _list_backup_timestamps(self, plan_file):
        plan_file_contents = get_lines_from_file(plan_file)
        logger.info("--------------------------------------------------------------------")
        logger.info("List of backup timestamps required for the restore time stamp %s" % self.context.timestamp)
        logger.info("--------------------------------------------------------------------")
        for line in plan_file_contents:
            logger.info("Backup Timestamp: %s" % (line.split(':')[0].strip()))
        logger.info("--------------------------------------------------------------------")

    def _gather_cluster_info(self):
        """
        Checking cluster status, no primary should be down.
        """
        hasMirrors = self.context.gparray.hasMirrors
        primaries = self.context.get_current_primaries()
        fail_count = len([seg for seg in primaries if seg.isSegmentDown()])

        if fail_count != 0:
            logger.fatal("There are %d primary segment databases marked as invalid")
            logger.fatal("Array fault action is set to readonly, unable to initiate a restore")
            logger.info("Use gprecoverseg utility to recover failed segment instances")
            raise ExceptionNoStackTraceNeeded("Unable to continue")

        return {'has_mirrors': hasMirrors,
                'fail_count': fail_count}

    def _gather_dump_info(self):
        """
        Validate the restore timestamp, collect information of the dump path and location.
        """
        if self.context.timestamp:
            self.context.target_db = ValidateTimestamp(self.context).run()
        else:
            if self.context.db_date_dir:
                self._validate_db_date_dir()
            elif self.context.db_host_path:
                self._validate_db_host_path()
            elif self.search_for_dbname:
                self._search_for_latest()

        if not self.context.drop_db and not self.context.redirected_restore_db and (self.context.restore_global != "only"):
            dburl = dbconn.DbURL(port=self.context.master_port)
            conn = dbconn.connect(dburl)
            count = dbconn.execSQLForSingleton(conn, "select count(*) from pg_database where datname='%s';" % escape_string(self.context.target_db, conn))
            # TODO: -e is needed even if the database doesn't exist yet?
            if count == 0:
                raise ExceptionNoStackTraceNeeded("Database %s does not exist and -e option not supplied" % self.context.target_db)

        table_counts = []

        if not self.context.db_host_path and not self.context.ddboost:
            report_filename = self.context.generate_filename("report")
            if not os.path.isfile(report_filename):
                raise ExceptionNoStackTraceNeeded("Report file does not exist for the given restore timestamp %s: '%s'" % (self.context.timestamp, report_filename))

        if not self.context.db_host_path:
            self.context.dump_host = None
        else:
            self.context.dump_host = self.context.db_host_path[0]

        self.table_counts = table_counts

    def validate_tablename_from_filtering_options(self):
        """
        This validates table name format and resolves duplicates of user inputs from any of the gpdbrestore filtering options
        """

        if len(self.context.restore_tables) > 0 or len(self.context.restore_schemas) > 0:
            self.context.restore_tables, self.context.restore_schemas = check_table_name_format_and_duplicate(self.context.restore_tables, self.context.restore_schemas)

        if not self.context.no_validate_table_name and len(self.context.restore_tables) > 0:

            is_remote_service = self.context.ddboost or self.context.netbackup_service_host or self.context.db_host_path
            if not is_remote_service and stat.S_ISFIFO(os.stat(self.context.generate_filename("dump")).st_mode):
                logger.warn('Skipping validation of tables in dump file due to the use of named pipes')
            else:
                dumped_tables = GetDumpTables(self.context).get_dump_tables()
                validate_tablenames_exist_in_dump_file(self.context.restore_tables, dumped_tables)

    def _validate_db_date_dir(self):
        root = self.context.get_date_dir()
        if not os.path.isdir(root):
            raise ExceptionNoStackTraceNeeded("Directory %s does not exist" % root)

        self._get_timestamp_for_validation(root)

        report_filename = self.context.generate_filename("report")
        self.context.compress, self.context.target_db = self.context.get_compress_and_dbname_from_report_file(report_filename)

    def _validate_db_host_path(self):
        # The format of the -R option should be 'hostname:path_to_dumpset', hence the length should be 2 (hostname, path_to_dumpset)
        if len(self.context.db_host_path) != 2:
            raise ProgramArgumentValidationException("The arguments of the -R flag are incorrect. The correct form should be as "
                                                     "follows:\nIPv4_address:path_to_dumpset\n-OR-\n[IPv6_address]:path_to_dumpset\n")

        host, path = self.context.db_host_path
        if self.context.dump_dir not in path:
            raise Exception('Path %s does not contain expected "db_dumps" directory' % path)

        logger.debug("The host is %s" % host)
        logger.debug("The path is %s" % path)
        self.context.backup_dir = path[0:path.index(self.context.dump_dir) - 1]

        Ping.local('Pinging %s' % host, host)

        try:
            CheckRemotePath(path, host).run()
        except:
            raise Exception("Could not find path %s on host %s" % (path, host))
        if path[-1] == '/':
            self.context.db_date_dir = path[-9:-1]
        else:
            self.context.db_date_dir = path[-8:]
        report_pattern = "%s*.rpt" % self.context.generate_prefix("report")
        remote_files = ListRemoteFilesByPattern(path, report_pattern, host).run()
        for file in remote_files:
            Scp('Copying report file', srcHost=host, srcFile="%s/%s" % (path, file), dstFile="/tmp").run(validateAfter=True)

        self._get_timestamp_for_validation("/tmp")
        report_filename = self.context.generate_filename("report", directory="/tmp")
        self.context.compress, self.context.target_db = self.context.get_compress_and_dbname_from_report_file(report_filename)

        local_files = ListFilesByPattern("/tmp", report_pattern).run()
        for file in local_files:
            os.remove("/tmp/%s" % file)

    def _get_timestamp_for_validation(self, root):
        report_prefix = self.context.generate_prefix("report", use_old_format=False)
        matching = ListFilesByPattern(root, "%s*.rpt" % report_prefix).run()
        matching = sorted(matching, key=lambda x: int(x[-18:-4].strip()))
        if len(matching) == 0:
            raise ExceptionNoStackTraceNeeded("Could not locate Master database dump files in %s" % root)
        if len(matching) > 1:
            dates_and_times = []
            for match in matching:
                temp = match[-18:-4]
                date, time = temp[0:8], temp[8:14]
                dates_and_times.append((date, time))
            self.context.timestamp = self._select_multi_file(dates_and_times)
        else:
            match = matching[0]
            self.context.timestamp = match[-18:-4]
        self.context.use_old_filename_format = self.context.is_timestamp_in_old_format()

    def _get_dump_timestamps_for_database(self, search_for_dbname, backup_root):
        timestamps = []
        report_files = self.context.get_report_files_and_paths(backup_root)
        for path, report_file in report_files:
            _, report_file_dbname = self.context.get_compress_and_dbname_from_report_file(os.path.join(path, report_file))
            logger.debug("Looking for %s, found %s" % (search_for_dbname, report_file_dbname))
            if search_for_dbname == report_file_dbname:
                logger.info('Located report file %s for database %s, adding to list' % (report_file, search_for_dbname))
                timestamps.append(int(report_file[-18:-4]))
            else:
                logger.debug("Report file has incorrect database name of %s, skipping..." % report_file_dbname)
        if len(timestamps) == 0:
            raise ExceptionNoStackTraceNeeded("No report files located with database %s" % search_for_dbname)
        return timestamps

    def _search_for_latest(self):
        logger.info("Scanning Master host for latest dump file set for database %s" % self.search_for_dbname)
        backup_root = os.path.join(self.context.get_backup_root(), self.context.dump_dir)
        timestamps = self._get_dump_timestamps_for_database(self.search_for_dbname, backup_root)
        self.context.timestamp = str(max(timestamps))
        logger.info("Identified latest dump timestamp for %s as %s" %  (self.search_for_dbname, self.context.timestamp))

        #Set context variables that we will need for the rest of the restore
        self.context.use_old_filename_format = self.context.is_timestamp_in_old_format()
        report_filename = self.context.generate_filename("report")
        self.context.compress, self.context.target_db = self.context.get_compress_and_dbname_from_report_file(report_filename)

    def _select_multi_file(self, dates_and_times):
        spc = "        "
        def info(msg):
            print "%s%s" % (spc, msg)
        info("Select required dump file timestamp to restore")
        info(" #           Date    Time")
        info("--------------------------")
        for i in range(0, len(dates_and_times)):
            date, time = dates_and_times[i]
            info("[%d] ...... %s %s" % (i, date, time))
        info("Enter timestamp number to restore >")
        choice = raw_input()
        if choice == '':
            raise ExceptionNoStackTraceNeeded("Invalid or null input")
        if not choice.isdigit():
            raise ExceptionNoStackTraceNeeded("Invalid or null input")
        choice = int(choice)
        if choice < 0 or choice >= len(dates_and_times):
            raise ExceptionNoStackTraceNeeded("Invalid or null input")
        date, time = dates_and_times[choice]
        return "%s%s" % (date, time)

def restore_global_callback(option, opt_str, value, parser):
    assert value is None
    if len(parser.rargs) > 0:
        value = parser.rargs[0]
        if value[:1] == "-":
            value = "include"
        else:
            del parser.rargs[:1]

    if value == None:
        value = "include"
    elif value not in ("include", "only"):
        raise OptionValueError('%s is not a valid argument for -G.  Valid arguments are "include" and "only".' % value)
    setattr(parser.values, option.dest, value)

def restore_stats_callback(option, opt_str, value, parser):
    assert value is None
    if len(parser.rargs) > 0:
        value = parser.rargs[0]
        if value[:1] == "-":
            value = "include"
        else:
            del parser.rargs[:1]

    if value == None:
        value = "include"
    elif value not in ("include", "only"):
        raise OptionValueError('%s is not a valid argument for --restore-stats.  Valid arguments are "include" and "only".' % value)
    setattr(parser.values, option.dest, value)

def create_parser():
    parser = OptParser(option_class=OptChecker,
                       version='%prog version $Revision$',
                       description="Restores a Greenplum database or tables from a dumpset generated by gpdbdump")

    parser.setHelp([])                      # TODO: make gpparseopts do this
    addStandardLoggingAndHelpOptions(parser, includeNonInteractiveOption=True)

    addTo = OptionGroup(parser, 'Connection opts')
    parser.add_option_group(addTo)
    addMasterDirectoryOptionForSingleClusterProgram(addTo)

    addTo = OptionGroup(parser, 'Restore options: ')
    addTo.add_option('-t', dest='timestamp', metavar='<timestamp>',
                     help="Timestamp key of backup file set that should be used for restore. Mandatory if -b, -R, -s are not supplied. Expects dumpset to reside on Greenplum array.")
    addTo.add_option('-L', action='store_true', dest='list_tables', default=False,
                     help="List table names in dump file, can only be used with -t <timestamp> option. Will display all tables in dump file, and then exit.")
    addTo.add_option('-b', dest='db_date_dir', metavar='<YYYYMMDD>',
                     help="db_dumps/<YYYYMMDD> directory where dump files are located on the Greenplum array. Mandatory if -t, -s, -R options are not supplied.")
    addTo.add_option('-R', dest='db_host_path', metavar='<hostname:dir_path>',
                     help="Hostname and full directory path where backup set is located. Utility will recover files to master and segment hosts, and then start restore process.")
    addTo.add_option('-s', dest='search_for_dbname', metavar='<database name>',
                     help="Search for latest backup set for the named database. Mandatory if -t, -b -R options are not supplied.")
    addTo.add_option('-e', action='store_true', dest='drop_db', default=False,
                     help="Drop (erase) target database before recovery commences")
    addTo.add_option('-B', dest='batch_default', type='int', default=DEFAULT_NUM_WORKERS, metavar="<number>",
                     help="Dispatches work to segment hosts in batches of specified size [default: %s]" % DEFAULT_NUM_WORKERS)
    addTo.add_option('-G', dest='restore_global', action="callback", callback=restore_global_callback,
                     help="Restore global objects dump file if found in target dumpset. The global objects file is secured via the gpcrondump -G options, and has a filename of the form gp_global_1_1_<timestamp>. Specify \"-G only\" to only restore the global objects dump file or \"-G include\" to restore globals objects along with a normal restore. Defaults to \"include\" if neither argument is provided.")
    addTo.add_option('-S', action='append', dest='restore_schemas', default=[], metavar='<schema name>',
                     help="Perform schema level restore from a backup set. -S can be provided multiple times to include multiple schema names.")
    addTo.add_option('-T', action='append', dest='restore_tables', default=[], metavar='<schema.tablename>',
                     help="Restore a single table from a backup set. -T can be provided multiple times to include multiple schema.tablenames. Note, table schema must exist in target database.")
    addTo.add_option('-K', action='store_true', dest='keep_dump_files', default=False,
                     help="Retain temporary generated table dump files.")
    addTo.add_option('-m', action='store_true', dest='metadata_only', default=False,
                     help="Only restore database metadata")
    addTo.add_option('--noanalyze', action='store_true', dest='no_analyze', default=False,
                     help="Suppress the ANALYZE run following a successful restore. The user is responsible for running ANALYZE on any restored tables; failure to run ANALYZE following a restore may result in poor databse performance.")
    addTo.add_option('--noplan', action='store_true', dest='no_plan', default=False)
    addTo.add_option('--noaostats', action='store_true', dest='no_ao_stats', default=False)
    addTo.add_option('-u', dest='backup_dir', metavar='<backup_dir>',
                     help="Full directory path where backup set is located.")
    addTo.add_option('--table-file', dest='table_file', metavar='<filename>',
                     help='Filename containing the names of tables to be restored')
    addTo.add_option('--prefix', dest='local_dump_prefix', default='', metavar='<filename prefix>',
                     help="Prefix of the dump files to be restored")
    addTo.add_option('--redirect', dest='redirected_restore_db', metavar='<database name>',
                     help="Database name to which the data needs to be restored for the given timestamp")
    addTo.add_option('--report-status-dir', dest='report_status_dir', metavar='<report_status_dir>',
                     help="Writable directory for report and status file creation")
    addTo.add_option('--truncate', action='store_true', dest='truncate', default=False,
                     help="Truncate's the restore tables specified using -T and --table-file option")
    addTo.add_option('--change-schema', dest='change_schema', metavar="<change schema>",
                     help="Different schema name to which tables will be restored")
    addTo.add_option('--restore-stats', dest='restore_stats', action="callback", callback=restore_stats_callback,
                     help="Restore database statistics. Analysis is skipped as if the --noanalyze flag were set.")
    # --no-validate-table-name option used only for separating table restore and data restore phases, set as hidden
    addTo.add_option('--no-validate-table-name', action='store_true', dest='no_validate_table_name', default=False, help=SUPPRESS_HELP)
    parser.add_option_group(addTo)

    # For Incremental Restore
    incrOpt = OptionGroup(parser, "Incremental")
    incrOpt.add_option('--list-backup', action='store_true', dest='list_backup', default=False,
                       help="List backup timestamps that are required to restore from the input timestamp. The input timestamp should be of an incremental backup")
    parser.add_option_group(incrOpt)

    # For NetBackup Restore
    nbuOpt = OptionGroup(parser, "NetBackup")
    nbuOpt.add_option('--netbackup-service-host', dest='netbackup_service_host', metavar="<server name>",
                      help="NetBackup service hostname")
    nbuOpt.add_option('--netbackup-block-size', dest='netbackup_block_size', metavar="<block size>",
                      help="NetBackup data transfer block size")
    parser.add_option_group(nbuOpt)

    # For DDBoost Restore
    ddOpt = OptionGroup(parser, "DDBoost")
    ddOpt.add_option('--ddboost', dest='ddboost', help="Dump to DDBoost using ~/.ddconfig", action="store_true", default=False)

    ddOpt.add_option('--ddboost-storage-unit', dest='ddboost_storage_unit', default=None,
                     help="Storage unit where backup files are retrieved from the ddboost server")
    parser.add_option_group(ddOpt)

    return parser

if __name__ == '__main__':
    simple_main(create_parser, GpdbRestore)
