blob: e3169d6f27dbe6d14e27b4c6777a394ccb348efd [file]
package backup
import (
"fmt"
"github.com/apache/cloudberry-backup/filepath"
"github.com/apache/cloudberry-backup/history"
"github.com/apache/cloudberry-backup/options"
"github.com/apache/cloudberry-backup/utils"
"github.com/apache/cloudberry-go-libs/dbconn"
"github.com/apache/cloudberry-go-libs/gplog"
"github.com/pkg/errors"
"github.com/spf13/pflag"
)
/*
* This file contains functions related to validating user input.
*/
/* This function populates the global table filter lists, so must be run before those are
* referenced
*/
func ValidateAndProcessFilterLists(opts *options.Options) {
gplog.Verbose("Validating Tables and Schemas exist in Database")
// pre-create these so we can save the processed versions of our filters
IncludedRelationFqns = make([]options.Relation, 0)
ExcludedRelationFqns = make([]options.Relation, 0)
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)
quotedTablesStr := utils.SliceToQuotedString(quotedIncludeRelations)
query := fmt.Sprintf(`
SELECT
n.oid as schemaoid,
c.oid,
n.nspname as schemaname,
c.relname as tablename,
quote_ident(n.nspname) || '.' || quote_ident(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 {
SchemaOid uint32
Oid uint32
SchemaName string
TableName string
Name string
}, 0)
err = conn.Select(&resultTables, query)
gplog.FatalOnError(err, fmt.Sprintf("Query was: %s", query))
tableMap := make(map[string]options.Relation)
for _, table := range resultTables {
currFqn := options.Relation{
SchemaOid: table.SchemaOid,
Oid: table.Oid,
Schema: table.SchemaName,
Name: table.TableName,
}
tableMap[table.Name] = currFqn
}
partTableMap := GetPartitionTableMap(conn)
for idx, table := range quotedIncludeRelations {
tableRel, ok := tableMap[table]
/* Because users have some flexibility in what they choose to quote, also check for matches
* using table names that are not run through quote_ident.
*/
if !ok {
unquotedTable := tableList[idx]
unQuotedTableRel, unquotedOk := tableMap[unquotedTable]
if unquotedOk {
tableRel = unQuotedTableRel
table = unquotedTable
ok = true
}
}
if !ok {
if excludeSet {
gplog.Warn("Excluded table %s does not exist", table)
} else {
gplog.Fatal(nil, "Table %s does not exist", table)
}
}
if partTableMap[tableRel.Oid].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[tableRel.Oid].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)
}
// store the table oids for later use, to avoid needing to re-process
if ok {
if excludeSet {
AddExcludedRelationFqn(tableRel)
} else {
AddIncludedRelationFqn(tableRel)
}
}
}
}
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"), "")
}
if MustGetFlagBool(options.NO_INHERITS) && !(FlagChanged(options.INCLUDE_RELATION) || FlagChanged(options.INCLUDE_RELATION_FILE)) {
gplog.Fatal(errors.Errorf("--no-inherits must be specified with either --include-table or --include-table-file"), "")
}
if FlagChanged(options.SINGLE_BACKUP_DIR) && !FlagChanged(options.BACKUP_DIR) {
gplog.Fatal(errors.Errorf("--single-backup-dir must be specified with --backup-dir"), "")
}
}
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, globalFPInfo.SingleBackupDir)
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), "")
}
}