| package backup |
| |
| import ( |
| "fmt" |
| |
| "github.com/greenplum-db/gp-common-go-libs/dbconn" |
| "github.com/greenplum-db/gp-common-go-libs/gplog" |
| "github.com/greenplum-db/gpbackup/toc" |
| ) |
| |
| func GetAOIncrementalMetadata(connectionPool *dbconn.DBConn) map[string]toc.AOEntry { |
| gplog.Verbose("Querying table row mod counts") |
| var modCounts = getAllModCounts(connectionPool) |
| gplog.Verbose("Querying last DDL modification timestamp for tables") |
| var lastDDLTimestamps = getLastDDLTimestamps(connectionPool) |
| aoTableEntries := make(map[string]toc.AOEntry) |
| for aoTableFQN := range modCounts { |
| aoTableEntries[aoTableFQN] = toc.AOEntry{ |
| Modcount: modCounts[aoTableFQN], |
| LastDDLTimestamp: lastDDLTimestamps[aoTableFQN], |
| } |
| } |
| |
| return aoTableEntries |
| } |
| |
| func getAllModCounts(connectionPool *dbconn.DBConn) map[string]int64 { |
| var segTableFQNs = getAOSegTableFQNs(connectionPool) |
| modCounts := make(map[string]int64) |
| for aoTableFQN, segTableFQN := range segTableFQNs { |
| modCounts[aoTableFQN] = getModCount(connectionPool, segTableFQN) |
| } |
| return modCounts |
| } |
| |
| func getAOSegTableFQNs(connectionPool *dbconn.DBConn) map[string]string { |
| |
| |
| atLeast7Query := fmt.Sprintf(` |
| SELECT seg.aotablefqn, |
| 'pg_aoseg.' || quote_ident(aoseg_c.relname) AS aosegtablefqn |
| FROM pg_class aoseg_c |
| JOIN (SELECT pg_ao.relid AS aooid, |
| pg_ao.segrelid, |
| aotables.aotablefqn |
| FROM pg_appendonly pg_ao |
| JOIN (SELECT c.oid, |
| quote_ident(n.nspname) || '.' || quote_ident(c.relname) AS aotablefqn |
| FROM pg_class c |
| JOIN pg_namespace n ON c.relnamespace = n.oid |
| JOIN pg_am a ON c.relam = a.oid |
| WHERE a.amname in ('ao_row', 'ao_column') |
| AND %s |
| ) aotables ON pg_ao.relid = aotables.oid |
| ) seg ON aoseg_c.oid = seg.segrelid`, relationAndSchemaFilterClause()) |
| |
| query := atLeast7Query |
| |
| results := make([]struct { |
| AOTableFQN string |
| AOSegTableFQN string |
| }, 0) |
| err := connectionPool.Select(&results, query) |
| gplog.FatalOnError(err) |
| resultMap := make(map[string]string) |
| for _, result := range results { |
| resultMap[result.AOTableFQN] = result.AOSegTableFQN |
| } |
| return resultMap |
| } |
| |
| func getModCount(connectionPool *dbconn.DBConn, aosegtablefqn string) int64 { |
| |
| // In GPDB 7+, the coordinator no longer stores AO segment data so we must |
| // query the modcount from the segments. Unfortunately, this does give a |
| // false positive if a VACUUM FULL compaction happens on the AO table. |
| atLeast7Query := fmt.Sprintf(`SELECT COALESCE(pg_catalog.sum(modcount), 0) AS modcount FROM gp_dist_random('%s')`, |
| aosegtablefqn) |
| |
| query := atLeast7Query |
| |
| var results []struct { |
| Modcount int64 |
| } |
| err := connectionPool.Select(&results, query) |
| gplog.FatalOnError(err) |
| |
| return results[0].Modcount |
| } |
| |
| func getLastDDLTimestamps(connectionPool *dbconn.DBConn) map[string]string { |
| |
| atLeast7Query := fmt.Sprintf(` |
| SELECT quote_ident(aoschema) || '.' || quote_ident(aorelname) as aotablefqn, |
| lastddltimestamp |
| FROM ( SELECT c.oid AS aooid, |
| n.nspname AS aoschema, |
| c.relname AS aorelname |
| FROM pg_class c |
| JOIN pg_namespace n ON c.relnamespace = n.oid |
| JOIN pg_am a ON c.relam = a.oid |
| WHERE a.amname in ('ao_row', 'ao_column') |
| AND %s |
| ) aotables |
| JOIN ( SELECT lo.objid, |
| MAX(lo.statime) AS lastddltimestamp |
| FROM pg_stat_last_operation lo |
| WHERE lo.staactionname IN ('CREATE', 'ALTER', 'TRUNCATE') |
| GROUP BY lo.objid |
| ) lastop |
| ON aotables.aooid = lastop.objid`, relationAndSchemaFilterClause()) |
| |
| query := atLeast7Query |
| |
| var results []struct { |
| AOTableFQN string |
| LastDDLTimestamp string |
| } |
| err := connectionPool.Select(&results, query) |
| gplog.FatalOnError(err) |
| resultMap := make(map[string]string) |
| for _, result := range results { |
| resultMap[result.AOTableFQN] = result.LastDDLTimestamp |
| } |
| return resultMap |
| } |