blob: 4c2d7dcf584969e078c1bf1dc19d605fbd4eacb8 [file] [log] [blame]
package backup
/*
* This file contains structs and functions related to executing specific
* queries to gather metadata for the objects handled in predata_shared.go.
*/
import (
"database/sql"
"fmt"
"strings"
"github.com/greenplum-db/gp-common-go-libs/dbconn"
"github.com/greenplum-db/gp-common-go-libs/gplog"
"github.com/greenplum-db/gpbackup/options"
"github.com/greenplum-db/gpbackup/toc"
"github.com/greenplum-db/gpbackup/utils"
)
/*
* All queries in the various queries_*.go files come from one of three sources:
* - Copied from pg_dump largely unmodified
* - Derived from the output of a psql flag like \d+ or \df
* - Constructed from scratch
* In the former two cases, a reference to the query source is provided for
* further reference.
*
* All structs in these file whose names begin with "Query" are intended only
* for use with the functions immediately following them. Structs in the utils
* package (especially Table and Schema) are intended for more general use.
*/
type Schema struct {
Oid uint32
Name string
}
func (s Schema) GetMetadataEntry() (string, toc.MetadataEntry) {
return "predata",
toc.MetadataEntry{
Schema: s.Name,
Name: s.Name,
ObjectType: "SCHEMA",
ReferenceObject: "",
StartByte: 0,
EndByte: 0,
}
}
func (s Schema) GetUniqueID() UniqueID {
return UniqueID{ClassID: PG_NAMESPACE_OID, Oid: s.Oid}
}
func (s Schema) FQN() string {
return s.Name
}
func GetAllUserSchemas(connectionPool *dbconn.DBConn, partitionAlteredSchemas map[string]bool) []Schema {
/*
* This query is constructed from scratch, but the list of schemas to exclude
* is copied from gpcrondump so that gpbackup exhibits similar behavior regarding
* which schemas are backed up.
*/
query := fmt.Sprintf(`
SELECT oid, quote_ident(nspname) AS name FROM pg_namespace n
WHERE %s AND %s ORDER BY name`,
SchemaFilterClauseWithAlteredPartitionSchemas("n", partitionAlteredSchemas),
ExtensionFilterClause(""))
results := make([]Schema, 0)
err := connectionPool.Select(&results, query)
gplog.FatalOnError(err)
return results
}
type Constraint struct {
Oid uint32
Schema string
Name string
ConType string
Def sql.NullString
ConIsLocal bool
OwningObject string
IsDomainConstraint bool
IsPartitionParent bool
}
func (c Constraint) GetMetadataEntry() (string, toc.MetadataEntry) {
var tocSection string
if c.Def.Valid && !strings.Contains(strings.ToUpper(c.Def.String), "NOT VALID") {
tocSection = "predata"
} else {
tocSection = "postdata"
}
return tocSection,
toc.MetadataEntry{
Schema: c.Schema,
Name: c.Name,
ObjectType: "CONSTRAINT",
ReferenceObject: c.OwningObject,
StartByte: 0,
EndByte: 0,
}
}
func (c Constraint) GetUniqueID() UniqueID {
return UniqueID{ClassID: PG_CONSTRAINT_OID, Oid: c.Oid}
}
func (c Constraint) FQN() string {
/*
* It is invalid to specify the schema name with the constraint
* even though they are technically part of the parent table's schema
*/
return c.Name
}
func GetConstraints(connectionPool *dbconn.DBConn, includeTables ...Relation) []Constraint {
// ConIsLocal should always return true from GetConstraints because we
// filter out constraints that are inherited using the INHERITS clause, or
// inherited from a parent partition table. This field only accurately
// reflects constraints in GPDB6+ because check constraints on parent
// tables must propogate to children. For GPDB versions 5 or lower, this
// field will default to false.
conIsLocal := ""
conIsLocal = `con.conislocal,`
// This query is adapted from the queries underlying \d in psql.
tableQuery := ""
tableQuery = fmt.Sprintf(`
SELECT con.oid,
quote_ident(n.nspname) AS schema,
quote_ident(conname) AS name,
contype,
con.conislocal,
pg_get_constraintdef(con.oid, TRUE) AS def,
quote_ident(n.nspname) || '.' || quote_ident(c.relname) AS owningobject,
'f' AS isdomainconstraint,
CASE
WHEN pt.partrelid IS NULL THEN 'f'
ELSE 't'
END AS ispartitionparent
FROM pg_constraint con
LEFT JOIN pg_class c ON con.conrelid = c.oid
LEFT JOIN pg_partitioned_table pt ON con.conrelid = pt.partrelid
JOIN pg_namespace n ON n.oid = con.connamespace
WHERE %s
AND %s
AND c.relname IS NOT NULL
AND contype != 't'
AND (c.relispartition IS FALSE OR conislocal IS TRUE)
AND (conrelid, conname) NOT IN (SELECT i.inhrelid, con.conname FROM pg_inherits i JOIN pg_constraint con ON i.inhrelid = con.conrelid JOIN pg_constraint p ON i.inhparent = p.conrelid WHERE con.conname = p.conname)
GROUP BY con.oid, conname, contype, c.relname, n.nspname, con.conislocal, pt.partrelid`, "%s", ExtensionFilterClause("c"))
nonTableQuery := fmt.Sprintf(`
SELECT con.oid,
quote_ident(n.nspname) AS schema,
quote_ident(conname) AS name,
contype,
%s
pg_get_constraintdef(con.oid, TRUE) AS def,
quote_ident(n.nspname) || '.' || quote_ident(t.typname) AS owningobject,
't' AS isdomainconstraint,
'f' AS ispartitionparent
FROM pg_constraint con
LEFT JOIN pg_type t ON con.contypid = t.oid
JOIN pg_namespace n ON n.oid = con.connamespace
WHERE %s
AND %s
AND t.typname IS NOT NULL
GROUP BY con.oid, conname, contype, n.nspname, %s t.typname
ORDER BY name`, conIsLocal, SchemaFilterClause("n"), ExtensionFilterClause("con"), conIsLocal)
query := ""
if len(includeTables) > 0 {
oidList := make([]string, 0)
for _, table := range includeTables {
oidList = append(oidList, fmt.Sprintf("%d", table.Oid))
}
filterClause := fmt.Sprintf("%s\nAND c.oid IN (%s)", SchemaFilterClause("n"), strings.Join(oidList, ","))
query = fmt.Sprintf(tableQuery, filterClause)
} else {
tableQuery = fmt.Sprintf(tableQuery, relationAndSchemaFilterClause())
query = fmt.Sprintf("%s\nUNION\n%s", tableQuery, nonTableQuery)
}
results := make([]Constraint, 0)
err := connectionPool.Select(&results, query)
gplog.FatalOnError(err)
return results
}
func RenameExchangedPartitionConstraints(connectionPool *dbconn.DBConn, constraints *[]Constraint) {
query := GetRenameExchangedPartitionQuery(connectionPool)
names := make([]ExchangedPartitionName, 0)
err := connectionPool.Select(&names, query)
gplog.FatalOnError(err)
nameMap := make(map[string]string)
for _, name := range names {
nameMap[name.OrigName] = name.NewName
}
for idx := range *constraints {
newName, hasNewName := nameMap[(*constraints)[idx].Name]
if hasNewName {
(*constraints)[idx].Def.String = strings.Replace((*constraints)[idx].Def.String, (*constraints)[idx].Name, newName, 1)
(*constraints)[idx].Name = newName
}
}
}
// A list of schemas we don't want to back up, formatted for use in a WHERE clause
func SchemaFilterClause(namespace string) string {
schemaFilterClauseStr := ""
if len(MustGetFlagStringArray(options.INCLUDE_SCHEMA)) > 0 {
schemaFilterClauseStr = fmt.Sprintf("\nAND %s.nspname IN (%s)", namespace, utils.SliceToQuotedString(MustGetFlagStringArray(options.INCLUDE_SCHEMA)))
}
if len(MustGetFlagStringArray(options.EXCLUDE_SCHEMA)) > 0 {
schemaFilterClauseStr = fmt.Sprintf("\nAND %s.nspname NOT IN (%s)", namespace, utils.SliceToQuotedString(MustGetFlagStringArray(options.EXCLUDE_SCHEMA)))
}
return fmt.Sprintf(`%s.nspname NOT LIKE 'pg_temp_%%' AND %s.nspname NOT LIKE 'pg_toast%%' AND %s.nspname NOT IN ('gp_toolkit', 'information_schema', 'pg_aoseg', 'pg_bitmapindex', 'pg_catalog') %s`, namespace, namespace, namespace, schemaFilterClauseStr)
}
/*
* A list of schemas we don't want to back up, formatted for use in a
* WHERE clause. This function takes into consideration child
* partitions that are in different schemas than their root partition.
*/
func SchemaFilterClauseWithAlteredPartitionSchemas(namespace string, partitionAlteredSchemas map[string]bool) string {
schemaFilterClauseStr := ""
if len(MustGetFlagStringArray(options.INCLUDE_SCHEMA)) > 0 {
includeSchemaArray := make([]string, 0)
// Add partitionAlteredSchemas keys to the string array of options.INCLUDE_SCHEMA
for _, includeSchema := range MustGetFlagStringArray(options.INCLUDE_SCHEMA) {
partitionAlteredSchemas[includeSchema] = true
}
for key := range partitionAlteredSchemas {
includeSchemaArray = append(includeSchemaArray, key)
}
if len(includeSchemaArray) > 0 {
schemaFilterClauseStr = fmt.Sprintf("\nAND %s.nspname IN (%s)", namespace, utils.SliceToQuotedString(includeSchemaArray))
}
}
if len(MustGetFlagStringArray(options.EXCLUDE_SCHEMA)) > 0 {
excludeSchemaArray := make([]string, 0)
// Remove partitionAlteredSchemas keys from the string array of options.EXCLUDE_SCHEMA
for _, excludeSchema := range MustGetFlagStringArray(options.EXCLUDE_SCHEMA) {
if !partitionAlteredSchemas[excludeSchema] {
excludeSchemaArray = append(excludeSchemaArray, excludeSchema)
}
}
if len(excludeSchemaArray) > 0 {
schemaFilterClauseStr = fmt.Sprintf("\nAND %s.nspname NOT IN (%s)", namespace, utils.SliceToQuotedString(excludeSchemaArray))
}
}
return fmt.Sprintf(`%s.nspname NOT LIKE 'pg_temp_%%' AND %s.nspname NOT LIKE 'pg_toast%%' AND %s.nspname NOT IN ('gp_toolkit', 'information_schema', 'pg_aoseg', 'pg_bitmapindex', 'pg_catalog') %s`, namespace, namespace, namespace, schemaFilterClauseStr)
}
func ExtensionFilterClause(namespace string) string {
oidStr := "oid"
if namespace != "" {
oidStr = fmt.Sprintf("%s.oid", namespace)
}
return fmt.Sprintf("%s NOT IN (select objid from pg_depend where deptype = 'e')", oidStr)
}
type AccessMethod struct {
Oid uint32
Name string
Handler string
Type string
}
func (a AccessMethod) GetMetadataEntry() (string, toc.MetadataEntry) {
return "predata",
toc.MetadataEntry{
Name: a.Name,
ObjectType: "ACCESS METHOD",
ReferenceObject: "",
StartByte: 0,
EndByte: 0,
}
}
func (a AccessMethod) FQN() string {
return a.Name
}
func (a AccessMethod) GetUniqueID() UniqueID {
return UniqueID{ClassID: PG_TYPE_OID, Oid: a.Oid}
}
func GetAccessMethods(connectionPool *dbconn.DBConn) []AccessMethod {
results := make([]AccessMethod, 0)
query := fmt.Sprintf(`
SELECT oid,
quote_ident(amname) AS name,
amhandler::pg_catalog.regproc AS handler,
amtype AS type
FROM pg_am
WHERE oid > %d
ORDER BY oid;`, FIRST_NORMAL_OBJECT_ID)
err := connectionPool.Select(&results, query)
gplog.FatalOnError(err)
return results
}