| package backup |
| |
| /* |
| * This file contains structs and functions related to backing up relation |
| * (sequence, table, and view) metadata on the coordinator. |
| */ |
| |
| import ( |
| "fmt" |
| "math" |
| "strconv" |
| "strings" |
| |
| "github.com/pkg/errors" |
| |
| "github.com/apache/cloudberry-backup/options" |
| "github.com/apache/cloudberry-backup/toc" |
| "github.com/apache/cloudberry-backup/utils" |
| "github.com/apache/cloudberry-go-libs/dbconn" |
| "github.com/apache/cloudberry-go-libs/gplog" |
| "github.com/spf13/pflag" |
| ) |
| |
| /* |
| * When leafPartitionData is set, for partition tables we want to print metadata |
| * for the parent tables and data for the leaf tables, so we split them into |
| * separate lists. Intermediate tables are skipped, and non-partition tables are |
| * backed up normally (both metadata and data). |
| * |
| * When the flag is not set, we want to back up both metadata and data for all |
| * tables, so both returned arrays contain all tables. |
| */ |
| func SplitTablesByPartitionType(tables []Table, includeList []options.Relation) ([]Table, []Table) { |
| metadataTables := make([]Table, 0) |
| dataTables := make([]Table, 0) |
| if MustGetFlagBool(options.LEAF_PARTITION_DATA) || len(includeList) > 0 { |
| // generate a set of oids for more efficient matching in large dumps |
| includeSet := make(map[uint32]bool, len(includeList)) |
| for _, item := range includeList { |
| includeSet[item.Oid] = true |
| } |
| |
| for _, table := range tables { |
| if table.IsExternal && table.PartitionLevelInfo.Level == "l" { |
| if connectionPool.Version.IsGPDB() && connectionPool.Version.Before("7") { |
| // GPDB7+ has different conventions for external partitions |
| // and does not need the suffix added |
| table.Name = AppendExtPartSuffix(table.Name) |
| } |
| metadataTables = append(metadataTables, table) |
| } |
| partType := table.PartitionLevelInfo.Level |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| // In GPDB 7+, we need to dump out the leaf partition DDL along with their |
| // ALTER TABLE ATTACH PARTITION commands to construct the partition table |
| metadataTables = append(metadataTables, table) |
| } else if partType != "l" && partType != "i" { |
| metadataTables = append(metadataTables, table) |
| } |
| if MustGetFlagBool(options.LEAF_PARTITION_DATA) { |
| if partType != "p" && partType != "i" { |
| dataTables = append(dataTables, table) |
| } |
| } else if ((connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB()) && |
| table.AttachPartitionInfo != (AttachPartitionInfo{}) { |
| // For GPDB 7+ and without --leaf-partition-data, we must exclude the |
| // leaf partitions from dumping data. The COPY will be called on the |
| // top-most root partition. |
| continue |
| } else if _, match := includeSet[table.Oid]; match { |
| dataTables = append(dataTables, table) |
| } |
| } |
| } else { |
| var excludeList *utils.FilterSet |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| excludeList = utils.NewExcludeSet(MustGetFlagStringArray(options.EXCLUDE_RELATION)) |
| } else { |
| excludeList = utils.NewExcludeSet([]string{}) |
| } |
| |
| for _, table := range tables { |
| // In GPDB 7+, we need to filter out leaf and intermediate subroot partitions |
| // from being added to the metadata table list if their root partition parent |
| // is in the exclude list. This is to prevent ATTACH PARTITION statements |
| // against nonexistant root partitions from being printed to the metadata file. |
| if ((connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB()) && |
| table.AttachPartitionInfo != (AttachPartitionInfo{}) && |
| !excludeList.MatchesFilter(table.AttachPartitionInfo.Parent) { |
| continue |
| } |
| |
| if connectionPool.Version.IsGPDB() && connectionPool.Version.Before("7") && table.IsExternal && table.PartitionLevelInfo.Level == "l" { |
| table.Name = AppendExtPartSuffix(table.Name) |
| } |
| |
| metadataTables = append(metadataTables, table) |
| // In GPDB 7+, we need to filter out leaf and intermediate subroot partitions |
| // since the COPY will be called on the top-most root partition. It just so |
| // happens that those particular partition types will always have an |
| // AttachPartitionInfo initialized. |
| if table.AttachPartitionInfo == (AttachPartitionInfo{}) { |
| dataTables = append(dataTables, table) |
| } |
| } |
| } |
| return metadataTables, dataTables |
| } |
| |
| func AppendExtPartSuffix(name string) string { |
| // Do not call this function for GPDB7+ |
| const SUFFIX = "_ext_part_" |
| const MAX_LEN = 63 // MAX_DATA_LEN - 1 is the maximum length of a relation name |
| const QUOTED_MAX_LEN = MAX_LEN + 2 // We add 2 to account for a double quote on each end |
| if name[len(name)-1] == '"' { |
| |
| if len(name)+len(SUFFIX) > QUOTED_MAX_LEN { |
| return name[0:QUOTED_MAX_LEN-len(SUFFIX)] + SUFFIX + `"` |
| } |
| return name[0:len(name)-1] + SUFFIX + `"` |
| } |
| if len(name)+len(SUFFIX) > MAX_LEN { |
| return name[0:MAX_LEN+1-len(SUFFIX)] + SUFFIX |
| } |
| return name + SUFFIX |
| } |
| |
| /* |
| * This function prints CREATE TABLE statements in a format very similar to pg_dump. Unlike pg_dump, |
| * however, table names are printed fully qualified with their schemas instead of relying on setting |
| * the search_path; this will aid in later filtering to include or exclude certain tables during the |
| * backup process, and allows customers to copy just the CREATE TABLE block in order to use it directly. |
| */ |
| func PrintCreateTableStatement(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, table Table, tableMetadata ObjectMetadata) { |
| start := metadataFile.ByteCount |
| // We use an empty TOC below to keep count of the bytes for testing purposes. |
| if table.IsExternal && table.PartitionLevelInfo.Level != "p" { |
| PrintExternalTableCreateStatement(metadataFile, nil, table) |
| } else { |
| PrintRegularTableCreateStatement(metadataFile, nil, table) |
| } |
| section, entry := table.GetMetadataEntry() |
| tier := globalTierMap[table.GetUniqueID()] |
| objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, tier) |
| PrintPostCreateTableStatements(metadataFile, objToc, table, tableMetadata, tier) |
| } |
| |
| func PrintRegularTableCreateStatement(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, table Table) { |
| start := metadataFile.ByteCount |
| |
| typeStr := "" |
| if table.TableType != "" { |
| typeStr = fmt.Sprintf("OF %s ", table.TableType) |
| } |
| |
| tableModifier := "" |
| if table.IsUnlogged { |
| tableModifier = "UNLOGGED " |
| } else if table.ForeignDef != (ForeignTableDefinition{}) { |
| tableModifier = "FOREIGN " |
| } |
| |
| metadataFile.MustPrintf("\n\nCREATE %sTABLE %s %s(\n", tableModifier, table.FQN(), typeStr) |
| |
| printColumnDefinitions(metadataFile, table.ColumnDefs, table.TableType) |
| metadataFile.MustPrintf(") ") |
| if table.PartitionKeyDef != "" { |
| metadataFile.MustPrintf("PARTITION BY %s ", table.PartitionKeyDef) |
| } |
| if len(table.Inherits) != 0 && table.AttachPartitionInfo == (AttachPartitionInfo{}) { |
| dependencyList := strings.Join(table.Inherits, ", ") |
| metadataFile.MustPrintf("INHERITS (%s) ", dependencyList) |
| } |
| if table.ForeignDef != (ForeignTableDefinition{}) { |
| metadataFile.MustPrintf("SERVER %s ", table.ForeignDef.Server) |
| if table.ForeignDef.Options != "" { |
| metadataFile.MustPrintf("OPTIONS (%s) ", table.ForeignDef.Options) |
| } |
| } |
| if table.AccessMethodName != "" { |
| metadataFile.MustPrintf("USING %s ", table.AccessMethodName) |
| } |
| if table.StorageOpts != "" { |
| metadataFile.MustPrintf("WITH (%s) ", table.StorageOpts) |
| } |
| if table.TablespaceName != "" { |
| metadataFile.MustPrintf("TABLESPACE %s ", table.TablespaceName) |
| } |
| metadataFile.MustPrintf("%s", table.DistPolicy.Policy) |
| if table.PartDef != "" { |
| metadataFile.MustPrintf(" %s", strings.TrimSpace(table.PartDef)) |
| } |
| metadataFile.MustPrintln(";") |
| if table.PartTemplateDef != "" { |
| metadataFile.MustPrintf("%s;\n", strings.TrimSpace(table.PartTemplateDef)) |
| } |
| printAlterColumnStatements(metadataFile, table, table.ColumnDefs) |
| if objToc != nil { |
| section, entry := table.GetMetadataEntry() |
| tier := globalTierMap[table.GetUniqueID()] |
| objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, tier) |
| } |
| } |
| |
| func printColumnDefinitions(metadataFile *utils.FileWithByteCount, columnDefs []ColumnDefinition, tableType string) { |
| lines := make([]string, 0) |
| for _, column := range columnDefs { |
| line := fmt.Sprintf("\t%s %s", column.Name, column.Type) |
| if tableType != "" { |
| line = fmt.Sprintf("\t%s WITH OPTIONS", column.Name) |
| } |
| if column.FdwOptions != "" { |
| line += fmt.Sprintf(" OPTIONS (%s)", column.FdwOptions) |
| } |
| if column.Collation != "" { |
| line += fmt.Sprintf(" COLLATE %s", column.Collation) |
| } |
| if column.HasDefault { |
| if column.AttGenerated != "" { |
| // Unlike most keywords, GENERATED cannot be applied to a column that inherits from a parent table, |
| // even if the specified generation expression is identical to that of the column it inherits, |
| // so we skip printing it in that case. |
| if !column.IsInherited { |
| line += fmt.Sprintf(" GENERATED ALWAYS AS %s %s", column.DefaultVal, column.AttGenerated) |
| } |
| } else { |
| line += fmt.Sprintf(" DEFAULT %s", column.DefaultVal) |
| } |
| } |
| if column.NotNull { |
| line += " NOT NULL" |
| } |
| if column.Encoding != "" { |
| line += fmt.Sprintf(" ENCODING (%s)", column.Encoding) |
| } |
| lines = append(lines, line) |
| } |
| if len(lines) > 0 { |
| metadataFile.MustPrintln(strings.Join(lines, ",\n")) |
| } |
| } |
| |
| func printAlterColumnStatements(metadataFile *utils.FileWithByteCount, table Table, columnDefs []ColumnDefinition) { |
| for _, column := range columnDefs { |
| if column.StatTarget > -1 { |
| metadataFile.MustPrintf("\nALTER TABLE ONLY %s ALTER COLUMN %s SET STATISTICS %d;", table.FQN(), column.Name, column.StatTarget) |
| } |
| if column.StorageType != "" { |
| metadataFile.MustPrintf("\nALTER TABLE ONLY %s ALTER COLUMN %s SET STORAGE %s;", table.FQN(), column.Name, column.StorageType) |
| } |
| if column.Options != "" { |
| metadataFile.MustPrintf("\nALTER TABLE ONLY %s ALTER COLUMN %s SET (%s);", table.FQN(), column.Name, column.Options) |
| } |
| } |
| } |
| |
| /* |
| * This function prints additional statements that come after the CREATE TABLE |
| * statement for both regular and external tables. |
| */ |
| func PrintPostCreateTableStatements(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, table Table, tableMetadata ObjectMetadata, tier []uint32) { |
| PrintObjectMetadata(metadataFile, objToc, tableMetadata, table, "", tier) |
| statements := make([]string, 0) |
| for _, att := range table.ColumnDefs { |
| if att.Comment != "" { |
| escapedComment := utils.EscapeSingleQuotes(att.Comment) |
| statements = append(statements, fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s';", table.FQN(), att.Name, escapedComment)) |
| } |
| if att.Privileges.Valid { |
| columnMetadata := ObjectMetadata{Privileges: getColumnACL(att.Privileges, att.Kind), Owner: tableMetadata.Owner} |
| columnPrivileges := columnMetadata.GetPrivilegesStatements(table.FQN(), toc.OBJ_COLUMN, att.Name) |
| statements = append(statements, strings.TrimSpace(columnPrivileges)) |
| } |
| if att.SecurityLabel != "" { |
| escapedLabel := utils.EscapeSingleQuotes(att.SecurityLabel) |
| statements = append(statements, fmt.Sprintf("SECURITY LABEL FOR %s ON COLUMN %s.%s IS '%s';", att.SecurityLabelProvider, table.FQN(), att.Name, escapedLabel)) |
| } |
| } |
| |
| // It seems that replica identity on foreign tables default to "n" and cannot be altered in postgres 9.4 |
| if (table.ReplicaIdentity != "") && (table.ForeignDef == ForeignTableDefinition{}) { |
| switch table.ReplicaIdentity { |
| case "d", "i": |
| // default values do not need to be written ; index values are handled when the index is created |
| break |
| case "n": |
| statements = append(statements, fmt.Sprintf("ALTER TABLE %s REPLICA IDENTITY NOTHING;", table.FQN())) |
| case "f": |
| statements = append(statements, fmt.Sprintf("ALTER TABLE %s REPLICA IDENTITY FULL;", table.FQN())) |
| } |
| } |
| |
| for _, alteredPartitionRelation := range table.PartitionAlteredSchemas { |
| statements = append(statements, |
| fmt.Sprintf("ALTER TABLE %s SET SCHEMA %s;", |
| utils.MakeFQN(alteredPartitionRelation.OldSchema, alteredPartitionRelation.Name), alteredPartitionRelation.NewSchema)) |
| } |
| |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() { |
| attachInfo := table.AttachPartitionInfo |
| if (attachInfo != AttachPartitionInfo{}) { |
| statements = append(statements, |
| fmt.Sprintf("ALTER TABLE ONLY %s ATTACH PARTITION %s %s;", table.Inherits[0], attachInfo.Relname, attachInfo.Expr)) |
| } |
| |
| if table.ForceRowSecurity { |
| statements = append(statements, fmt.Sprintf("ALTER TABLE ONLY %s FORCE ROW LEVEL SECURITY;", table.FQN())) |
| } |
| } |
| |
| PrintStatements(metadataFile, objToc, table, statements, tier) |
| } |
| |
| func generateSequenceDefinitionStatement(sequence Sequence) string { |
| statement := "" |
| definition := sequence.Definition |
| maxVal := int64(math.MaxInt64) |
| minVal := int64(math.MinInt64) |
| |
| // Identity columns cannot be defined with `AS smallint/integer` |
| if ((connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB()) && |
| sequence.OwningColumnAttIdentity == "" { |
| if definition.Type != "bigint" { |
| statement += fmt.Sprintf("\n\tAS %s", definition.Type) |
| } |
| if definition.Type == "smallint" { |
| maxVal = int64(math.MaxInt16) |
| minVal = int64(math.MinInt16) |
| } else if definition.Type == "integer" { |
| maxVal = int64(math.MaxInt32) |
| minVal = int64(math.MinInt32) |
| } |
| } |
| if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("6")) || connectionPool.Version.IsCBDB() { |
| statement += fmt.Sprintf("\n\tSTART WITH %d", definition.StartVal) |
| } else if !definition.IsCalled { |
| statement += fmt.Sprintf("\n\tSTART WITH %d", definition.LastVal) |
| } |
| statement += fmt.Sprintf("\n\tINCREMENT BY %d", definition.Increment) |
| |
| if !((definition.MaxVal == maxVal && definition.Increment > 0) || |
| (definition.MaxVal == -1 && definition.Increment < 0)) { |
| statement += fmt.Sprintf("\n\tMAXVALUE %d", definition.MaxVal) |
| } else { |
| statement += "\n\tNO MAXVALUE" |
| } |
| if !((definition.MinVal == minVal && definition.Increment < 0) || |
| (definition.MinVal == 1 && definition.Increment > 0)) { |
| statement += fmt.Sprintf("\n\tMINVALUE %d", definition.MinVal) |
| } else { |
| statement += "\n\tNO MINVALUE" |
| } |
| statement += fmt.Sprintf("\n\tCACHE %d", definition.CacheVal) |
| if definition.IsCycled { |
| statement += "\n\tCYCLE" |
| } |
| return statement |
| } |
| |
| func PrintIdentityColumns(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, sequences []Sequence) { |
| for _, seq := range sequences { |
| if seq.IsIdentity { |
| start := metadataFile.ByteCount |
| |
| attrIdentityStr := "" |
| if seq.OwningColumnAttIdentity == "a" { |
| attrIdentityStr = "ALWAYS" |
| } else if seq.OwningColumnAttIdentity == "d" { |
| attrIdentityStr = "BY DEFAULT" |
| } else { |
| gplog.Fatal(errors.Errorf("Invalid Owning Column Attribute came for Identity sequence: expected 'a' or 'd', got '%s'\n", seq.OwningColumnAttIdentity), "") |
| } |
| |
| metadataFile.MustPrintf("ALTER TABLE %s\nALTER COLUMN %s ADD GENERATED %s AS IDENTITY (", |
| seq.OwningTable, seq.UnqualifiedOwningColumn, attrIdentityStr) |
| metadataFile.MustPrintf("\n\tSEQUENCE NAME %s", seq.FQN()) |
| seqDefStatement := generateSequenceDefinitionStatement(seq) |
| metadataFile.MustPrintf("%s);\n", seqDefStatement) |
| section, entry := seq.GetMetadataEntry() |
| tier := globalTierMap[seq.GetUniqueID()] |
| objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, tier) |
| } |
| } |
| } |
| |
| /* |
| * This function is largely derived from the dumpSequence() function in pg_dump.c. The values of |
| * minVal and maxVal come from SEQ_MINVALUE and SEQ_MAXVALUE, defined in include/commands/sequence.h. |
| */ |
| func PrintCreateSequenceStatements(metadataFile *utils.FileWithByteCount, |
| toc *toc.TOC, sequences []Sequence, sequenceMetadata MetadataMap) { |
| for _, sequence := range sequences { |
| if sequence.IsIdentity { |
| continue |
| } |
| start := metadataFile.ByteCount |
| definition := sequence.Definition |
| metadataFile.MustPrintf("\n\nCREATE SEQUENCE %s", sequence.FQN()) |
| seqDefStatement := generateSequenceDefinitionStatement(sequence) |
| metadataFile.MustPrint(seqDefStatement + ";") |
| |
| metadataFile.MustPrintf("\n\nSELECT pg_catalog.setval('%s', %d, %v);\n", |
| utils.EscapeSingleQuotes(sequence.FQN()), definition.LastVal, definition.IsCalled) |
| |
| section, entry := sequence.GetMetadataEntry() |
| tier := globalTierMap[sequence.GetUniqueID()] |
| toc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, tier) |
| PrintObjectMetadata(metadataFile, toc, sequenceMetadata[sequence.Relation.GetUniqueID()], sequence, "", tier) |
| } |
| } |
| |
| func PrintAlterSequenceStatements(metadataFile *utils.FileWithByteCount, |
| tocfile *toc.TOC, sequences []Sequence) { |
| gplog.Verbose("Writing ALTER SEQUENCE statements to metadata file") |
| for _, sequence := range sequences { |
| if sequence.IsIdentity { |
| continue |
| } |
| seqFQN := sequence.FQN() |
| // owningColumn is quoted and doesn't need to be quoted again |
| if sequence.OwningColumn != "" { |
| start := metadataFile.ByteCount |
| metadataFile.MustPrintf("\n\nALTER SEQUENCE %s OWNED BY %s;\n", seqFQN, sequence.OwningColumn) |
| entry := toc.MetadataEntry{ |
| Schema: sequence.Relation.Schema, |
| Name: sequence.Relation.Name, |
| ObjectType: toc.OBJ_SEQUENCE_OWNER, |
| ReferenceObject: sequence.OwningTable, |
| } |
| tocfile.AddMetadataEntry("predata", entry, start, metadataFile.ByteCount, []uint32{0, 0}) |
| } |
| } |
| } |
| |
| // A view's column names are automatically factored into it's definition. |
| func PrintCreateViewStatement(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, view View, viewMetadata ObjectMetadata) { |
| start := metadataFile.ByteCount |
| var tablespaceClause string |
| if view.Tablespace != "" { |
| tablespaceClause = fmt.Sprintf(" TABLESPACE %s", view.Tablespace) |
| } |
| // Option's keyword WITH is expected to be prepended to its options in the SQL statement |
| // Remove trailing ';' at the end of materialized view's definition |
| if !view.IsMaterialized { |
| metadataFile.MustPrintf("\n\nCREATE VIEW %s%s AS %s\n", view.FQN(), view.Options, view.Definition.String) |
| } else { |
| accessMethodClause := "" |
| if view.AccessMethodName != "" { |
| accessMethodClause = fmt.Sprintf(" USING %s", view.AccessMethodName) |
| } |
| metadataFile.MustPrintf("\n\nCREATE MATERIALIZED VIEW %s%s%s%s AS %s\nWITH NO DATA\n%s;\n", |
| view.FQN(), accessMethodClause, view.Options, tablespaceClause, view.Definition.String[:len(view.Definition.String)-1], view.DistPolicy.Policy) |
| } |
| section, entry := view.GetMetadataEntry() |
| tier := globalTierMap[view.GetUniqueID()] |
| objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, tier) |
| PrintObjectMetadata(metadataFile, objToc, viewMetadata, view, "", tier) |
| } |
| |
| // The CREATE statement here should be kept in sync with the one in |
| // PrintCreateViewStatement |
| func PrintCreatePostdataViewStatements(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, views []View) { |
| for _, view := range views { |
| start := metadataFile.ByteCount |
| metadataFile.MustPrintf("\n\nCREATE OR REPLACE VIEW %s%s AS %s\n", view.FQN(), view.Options, view.Definition.String) |
| section, entry := view.GetMetadataEntry() |
| section = "postdata" |
| tier := globalTierMap[view.GetUniqueID()] |
| objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, tier) |
| } |
| } |
| |
| // A dummy view is used to resolve the circular dependency of the following |
| // scenario. Views are restored in predata. Views can depend on constraints. |
| // Constraints are restored in postdata for performance reasons. |
| // This is directly based off pg_dump's createDummyViewAsClause |
| func PrintCreateDummyViewStatement(metadataFile *utils.FileWithByteCount, objToc *toc.TOC, view View, viewMetadata ObjectMetadata) { |
| start := metadataFile.ByteCount |
| |
| dummyViewClause := "SELECT" |
| for i, column := range view.ColumnDefs { |
| if i > 0 { |
| dummyViewClause += "," |
| } |
| dummyViewClause += fmt.Sprintf("\n\tNULL::%s", column.Type) |
| |
| if column.Collation != "" { |
| dummyViewClause += fmt.Sprintf(" COLLATE %s", column.Collation) |
| } |
| |
| dummyViewClause += fmt.Sprintf(" AS %s", column.Name) |
| } |
| dummyViewClause += ";" |
| |
| metadataFile.MustPrintf("\n\nCREATE VIEW %s AS \n%s\n", view.FQN(), dummyViewClause) |
| |
| section, entry := view.GetMetadataEntry() |
| tier := globalTierMap[view.GetUniqueID()] |
| objToc.AddMetadataEntry(section, entry, start, metadataFile.ByteCount, tier) |
| PrintObjectMetadata(metadataFile, objToc, viewMetadata, view, "", tier) |
| } |
| |
| func ExpandIncludesForPartitions(conn *dbconn.DBConn, opts *options.Options, includeOids []string, flags *pflag.FlagSet) error { |
| if len(opts.GetIncludedTables()) == 0 { |
| return nil |
| } |
| |
| allRelStructs, err := opts.GetUserTableRelationsWithIncludeFiltering(conn, includeOids, options.MustGetFlagBool(flags, options.NO_INHERITS)) |
| if err != nil { |
| return err |
| } |
| |
| includeSet := map[string]bool{} |
| for _, oid := range includeOids { |
| includeSet[oid] = true |
| } |
| |
| allRelSet := map[string]options.Relation{} |
| for _, rel := range allRelStructs { |
| oidStr := strconv.FormatUint(uint64(rel.Oid), 10) |
| allRelSet[oidStr] = rel |
| } |
| |
| // set arithmetic: find difference |
| diff := make([]options.Relation, 0) |
| for oid, rel := range allRelSet { |
| _, oidExists := includeSet[oid] |
| if !oidExists { |
| diff = append(diff, rel) |
| } |
| } |
| |
| if len(diff) > 0 { |
| gplog.Info("The filtered table set has been expanded to include additional dependent tables; see the log file for a full list of tables that have been added") |
| } |
| for _, rel := range diff { |
| fqn := fmt.Sprintf("%s.%s", utils.UnEscapeDoubleQuotes(rel.Schema), utils.UnEscapeDoubleQuotes(rel.Name)) |
| err = flags.Set(options.INCLUDE_RELATION, fqn) |
| if err != nil { |
| return err |
| } |
| opts.AddIncludedRelation(fqn) |
| AddIncludedRelationFqn(rel) |
| gplog.Verbose("Added %s to the backup set", fqn) |
| } |
| |
| return nil |
| } |