blob: 067eea692f2c2e492a64bff848b113023f2ddfe5 [file]
package backup
import (
"fmt"
"github.com/cloudberrydb/gp-common-go-libs/dbconn"
"github.com/cloudberrydb/gp-common-go-libs/gplog"
"github.com/cloudberrydb/gpbackup/filepath"
"github.com/cloudberrydb/gpbackup/history"
"github.com/cloudberrydb/gpbackup/options"
"github.com/cloudberrydb/gpbackup/utils"
"github.com/pkg/errors"
"github.com/spf13/pflag"
)
/*
* This file contains functions related to validating user input.
*/
func validateFilterLists(opts *options.Options) {
gplog.Verbose("Validating Tables and Schemas exist in Database")
ValidateTablesExist(connectionPool, opts.GetIncludedTables(), false)
ValidateTablesExist(connectionPool, opts.GetExcludedTables(), true)
ValidateSchemasExist(connectionPool, opts.GetIncludedSchemas(), false)
ValidateSchemasExist(connectionPool, opts.GetExcludedSchemas(), true)
}
func ValidateSchemasExist(connectionPool *dbconn.DBConn, schemaList []string, excludeSet bool) {
if len(schemaList) == 0 {
return
}
quotedSchemasStr := utils.SliceToQuotedString(schemaList)
query := fmt.Sprintf("SELECT nspname AS string FROM pg_namespace WHERE nspname IN (%s)", quotedSchemasStr)
resultSchemas := dbconn.MustSelectStringSlice(connectionPool, query)
if len(resultSchemas) < len(schemaList) {
schemaSet := utils.NewSet(resultSchemas)
for _, schema := range schemaList {
if !schemaSet.MatchesFilter(schema) {
if excludeSet {
gplog.Warn(`Excluded schema %s does not exist`, schema)
} else {
gplog.Fatal(nil, "Schema %s does not exist", schema)
}
}
}
}
}
func ValidateTablesExist(conn *dbconn.DBConn, tableList []string, excludeSet bool) {
if len(tableList) == 0 {
return
}
quotedIncludeRelations, err := options.QuoteTableNames(connectionPool, tableList)
gplog.FatalOnError(err)
// todo perhaps store quoted list in options??
quotedTablesStr := utils.SliceToQuotedString(quotedIncludeRelations)
query := fmt.Sprintf(`
SELECT
c.oid,
n.nspname || '.' || c.relname AS name
FROM pg_namespace n
JOIN pg_class c ON n.oid = c.relnamespace
WHERE quote_ident(n.nspname) || '.' || quote_ident(c.relname) IN (%s)`, quotedTablesStr)
resultTables := make([]struct {
Oid uint32
Name string
}, 0)
err = conn.Select(&resultTables, query)
gplog.FatalOnError(err, fmt.Sprintf("Query was: %s", query))
tableMap := make(map[string]uint32)
for _, table := range resultTables {
tableMap[table.Name] = table.Oid
}
partTableMap := GetPartitionTableMap(conn)
for _, table := range tableList {
tableOid := tableMap[table]
if tableOid == 0 {
if excludeSet {
gplog.Warn("Excluded table %s does not exist", table)
} else {
gplog.Fatal(nil, "Table %s does not exist", table)
}
}
if partTableMap[tableOid].Level == "i" {
gplog.Fatal(nil, "Cannot filter on %s, as it is an intermediate partition table. Only parent partition tables and leaf partition tables may be specified.", table)
}
if partTableMap[tableOid].Level == "l" && !MustGetFlagBool(options.LEAF_PARTITION_DATA) {
gplog.Fatal(nil, "--leaf-partition-data flag must be specified to filter on %s, as it is a leaf partition table.", table)
}
}
}
func validateFlagCombinations(flags *pflag.FlagSet) {
options.CheckExclusiveFlags(flags, options.DEBUG, options.QUIET, options.VERBOSE)
options.CheckExclusiveFlags(flags, options.DATA_ONLY, options.METADATA_ONLY, options.INCREMENTAL)
options.CheckExclusiveFlags(flags, options.INCLUDE_SCHEMA, options.INCLUDE_SCHEMA_FILE, options.INCLUDE_RELATION, options.INCLUDE_RELATION_FILE)
options.CheckExclusiveFlags(flags, options.EXCLUDE_SCHEMA, options.EXCLUDE_SCHEMA_FILE, options.INCLUDE_SCHEMA, options.INCLUDE_SCHEMA_FILE)
options.CheckExclusiveFlags(flags, options.EXCLUDE_SCHEMA, options.EXCLUDE_SCHEMA_FILE, options.EXCLUDE_RELATION, options.INCLUDE_RELATION, options.EXCLUDE_RELATION_FILE, options.INCLUDE_RELATION_FILE)
options.CheckExclusiveFlags(flags, options.JOBS, options.METADATA_ONLY, options.SINGLE_DATA_FILE)
options.CheckExclusiveFlags(flags, options.METADATA_ONLY, options.LEAF_PARTITION_DATA)
options.CheckExclusiveFlags(flags, options.NO_COMPRESSION, options.COMPRESSION_TYPE)
options.CheckExclusiveFlags(flags, options.NO_COMPRESSION, options.COMPRESSION_LEVEL)
options.CheckExclusiveFlags(flags, options.PLUGIN_CONFIG, options.BACKUP_DIR)
if FlagChanged(options.COPY_QUEUE_SIZE) && !MustGetFlagBool(options.SINGLE_DATA_FILE) {
gplog.Fatal(errors.Errorf("--copy-queue-size must be specified with --single-data-file"), "")
}
if MustGetFlagString(options.FROM_TIMESTAMP) != "" && !MustGetFlagBool(options.INCREMENTAL) {
gplog.Fatal(errors.Errorf("--from-timestamp must be specified with --incremental"), "")
}
if MustGetFlagBool(options.INCREMENTAL) && !MustGetFlagBool(options.LEAF_PARTITION_DATA) {
gplog.Fatal(errors.Errorf("--leaf-partition-data must be specified with --incremental"), "")
}
}
func validateFlagValues() {
err := utils.ValidateFullPath(MustGetFlagString(options.BACKUP_DIR))
gplog.FatalOnError(err)
err = utils.ValidateFullPath(MustGetFlagString(options.PLUGIN_CONFIG))
gplog.FatalOnError(err)
err = utils.ValidateCompressionTypeAndLevel(MustGetFlagString(options.COMPRESSION_TYPE), MustGetFlagInt(options.COMPRESSION_LEVEL))
gplog.FatalOnError(err)
if MustGetFlagString(options.FROM_TIMESTAMP) != "" && !filepath.IsValidTimestamp(MustGetFlagString(options.FROM_TIMESTAMP)) {
gplog.Fatal(errors.Errorf("Timestamp %s is invalid. Timestamps must be in the format YYYYMMDDHHMMSS.",
MustGetFlagString(options.FROM_TIMESTAMP)), "")
}
if FlagChanged(options.COPY_QUEUE_SIZE) && MustGetFlagInt(options.COPY_QUEUE_SIZE) < 2 {
gplog.Fatal(errors.Errorf("--copy-queue-size %d is invalid. Must be at least 2",
MustGetFlagInt(options.COPY_QUEUE_SIZE)), "")
}
}
func validateFromTimestamp(fromTimestamp string) {
fromTimestampFPInfo := filepath.NewFilePathInfo(globalCluster, globalFPInfo.UserSpecifiedBackupDir,
fromTimestamp, globalFPInfo.UserSpecifiedSegPrefix)
if MustGetFlagString(options.PLUGIN_CONFIG) != "" {
// The config file needs to be downloaded from the remote system into the local filesystem
pluginConfig.MustRestoreFile(fromTimestampFPInfo.GetConfigFilePath())
}
fromBackupConfig := history.ReadConfigFile(fromTimestampFPInfo.GetConfigFilePath())
if !matchesIncrementalFlags(fromBackupConfig, &backupReport.BackupConfig) {
gplog.Fatal(errors.Errorf("The flags of the backup with timestamp = %s does not match "+
"that of the current one. Please refer to the report to view the flags supplied for the "+
"previous backup.", fromTimestampFPInfo.Timestamp), "")
}
}