github.com/tuhaihe/gpbackup@v1.0.3/testutils/functions.go (about)

     1  package testutils
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"os/exec"
     8  	"strings"
     9  
    10  	"github.com/tuhaihe/gp-common-go-libs/gplog"
    11  	"github.com/tuhaihe/gp-common-go-libs/operating"
    12  	"github.com/jmoiron/sqlx"
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/DATA-DOG/go-sqlmock"
    16  	"github.com/tuhaihe/gp-common-go-libs/cluster"
    17  	"github.com/tuhaihe/gp-common-go-libs/dbconn"
    18  	"github.com/tuhaihe/gp-common-go-libs/structmatcher"
    19  	"github.com/tuhaihe/gp-common-go-libs/testhelper"
    20  	"github.com/tuhaihe/gpbackup/backup"
    21  	"github.com/tuhaihe/gpbackup/filepath"
    22  	"github.com/tuhaihe/gpbackup/restore"
    23  	"github.com/tuhaihe/gpbackup/toc"
    24  	"github.com/tuhaihe/gpbackup/utils"
    25  	"github.com/sergi/go-diff/diffmatchpatch"
    26  
    27  	. "github.com/onsi/ginkgo/v2"
    28  	. "github.com/onsi/gomega"
    29  	. "github.com/onsi/gomega/gbytes"
    30  )
    31  
    32  /*
    33   * Functions for setting up the test environment and mocking out variables
    34   */
    35  
    36  func SetupTestEnvironment() (*dbconn.DBConn, sqlmock.Sqlmock, *Buffer, *Buffer, *Buffer) {
    37  	connectionPool, mock, testStdout, testStderr, testLogfile := testhelper.SetupTestEnvironment()
    38  
    39  	// Default if not set is GPDB version `5.1.0`
    40  	envTestGpdbVersion := os.Getenv("TEST_GPDB_VERSION")
    41  	if envTestGpdbVersion != "" {
    42  		testhelper.SetDBVersion(connectionPool, envTestGpdbVersion)
    43  	}
    44  
    45  	SetupTestCluster()
    46  	backup.SetVersion("0.1.0")
    47  	return connectionPool, mock, testStdout, testStderr, testLogfile
    48  }
    49  
    50  func SetupTestCluster() *cluster.Cluster {
    51  	testCluster := SetDefaultSegmentConfiguration()
    52  	backup.SetCluster(testCluster)
    53  	restore.SetCluster(testCluster)
    54  	testFPInfo := filepath.NewFilePathInfo(testCluster, "", "20170101010101", "gpseg")
    55  	backup.SetFPInfo(testFPInfo)
    56  	restore.SetFPInfo(testFPInfo)
    57  	return testCluster
    58  }
    59  
    60  func SetupTestDbConn(dbname string) *dbconn.DBConn {
    61  	conn := dbconn.NewDBConnFromEnvironment(dbname)
    62  	conn.MustConnect(1)
    63  	return conn
    64  }
    65  
    66  // Connects to specific segment in utility mode
    67  func SetupTestDBConnSegment(dbname string, port int, gpVersion dbconn.GPDBVersion) *dbconn.DBConn {
    68  
    69  	if dbname == "" {
    70  		gplog.Fatal(errors.New("No database provided"), "")
    71  	}
    72  	if port == 0 {
    73  		gplog.Fatal(errors.New("No segment port provided"), "")
    74  	}
    75  	username := operating.System.Getenv("PGUSER")
    76  	if username == "" {
    77  		currentUser, _ := operating.System.CurrentUser()
    78  		username = currentUser.Username
    79  	}
    80  	host := operating.System.Getenv("PGHOST")
    81  	if host == "" {
    82  		host, _ = operating.System.Hostname()
    83  	}
    84  
    85  	conn := &dbconn.DBConn{
    86  		ConnPool: nil,
    87  		NumConns: 0,
    88  		Driver:   &dbconn.GPDBDriver{},
    89  		User:     username,
    90  		DBName:   dbname,
    91  		Host:     host,
    92  		Port:     port,
    93  		Tx:       nil,
    94  		Version:  dbconn.GPDBVersion{},
    95  	}
    96  
    97  	var gpRoleGuc string
    98  	gpRoleGuc = "gp_role"
    99  
   100  	connStr := fmt.Sprintf("postgres://%s@%s:%d/%s?sslmode=disable&statement_cache_capacity=0&%s=utility", conn.User, conn.Host, conn.Port, conn.DBName, gpRoleGuc)
   101  
   102  	segConn, err := conn.Driver.Connect("pgx", connStr)
   103  	if err != nil {
   104  		gplog.FatalOnError(err)
   105  	}
   106  	conn.ConnPool = make([]*sqlx.DB, 1)
   107  	conn.ConnPool[0] = segConn
   108  
   109  	conn.Tx = make([]*sqlx.Tx, 1)
   110  	conn.NumConns = 1
   111  	version, err := dbconn.InitializeVersion(conn)
   112  	if err != nil {
   113  		gplog.FatalOnError(err)
   114  	}
   115  	conn.Version = version
   116  	return conn
   117  }
   118  
   119  func SetDefaultSegmentConfiguration() *cluster.Cluster {
   120  	configCoordinator := cluster.SegConfig{ContentID: -1, Hostname: "localhost", DataDir: "gpseg-1"}
   121  	configSegOne := cluster.SegConfig{ContentID: 0, Hostname: "localhost", DataDir: "gpseg0"}
   122  	configSegTwo := cluster.SegConfig{ContentID: 1, Hostname: "localhost", DataDir: "gpseg1"}
   123  	return cluster.NewCluster([]cluster.SegConfig{configCoordinator, configSegOne, configSegTwo})
   124  }
   125  
   126  func SetupTestFilespace(connectionPool *dbconn.DBConn, testCluster *cluster.Cluster) {
   127  	remoteOutput := testCluster.GenerateAndExecuteCommand("Creating filespace test directory",
   128  		cluster.ON_HOSTS|cluster.INCLUDE_COORDINATOR,
   129  		func(contentID int) string {
   130  			return fmt.Sprintf("mkdir -p /tmp/test_dir")
   131  		})
   132  	if remoteOutput.NumErrors != 0 {
   133  		Fail("Could not create filespace test directory on 1 or more hosts")
   134  	}
   135  	// Construct a filespace config like the one that gpfilespace generates
   136  	filespaceConfigQuery := `COPY (SELECT hostname || ':' || dbid || ':/tmp/test_dir/' || preferred_role || content FROM gp_segment_configuration AS subselect) TO '/tmp/temp_filespace_config';`
   137  	testhelper.AssertQueryRuns(connectionPool, filespaceConfigQuery)
   138  	out, err := exec.Command("bash", "-c", "echo \"filespace:test_dir\" > /tmp/filespace_config").CombinedOutput()
   139  	if err != nil {
   140  		Fail(fmt.Sprintf("Cannot create test filespace configuration: %s: %s", out, err.Error()))
   141  	}
   142  	out, err = exec.Command("bash", "-c", "cat /tmp/temp_filespace_config >> /tmp/filespace_config").CombinedOutput()
   143  	if err != nil {
   144  		Fail(fmt.Sprintf("Cannot finalize test filespace configuration: %s: %s", out, err.Error()))
   145  	}
   146  	// Create the filespace and verify it was created successfully
   147  	out, err = exec.Command("bash", "-c", "gpfilespace --config /tmp/filespace_config").CombinedOutput()
   148  	if err != nil {
   149  		Fail(fmt.Sprintf("Cannot create test filespace: %s: %s", out, err.Error()))
   150  	}
   151  	filespaceName := dbconn.MustSelectString(connectionPool, "SELECT fsname AS string FROM pg_filespace WHERE fsname = 'test_dir';")
   152  	if filespaceName != "test_dir" {
   153  		Fail("Filespace test_dir was not successfully created")
   154  	}
   155  }
   156  
   157  func DestroyTestFilespace(connectionPool *dbconn.DBConn) {
   158  	filespaceName := dbconn.MustSelectString(connectionPool, "SELECT fsname AS string FROM pg_filespace WHERE fsname = 'test_dir';")
   159  	if filespaceName != "test_dir" {
   160  		return
   161  	}
   162  	testhelper.AssertQueryRuns(connectionPool, "DROP FILESPACE test_dir")
   163  	out, err := exec.Command("bash", "-c", "rm -rf /tmp/test_dir /tmp/filespace_config /tmp/temp_filespace_config").CombinedOutput()
   164  	if err != nil {
   165  		Fail(fmt.Sprintf("Could not remove test filespace directory and configuration files: %s: %s", out, err.Error()))
   166  	}
   167  }
   168  
   169  func DefaultMetadata(objType string, hasPrivileges bool, hasOwner bool, hasComment bool, hasSecurityLabel bool) backup.ObjectMetadata {
   170  	privileges := make([]backup.ACL, 0)
   171  	if hasPrivileges {
   172  		privileges = []backup.ACL{DefaultACLForType("testrole", objType)}
   173  	}
   174  	owner := ""
   175  	if hasOwner {
   176  		owner = "testrole"
   177  	}
   178  	comment := ""
   179  	if hasComment {
   180  		n := ""
   181  		switch objType[0] {
   182  		case 'A', 'E', 'I', 'O', 'U':
   183  			n = "n"
   184  		}
   185  		comment = fmt.Sprintf("This is a%s %s comment.", n, strings.ToLower(objType))
   186  	}
   187  	securityLabelProvider := ""
   188  	securityLabel := ""
   189  	if hasSecurityLabel {
   190  		securityLabelProvider = "dummy"
   191  		securityLabel = "unclassified"
   192  	}
   193  	switch objType {
   194  	case "DOMAIN":
   195  		objType = "TYPE"
   196  	case "FOREIGN SERVER":
   197  		objType = "SERVER"
   198  	case "MATERIALIZED VIEW":
   199  		objType = "RELATION"
   200  	case "SEQUENCE":
   201  		objType = "RELATION"
   202  	case "TABLE":
   203  		objType = "RELATION"
   204  	case "VIEW":
   205  		objType = "RELATION"
   206  	}
   207  	return backup.ObjectMetadata{
   208  		Privileges:            privileges,
   209  		ObjectType:            objType,
   210  		Owner:                 owner,
   211  		Comment:               comment,
   212  		SecurityLabelProvider: securityLabelProvider,
   213  		SecurityLabel:         securityLabel,
   214  	}
   215  
   216  }
   217  
   218  // objType should be an all-caps string like TABLE, INDEX, etc.
   219  func DefaultMetadataMap(objType string, hasPrivileges bool, hasOwner bool, hasComment bool, hasSecurityLabel bool) backup.MetadataMap {
   220  	return backup.MetadataMap{
   221  		backup.UniqueID{ClassID: ClassIDFromObjectName(objType), Oid: 1}: DefaultMetadata(objType, hasPrivileges, hasOwner, hasComment, hasSecurityLabel),
   222  	}
   223  }
   224  
   225  var objNameToClassID = map[string]uint32{
   226  	"AGGREGATE":                 1255,
   227  	"CAST":                      2605,
   228  	"COLLATION":                 3456,
   229  	"CONSTRAINT":                2606,
   230  	"CONVERSION":                2607,
   231  	"DATABASE":                  1262,
   232  	"DOMAIN":                    1247,
   233  	"EVENT TRIGGER":             3466,
   234  	"EXTENSION":                 3079,
   235  	"FOREIGN DATA WRAPPER":      2328,
   236  	"FOREIGN SERVER":            1417,
   237  	"FUNCTION":                  1255,
   238  	"INDEX":                     2610,
   239  	"LANGUAGE":                  2612,
   240  	"OPERATOR CLASS":            2616,
   241  	"OPERATOR FAMILY":           2753,
   242  	"OPERATOR":                  2617,
   243  	"PROTOCOL":                  7175,
   244  	"RESOURCE GROUP":            6436,
   245  	"RESOURCE QUEUE":            6026,
   246  	"ROLE":                      1260,
   247  	"RULE":                      2618,
   248  	"SCHEMA":                    2615,
   249  	"SEQUENCE":                  1259,
   250  	"TABLE":                     1259,
   251  	"TABLESPACE":                1213,
   252  	"TEXT SEARCH CONFIGURATION": 3602,
   253  	"TEXT SEARCH DICTIONARY":    3600,
   254  	"TEXT SEARCH PARSER":        3601,
   255  	"TEXT SEARCH TEMPLATE":      3764,
   256  	"TRIGGER":                   2620,
   257  	"TYPE":                      1247,
   258  	"USER MAPPING":              1418,
   259  	"VIEW":                      1259,
   260  	"MATERIALIZED VIEW":         1259,
   261  }
   262  
   263  func ClassIDFromObjectName(objName string) uint32 {
   264  	return objNameToClassID[objName]
   265  
   266  }
   267  func DefaultACLForType(grantee string, objType string) backup.ACL {
   268  	return backup.ACL{
   269  		Grantee:    grantee,
   270  		Select:     objType == "PROTOCOL" || objType == "SEQUENCE" || objType == "TABLE" || objType == "VIEW" || objType == "FOREIGN TABLE" || objType == "MATERIALIZED VIEW",
   271  		Insert:     objType == "PROTOCOL" || objType == "TABLE" || objType == "VIEW" || objType == "FOREIGN TABLE" || objType == "MATERIALIZED VIEW",
   272  		Update:     objType == "SEQUENCE" || objType == "TABLE" || objType == "VIEW" || objType == "FOREIGN TABLE" || objType == "MATERIALIZED VIEW",
   273  		Delete:     objType == "TABLE" || objType == "VIEW" || objType == "FOREIGN TABLE" || objType == "MATERIALIZED VIEW",
   274  		Truncate:   objType == "TABLE" || objType == "VIEW" || objType == "MATERIALIZED VIEW",
   275  		References: objType == "TABLE" || objType == "VIEW" || objType == "FOREIGN TABLE" || objType == "MATERIALIZED VIEW",
   276  		Trigger:    objType == "TABLE" || objType == "VIEW" || objType == "FOREIGN TABLE" || objType == "MATERIALIZED VIEW",
   277  		Usage:      objType == "LANGUAGE" || objType == "SCHEMA" || objType == "SEQUENCE" || objType == "FOREIGN DATA WRAPPER" || objType == "FOREIGN SERVER",
   278  		Execute:    objType == "FUNCTION" || objType == "AGGREGATE",
   279  		Create:     objType == "DATABASE" || objType == "SCHEMA" || objType == "TABLESPACE",
   280  		Temporary:  objType == "DATABASE",
   281  		Connect:    objType == "DATABASE",
   282  	}
   283  }
   284  
   285  func DefaultACLForTypeWithGrant(grantee string, objType string) backup.ACL {
   286  	return backup.ACL{
   287  		Grantee:             grantee,
   288  		SelectWithGrant:     objType == "PROTOCOL" || objType == "SEQUENCE" || objType == "TABLE" || objType == "VIEW" || objType == "MATERIALIZED VIEW",
   289  		InsertWithGrant:     objType == "PROTOCOL" || objType == "TABLE" || objType == "VIEW" || objType == "MATERIALIZED VIEW",
   290  		UpdateWithGrant:     objType == "SEQUENCE" || objType == "TABLE" || objType == "VIEW" || objType == "MATERIALIZED VIEW",
   291  		DeleteWithGrant:     objType == "TABLE" || objType == "VIEW" || objType == "MATERIALIZED VIEW",
   292  		TruncateWithGrant:   objType == "TABLE" || objType == "VIEW" || objType == "MATERIALIZED VIEW",
   293  		ReferencesWithGrant: objType == "TABLE" || objType == "VIEW" || objType == "MATERIALIZED VIEW",
   294  		TriggerWithGrant:    objType == "TABLE" || objType == "VIEW" || objType == "MATERIALIZED VIEW",
   295  		UsageWithGrant:      objType == "LANGUAGE" || objType == "SCHEMA" || objType == "SEQUENCE" || objType == "FOREIGN DATA WRAPPER" || objType == "FOREIGN SERVER",
   296  		ExecuteWithGrant:    objType == "FUNCTION",
   297  		CreateWithGrant:     objType == "DATABASE" || objType == "SCHEMA" || objType == "TABLESPACE",
   298  		TemporaryWithGrant:  objType == "DATABASE",
   299  		ConnectWithGrant:    objType == "DATABASE",
   300  	}
   301  }
   302  
   303  func DefaultACLWithout(grantee string, objType string, revoke ...string) backup.ACL {
   304  	defaultACL := DefaultACLForType(grantee, objType)
   305  	for _, priv := range revoke {
   306  		switch priv {
   307  		case "SELECT":
   308  			defaultACL.Select = false
   309  		case "INSERT":
   310  			defaultACL.Insert = false
   311  		case "UPDATE":
   312  			defaultACL.Update = false
   313  		case "DELETE":
   314  			defaultACL.Delete = false
   315  		case "TRUNCATE":
   316  			defaultACL.Truncate = false
   317  		case "REFERENCES":
   318  			defaultACL.References = false
   319  		case "TRIGGER":
   320  			defaultACL.Trigger = false
   321  		case "EXECUTE":
   322  			defaultACL.Execute = false
   323  		case "USAGE":
   324  			defaultACL.Usage = false
   325  		case "CREATE":
   326  			defaultACL.Create = false
   327  		case "TEMPORARY":
   328  			defaultACL.Temporary = false
   329  		case "CONNECT":
   330  			defaultACL.Connect = false
   331  		}
   332  	}
   333  	return defaultACL
   334  }
   335  
   336  func DefaultACLWithGrantWithout(grantee string, objType string, revoke ...string) backup.ACL {
   337  	defaultACL := DefaultACLForTypeWithGrant(grantee, objType)
   338  	for _, priv := range revoke {
   339  		switch priv {
   340  		case "SELECT":
   341  			defaultACL.SelectWithGrant = false
   342  		case "INSERT":
   343  			defaultACL.InsertWithGrant = false
   344  		case "UPDATE":
   345  			defaultACL.UpdateWithGrant = false
   346  		case "DELETE":
   347  			defaultACL.DeleteWithGrant = false
   348  		case "TRUNCATE":
   349  			defaultACL.TruncateWithGrant = false
   350  		case "REFERENCES":
   351  			defaultACL.ReferencesWithGrant = false
   352  		case "TRIGGER":
   353  			defaultACL.TriggerWithGrant = false
   354  		case "EXECUTE":
   355  			defaultACL.ExecuteWithGrant = false
   356  		case "USAGE":
   357  			defaultACL.UsageWithGrant = false
   358  		case "CREATE":
   359  			defaultACL.CreateWithGrant = false
   360  		case "TEMPORARY":
   361  			defaultACL.TemporaryWithGrant = false
   362  		case "CONNECT":
   363  			defaultACL.ConnectWithGrant = false
   364  		}
   365  	}
   366  	return defaultACL
   367  }
   368  
   369  /*
   370   * Wrapper functions around gomega operators for ease of use in tests
   371   */
   372  
   373  func SliceBufferByEntries(entries []toc.MetadataEntry, buffer *Buffer) ([]string, string) {
   374  	contents := buffer.Contents()
   375  	hunks := make([]string, 0)
   376  	length := uint64(len(contents))
   377  	var end uint64
   378  	for _, entry := range entries {
   379  		start := entry.StartByte
   380  		end = entry.EndByte
   381  		if start > length {
   382  			start = length
   383  		}
   384  		if end > length {
   385  			end = length
   386  		}
   387  		hunks = append(hunks, string(contents[start:end]))
   388  	}
   389  	return hunks, string(contents[end:])
   390  }
   391  
   392  func CompareSlicesIgnoringWhitespace(actual []string, expected []string) bool {
   393  	if len(actual) != len(expected) {
   394  		return false
   395  	}
   396  	for i := range actual {
   397  		if strings.TrimSpace(actual[i]) != expected[i] {
   398  			return false
   399  		}
   400  	}
   401  	return true
   402  }
   403  
   404  func formatEntries(entries []toc.MetadataEntry, slice []string) string {
   405  	formatted := ""
   406  	for i, item := range slice {
   407  		formatted += fmt.Sprintf("%v -> %q\n", entries[i], item)
   408  	}
   409  	return formatted
   410  }
   411  
   412  func formatContents(slice []string) string {
   413  	formatted := ""
   414  	for _, item := range slice {
   415  		formatted += fmt.Sprintf("%q\n", item)
   416  	}
   417  	return formatted
   418  }
   419  
   420  func formatDiffs(actual []string, expected []string) string {
   421  	dmp := diffmatchpatch.New()
   422  	diffs := ""
   423  	for idx := range actual {
   424  		diffs += dmp.DiffPrettyText(dmp.DiffMain(expected[idx], actual[idx], false))
   425  	}
   426  	return diffs
   427  }
   428  
   429  func AssertBufferContents(entries []toc.MetadataEntry, buffer *Buffer, expected ...string) {
   430  	if len(entries) == 0 {
   431  		Fail("TOC is empty")
   432  	}
   433  	hunks, remaining := SliceBufferByEntries(entries, buffer)
   434  	if remaining != "" {
   435  		Fail(fmt.Sprintf("Buffer contains extra contents that are not being counted by TOC:\n%s\n\nActual TOC entries were:\n\n%s", remaining, formatEntries(entries, hunks)))
   436  	}
   437  	//ok := CompareSlicesIgnoringWhitespace(hunks, expected)
   438  	//if !ok {
   439  	//	Fail(fmt.Sprintf("Actual TOC entries:\n\n%s\n\ndid not match expected contents (ignoring whitespace):\n\n%s \n\nDiff:\n>>%s\x1b[31m<<", formatEntries(entries, hunks), formatContents(expected), formatDiffs(hunks, expected)))
   440  	//}
   441  }
   442  
   443  func ExpectEntry(entries []toc.MetadataEntry, index int, schema, referenceObject, name, objectType string) {
   444  	Expect(len(entries)).To(BeNumerically(">", index))
   445  	structmatcher.ExpectStructsToMatchExcluding(entries[index], toc.MetadataEntry{Schema: schema, Name: name, ObjectType: objectType, ReferenceObject: referenceObject, StartByte: 0, EndByte: 0}, "StartByte", "EndByte")
   446  }
   447  
   448  func ExpectEntryCount(entries []toc.MetadataEntry, index int) {
   449  	Expect(len(entries)).To(BeNumerically("==", index))
   450  }
   451  
   452  func ExecuteSQLFile(connectionPool *dbconn.DBConn, filename string) {
   453  	connStr := []string{
   454  		"-U", connectionPool.User,
   455  		"-d", connectionPool.DBName,
   456  		"-h", connectionPool.Host,
   457  		"-p", fmt.Sprintf("%d", connectionPool.Port),
   458  		"-f", filename,
   459  		"-v", "ON_ERROR_STOP=1",
   460  		"-q",
   461  	}
   462  	out, err := exec.Command("psql", connStr...).CombinedOutput()
   463  	if err != nil {
   464  		Fail(fmt.Sprintf("Execution of SQL file encountered an error: %s", out))
   465  	}
   466  }
   467  
   468  func BufferLength(buffer *Buffer) uint64 {
   469  	return uint64(len(buffer.Contents()))
   470  }
   471  
   472  func OidFromCast(connectionPool *dbconn.DBConn, castSource uint32, castTarget uint32) uint32 {
   473  	query := fmt.Sprintf("SELECT c.oid FROM pg_cast c WHERE castsource = '%d' AND casttarget = '%d'", castSource, castTarget)
   474  	result := struct {
   475  		Oid uint32
   476  	}{}
   477  	err := connectionPool.Get(&result, query)
   478  	if err != nil {
   479  		Fail(fmt.Sprintf("Execution of query failed: %v", err))
   480  	}
   481  	return result.Oid
   482  }
   483  
   484  func OidFromObjectName(connectionPool *dbconn.DBConn, schemaName string, objectName string, params backup.MetadataQueryParams) uint32 {
   485  	catalogTable := params.CatalogTable
   486  	if params.OidTable != "" {
   487  		catalogTable = params.OidTable
   488  	}
   489  	schemaStr := ""
   490  	if schemaName != "" {
   491  		schemaStr = fmt.Sprintf(" AND %s = (SELECT oid FROM pg_namespace WHERE nspname = '%s')", params.SchemaField, schemaName)
   492  	}
   493  	query := fmt.Sprintf("SELECT oid FROM %s WHERE %s ='%s'%s", catalogTable, params.NameField, objectName, schemaStr)
   494  	result := struct {
   495  		Oid uint32
   496  	}{}
   497  	err := connectionPool.Get(&result, query)
   498  	if err != nil {
   499  		Fail(fmt.Sprintf("Execution of query failed: %v", err))
   500  	}
   501  	return result.Oid
   502  }
   503  
   504  func UniqueIDFromObjectName(connectionPool *dbconn.DBConn, schemaName string, objectName string, params backup.MetadataQueryParams) backup.UniqueID {
   505  	query := fmt.Sprintf("SELECT '%s'::regclass::oid", params.CatalogTable)
   506  	result := struct {
   507  		Oid uint32
   508  	}{}
   509  	err := connectionPool.Get(&result, query)
   510  	if err != nil {
   511  		Fail(fmt.Sprintf("Execution of query failed: %v", err))
   512  	}
   513  
   514  	return backup.UniqueID{ClassID: result.Oid, Oid: OidFromObjectName(connectionPool, schemaName, objectName, params)}
   515  }
   516  
   517  func GetUserByID(connectionPool *dbconn.DBConn, oid uint32) string {
   518  	return dbconn.MustSelectString(connectionPool, fmt.Sprintf("SELECT rolname AS string FROM pg_roles WHERE oid = %d", oid))
   519  }
   520  
   521  func CreateSecurityLabelIfGPDB6(connectionPool *dbconn.DBConn, objectType string, objectName string) {
   522  	if true {
   523  		testhelper.AssertQueryRuns(connectionPool, fmt.Sprintf("SECURITY LABEL FOR dummy ON %s %s IS 'unclassified';", objectType, objectName))
   524  	}
   525  }
   526  
   527  func SkipIfNot4(connectionPool *dbconn.DBConn) {
   528  	if true {
   529  		Skip("Test only applicable to GPDB4")
   530  	}
   531  }
   532  
   533  func SkipIfBefore5(connectionPool *dbconn.DBConn) {
   534  	if false {
   535  		Skip("Test only applicable to GPDB5 and above")
   536  	}
   537  }
   538  
   539  func SkipIfBefore6(connectionPool *dbconn.DBConn) {
   540  	if false {
   541  		Skip("Test only applicable to GPDB6 and above")
   542  	}
   543  }
   544  
   545  func SkipIfBefore7(connectionPool *dbconn.DBConn) {
   546  	if false {
   547  		Skip("Test only applicable to GPDB7 and above")
   548  	}
   549  }
   550  
   551  func InitializeTestTOC(buffer io.Writer, which string) (*toc.TOC, *utils.FileWithByteCount) {
   552  	tocfile := &toc.TOC{}
   553  	tocfile.InitializeMetadataEntryMap()
   554  	backupfile := utils.NewFileWithByteCount(buffer)
   555  	backupfile.Filename = which
   556  	return tocfile, backupfile
   557  }
   558  
   559  type TestExecutorMultiple struct {
   560  	ClusterOutputs      []*cluster.RemoteOutput
   561  	ClusterCommands     [][]cluster.ShellCommand
   562  	ErrorOnExecNum      int // Throw the specified error after this many executions of Execute[...]Command(); 0 means always return error
   563  	NumLocalExecutions  int
   564  	NumRemoteExecutions int
   565  	LocalOutput         string
   566  	LocalError          error
   567  	LocalCommands       []string
   568  }
   569  
   570  func (executor *TestExecutorMultiple) ExecuteLocalCommand(commandStr string) (string, error) {
   571  	executor.NumLocalExecutions++
   572  	executor.LocalCommands = append(executor.LocalCommands, commandStr)
   573  	if executor.ErrorOnExecNum == 0 || executor.NumLocalExecutions == executor.ErrorOnExecNum {
   574  		return executor.LocalOutput, executor.LocalError
   575  	}
   576  	return executor.LocalOutput, nil
   577  }
   578  
   579  func (executor *TestExecutorMultiple) ExecuteClusterCommand(scope cluster.Scope, commands []cluster.ShellCommand) (result *cluster.RemoteOutput) {
   580  	originalExecutions := executor.NumRemoteExecutions
   581  	executor.NumRemoteExecutions++
   582  	executor.ClusterCommands = append(executor.ClusterCommands, commands)
   583  	if executor.ErrorOnExecNum == 0 || executor.NumRemoteExecutions == executor.ErrorOnExecNum {
   584  		// return the indexed item if exists, otherwise the last item
   585  		numOutputs := len(executor.ClusterOutputs)
   586  		result = executor.ClusterOutputs[numOutputs-1]
   587  		if originalExecutions < numOutputs {
   588  			result = executor.ClusterOutputs[originalExecutions]
   589  		}
   590  	}
   591  	return result
   592  }