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 }