github.com/tuhaihe/gpbackup@v1.0.3/backup/queries_postdata.go (about)

     1  package backup
     2  
     3  /*
     4   * This file contains structs and functions related to executing specific
     5   * queries to gather metadata for the objects handled in postdata.go.
     6   */
     7  
     8  import (
     9  	"database/sql"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/tuhaihe/gp-common-go-libs/dbconn"
    14  	"github.com/tuhaihe/gp-common-go-libs/gplog"
    15  	"github.com/tuhaihe/gpbackup/toc"
    16  	"github.com/tuhaihe/gpbackup/utils"
    17  )
    18  
    19  /*
    20   * This function constructs the names of implicit indexes created by
    21   * unique constraints on tables, so they can be filtered out of the
    22   * index list.
    23   *
    24   * Primary key indexes can only be created implicitly by a primary
    25   * key constraint, so they can be filtered out directly in the query
    26   * to get indexes, but multiple unique indexes can be created on the
    27   * same column so we only want to filter out the implicit ones.
    28   */
    29  func ConstructImplicitIndexOidList(connectionPool *dbconn.DBConn) string {
    30  	query := fmt.Sprintf(`
    31  	SELECT i.indexrelid
    32  	FROM pg_index i
    33  		JOIN pg_depend d on i.indexrelid = d.objid
    34  		JOIN pg_constraint c on d.refobjid = c.oid
    35  	WHERE i.indexrelid >= %d
    36  		AND i.indisunique is true
    37  		AND i.indisprimary is false;`, FIRST_NORMAL_OBJECT_ID)
    38  	indexNames := dbconn.MustSelectStringSlice(connectionPool, query)
    39  	return utils.SliceToQuotedString(indexNames)
    40  }
    41  
    42  type IndexDefinition struct {
    43  	Oid                uint32
    44  	Name               string
    45  	OwningSchema       string
    46  	OwningTable        string
    47  	Tablespace         string
    48  	Def                sql.NullString
    49  	IsClustered        bool
    50  	SupportsConstraint bool
    51  	IsReplicaIdentity  bool
    52  	StatisticsColumns  string
    53  	StatisticsValues   string
    54  	ParentIndex        uint32
    55  	ParentIndexFQN     string
    56  }
    57  
    58  func (i IndexDefinition) GetMetadataEntry() (string, toc.MetadataEntry) {
    59  	tableFQN := utils.MakeFQN(i.OwningSchema, i.OwningTable)
    60  	return "postdata",
    61  		toc.MetadataEntry{
    62  			Schema:          i.OwningSchema,
    63  			Name:            i.Name,
    64  			ObjectType:      "INDEX",
    65  			ReferenceObject: tableFQN,
    66  			StartByte:       0,
    67  			EndByte:         0,
    68  		}
    69  }
    70  
    71  func (i IndexDefinition) GetUniqueID() UniqueID {
    72  	return UniqueID{ClassID: PG_INDEX_OID, Oid: i.Oid}
    73  }
    74  
    75  func (i IndexDefinition) FQN() string {
    76  	return utils.MakeFQN(i.OwningSchema, i.Name)
    77  }
    78  
    79  /*
    80   * GetIndexes queries for all user and implicitly created indexes, since
    81   * implicitly created indexes could still have metadata to be backed up.
    82   * e.g. comments on implicitly created indexes
    83   */
    84  func GetIndexes(connectionPool *dbconn.DBConn) []IndexDefinition {
    85  	atLeast7Query := fmt.Sprintf(`
    86  		SELECT DISTINCT i.indexrelid AS oid,
    87  			coalesce(inh.inhparent, '0') AS parentindex,
    88  			quote_ident(ic.relname) AS name,
    89  			quote_ident(n.nspname) AS owningschema,
    90  			quote_ident(c.relname) AS owningtable,
    91  			coalesce(quote_ident(s.spcname), '') AS tablespace,
    92  			pg_get_indexdef(i.indexrelid) AS def,
    93  			i.indisclustered AS isclustered,
    94  			i.indisreplident AS isreplicaidentity,
    95  			CASE
    96  				WHEN conindid > 0 THEN 't'
    97  				ELSE 'f'
    98  			END as supportsconstraint,
    99  			coalesce(array_to_string((SELECT pg_catalog.array_agg(attnum ORDER BY attnum) FROM pg_catalog.pg_attribute WHERE attrelid = i.indexrelid AND attstattarget >= 0), ','), '') as statisticscolumns,
   100  			coalesce(array_to_string((SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) FROM pg_catalog.pg_attribute WHERE attrelid = i.indexrelid AND attstattarget >= 0), ','), '') as statisticsvalues	
   101  		FROM pg_index i
   102  			JOIN pg_class ic ON ic.oid = i.indexrelid
   103  			JOIN pg_namespace n ON ic.relnamespace = n.oid
   104  			JOIN pg_class c ON c.oid = i.indrelid
   105  			LEFT JOIN pg_tablespace s ON ic.reltablespace = s.oid
   106  			LEFT JOIN pg_constraint con ON i.indexrelid = con.conindid
   107  			LEFT JOIN pg_catalog.pg_inherits inh ON inh.inhrelid = i.indexrelid
   108  		WHERE %s
   109  			AND i.indisready
   110  			AND i.indisprimary = 'f'
   111  			AND i.indexrelid >= %d
   112  			AND %s
   113  		ORDER BY name`,
   114  		relationAndSchemaFilterClause(), FIRST_NORMAL_OBJECT_ID, ExtensionFilterClause("c"))
   115  
   116  	query := atLeast7Query
   117  
   118  	resultIndexes := make([]IndexDefinition, 0)
   119  	err := connectionPool.Select(&resultIndexes, query)
   120  	gplog.FatalOnError(err)
   121  
   122  	// Remove all indexes that have NULL definitions. This can happen
   123  	// if a concurrent index drop happens before the associated table
   124  	// lock is acquired earlier during gpbackup execution.
   125  	verifiedResultIndexes := make([]IndexDefinition, 0)
   126  	indexMap := make(map[uint32]IndexDefinition, 0)
   127  	for _, index := range resultIndexes {
   128  		if index.Def.Valid {
   129  			verifiedResultIndexes = append(verifiedResultIndexes, index)
   130  			indexMap[index.Oid] = index // hash index for topological sort
   131  		} else {
   132  			gplog.Warn("Index '%s' on table '%s.%s' not backed up, most likely dropped after gpbackup had begun.",
   133  				index.Name, index.OwningSchema, index.OwningTable)
   134  		}
   135  	}
   136  
   137  	// Since GPDB 7+ partition indexes can now be ALTERED to attach to a parent
   138  	// index. Topological sort indexes to ensure parent indexes are printed
   139  	// before their child indexes.
   140  	visited := make(map[uint32]struct{})
   141  	sortedIndexes := make([]IndexDefinition, 0)
   142  	stack := make([]uint32, 0)
   143  	var seen struct{}
   144  	for _, index := range verifiedResultIndexes {
   145  		currIndex := index
   146  		// Depth-first search loop. Store visited indexes to a stack
   147  		for {
   148  			if _, indexWasVisited := visited[currIndex.Oid]; indexWasVisited {
   149  				break // exit DFS if a visited index is found.
   150  			}
   151  
   152  			stack = append(stack, currIndex.Oid)
   153  			visited[currIndex.Oid] = seen
   154  			parentIndex, parentIsPresent := indexMap[currIndex.ParentIndex]
   155  			if currIndex.ParentIndex == 0 || !parentIsPresent {
   156  				break // exit DFS if index has no parent.
   157  			} else {
   158  				currIndex = parentIndex
   159  			}
   160  		}
   161  
   162  		// "Pop" indexes found by DFS
   163  		for i := len(stack) - 1; i >= 0; i-- {
   164  			indexOid := stack[i]
   165  			popIndex := indexMap[indexOid]
   166  			if popIndex.ParentIndex != 0 {
   167  				// Preprocess parent index FQN for GPDB 7+ partition indexes
   168  				popIndex.ParentIndexFQN = indexMap[popIndex.ParentIndex].FQN()
   169  			}
   170  			sortedIndexes = append(sortedIndexes, popIndex)
   171  		}
   172  		stack = stack[:0] // empty slice but keep memory allocation
   173  	}
   174  
   175  	return sortedIndexes
   176  }
   177  
   178  func GetRenameExchangedPartitionQuery(connection *dbconn.DBConn) string {
   179  	// In the case of exchanged partition tables, restoring index constraints with system-generated
   180  	// will cause a name collision in GPDB7+.  Rename those constraints to match their new owning
   181  	// tables.  In GPDB6 and below this renaming was done automatically by server code.
   182  	cteClause := ""
   183  	cteClause = `SELECT DISTINCT cl.relname
   184               FROM pg_class cl
   185               WHERE
   186                  cl.relkind IN ('r', 'f')
   187                  AND cl.relispartition = true
   188                  AND cl.relhassubclass = false`
   189  	query := fmt.Sprintf(`
   190          WITH table_cte AS (%s)
   191          SELECT
   192              ic.relname AS origname,
   193              rc.relname || SUBSTRING(ic.relname, LENGTH(ch.relname)+1, LENGTH(ch.relname)) AS newname
   194          FROM
   195              pg_index i
   196              JOIN pg_class ic ON i.indexrelid = ic.oid
   197              JOIN pg_class rc
   198                  ON i.indrelid = rc.oid
   199                  AND rc.relname != SUBSTRING(ic.relname, 1, LENGTH(rc.relname))
   200              JOIN pg_namespace n ON rc.relnamespace = n.oid
   201              INNER JOIN table_cte ch
   202                  ON SUBSTRING(ic.relname, 1, LENGTH(ch.relname)) = ch.relname
   203                  AND rc.relname != ch.relname
   204          WHERE %s;`, cteClause, SchemaFilterClause("n"))
   205  	return query
   206  }
   207  
   208  func RenameExchangedPartitionIndexes(connectionPool *dbconn.DBConn, indexes *[]IndexDefinition) {
   209  	query := GetRenameExchangedPartitionQuery(connectionPool)
   210  	names := make([]ExchangedPartitionName, 0)
   211  	err := connectionPool.Select(&names, query)
   212  	gplog.FatalOnError(err)
   213  
   214  	nameMap := make(map[string]string)
   215  	for _, name := range names {
   216  		nameMap[name.OrigName] = name.NewName
   217  	}
   218  
   219  	for idx := range *indexes {
   220  		newName, hasNewName := nameMap[(*indexes)[idx].Name]
   221  		if hasNewName {
   222  			(*indexes)[idx].Def.String = strings.Replace((*indexes)[idx].Def.String, (*indexes)[idx].Name, newName, 1)
   223  			(*indexes)[idx].Name = newName
   224  		}
   225  	}
   226  }
   227  
   228  type ExchangedPartitionName struct {
   229  	OrigName string
   230  	NewName  string
   231  }
   232  
   233  type RuleDefinition struct {
   234  	Oid          uint32
   235  	Name         string
   236  	OwningSchema string
   237  	OwningTable  string
   238  	Def          sql.NullString
   239  }
   240  
   241  func (r RuleDefinition) GetMetadataEntry() (string, toc.MetadataEntry) {
   242  	tableFQN := utils.MakeFQN(r.OwningSchema, r.OwningTable)
   243  	return "postdata",
   244  		toc.MetadataEntry{
   245  			Schema:          r.OwningSchema,
   246  			Name:            r.Name,
   247  			ObjectType:      "RULE",
   248  			ReferenceObject: tableFQN,
   249  			StartByte:       0,
   250  			EndByte:         0,
   251  		}
   252  }
   253  
   254  func (r RuleDefinition) GetUniqueID() UniqueID {
   255  	return UniqueID{ClassID: PG_REWRITE_OID, Oid: r.Oid}
   256  }
   257  
   258  func (r RuleDefinition) FQN() string {
   259  	return r.Name
   260  }
   261  
   262  /*
   263   * Rules named "_RETURN", "pg_settings_n", and "pg_settings_u" are
   264   * built-in rules and we don't want to back them up. We use two `%` to
   265   * prevent Go from interpolating the % symbol.
   266   */
   267  func GetRules(connectionPool *dbconn.DBConn) []RuleDefinition {
   268  	query := fmt.Sprintf(`
   269  	SELECT r.oid AS oid,
   270  		quote_ident(r.rulename) AS name,
   271  		quote_ident(n.nspname) AS owningschema,
   272  		quote_ident(c.relname) AS owningtable,
   273  		pg_get_ruledef(r.oid) AS def
   274  	FROM pg_rewrite r
   275  		JOIN pg_class c ON c.oid = r.ev_class
   276  		JOIN pg_namespace n ON c.relnamespace = n.oid
   277  	WHERE %s
   278  		AND rulename NOT LIKE '%%RETURN'
   279  		AND rulename NOT LIKE 'pg_%%'
   280  		AND %s
   281  	ORDER BY rulename`,
   282  		relationAndSchemaFilterClause(), ExtensionFilterClause("c"))
   283  
   284  	results := make([]RuleDefinition, 0)
   285  	err := connectionPool.Select(&results, query)
   286  	gplog.FatalOnError(err)
   287  
   288  	// Remove all rules that have NULL definitions. Not sure how
   289  	// this can happen since pg_get_ruledef uses an SPI query but
   290  	// handle the NULL just in case.
   291  	verifiedResults := make([]RuleDefinition, 0)
   292  	for _, result := range results {
   293  		if result.Def.Valid {
   294  			verifiedResults = append(verifiedResults, result)
   295  		} else {
   296  			gplog.Warn("Rule '%s' on table '%s.%s' not backed up, most likely dropped after gpbackup had begun.",
   297  				result.Name, result.OwningSchema, result.OwningTable)
   298  		}
   299  	}
   300  
   301  	return verifiedResults
   302  }
   303  
   304  type TriggerDefinition RuleDefinition
   305  
   306  func (t TriggerDefinition) GetMetadataEntry() (string, toc.MetadataEntry) {
   307  	tableFQN := utils.MakeFQN(t.OwningSchema, t.OwningTable)
   308  	return "postdata",
   309  		toc.MetadataEntry{
   310  			Schema:          t.OwningSchema,
   311  			Name:            t.Name,
   312  			ObjectType:      "TRIGGER",
   313  			ReferenceObject: tableFQN,
   314  			StartByte:       0,
   315  			EndByte:         0,
   316  		}
   317  }
   318  
   319  func (t TriggerDefinition) GetUniqueID() UniqueID {
   320  	return UniqueID{ClassID: PG_TRIGGER_OID, Oid: t.Oid}
   321  }
   322  
   323  func (t TriggerDefinition) FQN() string {
   324  	return t.Name
   325  }
   326  
   327  func GetTriggers(connectionPool *dbconn.DBConn) []TriggerDefinition {
   328  	constraintClause := "NOT tgisinternal"
   329  	query := fmt.Sprintf(`
   330  	SELECT t.oid AS oid,
   331  		quote_ident(t.tgname) AS name,
   332  		quote_ident(n.nspname) AS owningschema,
   333  		quote_ident(c.relname) AS owningtable,
   334  		pg_get_triggerdef(t.oid) AS def
   335  	FROM pg_trigger t
   336  		JOIN pg_class c ON c.oid = t.tgrelid
   337  		JOIN pg_namespace n ON c.relnamespace = n.oid
   338  	WHERE %s
   339  		AND tgname NOT LIKE 'pg_%%'
   340  		AND %s
   341  		AND %s
   342  	ORDER BY tgname`,
   343  		relationAndSchemaFilterClause(), constraintClause, ExtensionFilterClause("c"))
   344  
   345  	results := make([]TriggerDefinition, 0)
   346  	err := connectionPool.Select(&results, query)
   347  	gplog.FatalOnError(err)
   348  
   349  	// Remove all triggers that have NULL definitions. This can happen
   350  	// if the query above is run and a concurrent trigger drop happens
   351  	// just before the pg_get_triggerdef function executes.
   352  	verifiedResults := make([]TriggerDefinition, 0)
   353  	for _, result := range results {
   354  		if result.Def.Valid {
   355  			verifiedResults = append(verifiedResults, result)
   356  		} else {
   357  			gplog.Warn("Trigger '%s' on table '%s.%s' not backed up, most likely dropped after gpbackup had begun.",
   358  				result.Name, result.OwningSchema, result.OwningTable)
   359  		}
   360  	}
   361  
   362  	return verifiedResults
   363  }
   364  
   365  type EventTrigger struct {
   366  	Oid          uint32
   367  	Name         string
   368  	Event        string
   369  	FunctionName string
   370  	Enabled      string
   371  	EventTags    string
   372  }
   373  
   374  func (et EventTrigger) GetMetadataEntry() (string, toc.MetadataEntry) {
   375  	return "postdata",
   376  		toc.MetadataEntry{
   377  			Schema:          "",
   378  			Name:            et.Name,
   379  			ObjectType:      "EVENT TRIGGER",
   380  			ReferenceObject: "",
   381  			StartByte:       0,
   382  			EndByte:         0,
   383  		}
   384  }
   385  
   386  func (et EventTrigger) GetUniqueID() UniqueID {
   387  	return UniqueID{ClassID: PG_EVENT_TRIGGER, Oid: et.Oid}
   388  }
   389  
   390  func (et EventTrigger) FQN() string {
   391  	return et.Name
   392  }
   393  
   394  func GetEventTriggers(connectionPool *dbconn.DBConn) []EventTrigger {
   395  	query := fmt.Sprintf(`
   396  	SELECT et.oid,
   397  		quote_ident(et.evtname) AS name,
   398  		et.evtevent AS event,
   399  		array_to_string(array(select quote_literal(x) from unnest(evttags) as t(x)), ', ') AS eventtags,
   400  		et.evtfoid::regproc AS functionname,
   401  		et.evtenabled AS enabled
   402  	FROM pg_event_trigger et
   403  	WHERE %s
   404  	ORDER BY name`, ExtensionFilterClause("et"))
   405  
   406  	results := make([]EventTrigger, 0)
   407  	err := connectionPool.Select(&results, query)
   408  	gplog.FatalOnError(err)
   409  	return results
   410  }
   411  
   412  type RLSPolicy struct {
   413  	Oid        uint32
   414  	Name       string
   415  	Cmd        string
   416  	Permissive string
   417  	Schema     string
   418  	Table      string
   419  	Roles      string
   420  	Qual       string
   421  	WithCheck  string
   422  }
   423  
   424  func GetPolicies(connectionPool *dbconn.DBConn) []RLSPolicy {
   425  	query := `
   426  	SELECT
   427  		p.oid as oid,
   428  		quote_ident(p.polname) as name,
   429  		p.polcmd as cmd,
   430  		p.polpermissive as permissive,
   431  		quote_ident(c.relnamespace::regnamespace::text) as schema,
   432  		quote_ident(c.relname) as table,
   433  		CASE
   434  			WHEN polroles = '{0}' THEN ''
   435  			ELSE coalesce(pg_catalog.array_to_string(ARRAY(SELECT pg_catalog.quote_ident(rolname) from pg_catalog.pg_roles WHERE oid = ANY(polroles)), ', '), '')
   436  		END AS roles,
   437  		coalesce(pg_catalog.pg_get_expr(polqual, polrelid), '') AS qual,
   438  		coalesce(pg_catalog.pg_get_expr(polwithcheck, polrelid), '') AS withcheck
   439  	FROM pg_catalog.pg_policy p
   440  		JOIN pg_catalog.pg_class c ON p.polrelid = c.oid
   441  	ORDER BY p.polname`
   442  
   443  	results := make([]RLSPolicy, 0)
   444  	err := connectionPool.Select(&results, query)
   445  	gplog.FatalOnError(err)
   446  	return results
   447  }
   448  
   449  func (p RLSPolicy) GetMetadataEntry() (string, toc.MetadataEntry) {
   450  	tableFQN := utils.MakeFQN(p.Schema, p.Table)
   451  	return "postdata",
   452  		toc.MetadataEntry{
   453  			Schema:          p.Schema,
   454  			Name:            p.Table,
   455  			ObjectType:      "POLICY",
   456  			ReferenceObject: tableFQN,
   457  			StartByte:       0,
   458  			EndByte:         0,
   459  		}
   460  }
   461  
   462  func (p RLSPolicy) GetUniqueID() UniqueID {
   463  	return UniqueID{ClassID: PG_REWRITE_OID, Oid: p.Oid}
   464  }
   465  
   466  func (p RLSPolicy) FQN() string {
   467  	return p.Name
   468  }