Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions backup/queries_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,15 @@ func (fs ForeignServer) FQN() string {

func GetForeignServers(connectionPool *dbconn.DBConn) []ForeignServer {
results := make([]ForeignServer, 0)
// A foreign server that belongs to an extension (pg_depend deptype 'e') is
// normally skipped, since CREATE EXTENSION is expected to recreate it.
// However, gpbackup still dumps the user's foreign tables, which are not
// extension members. When such a table depends on an extension-member
// server -- e.g. a migration tool registered the server under the FDW
// extension with ALTER EXTENSION ... ADD SERVER -- skipping the server
// leaves the dumped foreign table unrestorable ("server ... does not
// exist"). Also dump an extension-member server when a to-be-dumped foreign
// table depends on it, so the backup is self-contained.
query := fmt.Sprintf(`
SELECT fs.oid,
quote_ident(fs.srvname) AS name,
Expand All @@ -890,8 +899,16 @@ func GetForeignServers(connectionPool *dbconn.DBConn) []ForeignServer {
FROM pg_options_to_table(fs.srvoptions) ORDER BY option_name), ', ') AS options
FROM pg_foreign_server fs
LEFT JOIN pg_foreign_data_wrapper fdw ON fdw.oid = srvfdw
WHERE fs.oid >= %d AND %s
ORDER BY fs.oid`, FIRST_NORMAL_OBJECT_ID, ExtensionFilterClause("fs"))
WHERE fs.oid >= %d
AND (%s
OR fs.oid IN (
SELECT ft.ftserver
FROM pg_foreign_table ft
JOIN pg_class c ON c.oid = ft.ftrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE %s AND %s))
ORDER BY fs.oid`, FIRST_NORMAL_OBJECT_ID, ExtensionFilterClause("fs"),
relationAndSchemaFilterClause(), ExtensionFilterClause("c"))

err := connectionPool.Select(&results, query)
gplog.FatalOnError(err)
Expand Down
24 changes: 24 additions & 0 deletions integration/predata_functions_queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,30 @@ LANGUAGE SQL`)
Expect(resultServers).To(HaveLen(1))
structmatcher.ExpectStructsToMatchExcluding(&expectedServer, &resultServers[0], "Oid")
})
It("dumps an extension-member server a foreign table depends on, but not an unreferenced one", func() {
// A server registered under an extension (e.g. by a migration tool
// via ALTER EXTENSION ... ADD SERVER) is normally filtered out. It
// must still be dumped when a non-extension foreign table depends on
// it, otherwise that dumped foreign table cannot be restored. A
// server no dumped foreign table references stays filtered.
testhelper.AssertQueryRuns(connectionPool, "CREATE SERVER referencedserver FOREIGN DATA WRAPPER foreigndatawrapper")
testhelper.AssertQueryRuns(connectionPool, "CREATE FOREIGN TABLE public.ref_ftable (i int) SERVER referencedserver")
defer testhelper.AssertQueryRuns(connectionPool, "DROP FOREIGN TABLE public.ref_ftable")
testhelper.AssertQueryRuns(connectionPool, "ALTER EXTENSION plpgsql ADD SERVER referencedserver")
defer testhelper.AssertQueryRuns(connectionPool, "ALTER EXTENSION plpgsql DROP SERVER referencedserver")
testhelper.AssertQueryRuns(connectionPool, "CREATE SERVER orphanserver FOREIGN DATA WRAPPER foreigndatawrapper")
testhelper.AssertQueryRuns(connectionPool, "ALTER EXTENSION plpgsql ADD SERVER orphanserver")
defer testhelper.AssertQueryRuns(connectionPool, "ALTER EXTENSION plpgsql DROP SERVER orphanserver")

resultServers := backup.GetForeignServers(connectionPool)

serverNames := make([]string, len(resultServers))
for i, server := range resultServers {
serverNames[i] = server.Name
}
Expect(serverNames).To(ContainElement("referencedserver"))
Expect(serverNames).ToNot(ContainElement("orphanserver"))
})
})
Describe("GetUserMappings", func() {
BeforeEach(func() {
Expand Down