| package integration |
| |
| import ( |
| "fmt" |
| |
| "github.com/greenplum-db/gp-common-go-libs/structmatcher" |
| "github.com/greenplum-db/gp-common-go-libs/testhelper" |
| "github.com/greenplum-db/gpbackup/backup" |
| "github.com/greenplum-db/gpbackup/testutils" |
| |
| . "github.com/onsi/ginkgo/v2" |
| . "github.com/onsi/gomega" |
| ) |
| |
| var _ = Describe("backup integration tests", func() { |
| Describe("GetSessionGUCs", func() { |
| It("returns a slice of values for session level GUCs", func() { |
| /* |
| * We shouldn't need to run any setup queries, because we're using |
| * the default values of these GUCs. |
| */ |
| results := backup.GetSessionGUCs(connectionPool) |
| Expect(results.ClientEncoding).To(Equal("UTF8")) |
| }) |
| }) |
| Describe("GetDatabaseGUCs", func() { |
| It("returns a slice of values for database level GUCs", func() { |
| testhelper.AssertQueryRuns(connectionPool, "ALTER DATABASE testdb SET enable_nestloop TO true") |
| defer testhelper.AssertQueryRuns(connectionPool, "ALTER DATABASE testdb RESET enable_nestloop") |
| testhelper.AssertQueryRuns(connectionPool, "ALTER DATABASE testdb SET search_path TO public,pg_catalog") |
| defer testhelper.AssertQueryRuns(connectionPool, "ALTER DATABASE testdb RESET search_path") |
| testhelper.AssertQueryRuns(connectionPool, "ALTER DATABASE testdb SET lc_time TO 'C'") |
| defer testhelper.AssertQueryRuns(connectionPool, "ALTER DATABASE testdb RESET lc_time") |
| results := backup.GetDatabaseGUCs(connectionPool) |
| Expect(results).To(HaveLen(3)) |
| Expect(results[0]).To(Equal(`SET enable_nestloop TO 'true'`)) |
| Expect(results[1]).To(Equal("SET search_path TO public, pg_catalog")) |
| Expect(results[2]).To(Equal(`SET lc_time TO 'C'`)) |
| }) |
| It("only gets GUCs that are non role specific", func() { |
| testutils.SkipIfBefore6(connectionPool) |
| testhelper.AssertQueryRuns(connectionPool, "ALTER ROLE testrole IN DATABASE testdb SET enable_nestloop TO true") |
| defer testhelper.AssertQueryRuns(connectionPool, "ALTER ROLE testrole IN DATABASE testdb RESET enable_nestloop") |
| testhelper.AssertQueryRuns(connectionPool, "ALTER DATABASE testdb SET enable_nestloop TO false") |
| defer testhelper.AssertQueryRuns(connectionPool, "ALTER DATABASE testdb RESET enable_nestloop") |
| results := backup.GetDatabaseGUCs(connectionPool) |
| Expect(results).To(HaveLen(1)) |
| Expect(results[0]).To(Equal(`SET enable_nestloop TO 'false'`)) |
| }) |
| }) |
| Describe("GetDefaultDatabaseEncodingInfo", func() { |
| It("queries default values from template0", func() { |
| result := backup.GetDefaultDatabaseEncodingInfo(connectionPool) |
| |
| Expect(result.Name).To(Equal("template0")) |
| Expect(result.Encoding).To(Equal("UTF8")) |
| if true { |
| /* |
| * These values are slightly different between mac and linux |
| * so we use a regexp to match them |
| */ |
| Expect(result.Collate).To(MatchRegexp("en_US.(utf|UTF)-?8")) |
| Expect(result.CType).To(MatchRegexp("en_US.(utf|UTF)-?8")) |
| } |
| }) |
| }) |
| Describe("GetDatabaseInfo", func() { |
| It("returns a database info struct for a basic database", func() { |
| result := backup.GetDatabaseInfo(connectionPool) |
| |
| testdbExpected := backup.Database{Oid: 0, Name: "testdb", Tablespace: "pg_default", Encoding: "UTF8"} |
| structmatcher.ExpectStructsToMatchExcluding(&testdbExpected, &result, "Oid", "Collate", "CType") |
| }) |
| It("returns a database info struct for a complex database", func() { |
| var expectedDB backup.Database |
| if false { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE DATABASE create_test_db ENCODING 'UTF8' TEMPLATE template0") |
| expectedDB = backup.Database{Oid: 1, Name: "create_test_db", Tablespace: "pg_default", Encoding: "UTF8", Collate: "", CType: ""} |
| } else { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE DATABASE create_test_db ENCODING 'UTF8' LC_COLLATE 'en_US.utf-8' LC_CTYPE 'en_US.utf-8' TEMPLATE template0") |
| expectedDB = backup.Database{Oid: 1, Name: "create_test_db", Tablespace: "pg_default", Encoding: "UTF8", Collate: "en_US.utf-8", CType: "en_US.utf-8"} |
| } |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP DATABASE create_test_db") |
| |
| connectionPool.DBName = `create_test_db` |
| result := backup.GetDatabaseInfo(connectionPool) |
| connectionPool.DBName = `testdb` |
| |
| structmatcher.ExpectStructsToMatchExcluding(&expectedDB, &result, "Oid") |
| }) |
| It("returns a database info struct if database contains single quote", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE DATABASE "test'db"`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP DATABASE "test'db"`) |
| connectionPool.DBName = `test'db` |
| result := backup.GetDatabaseInfo(connectionPool) |
| connectionPool.DBName = `testdb` |
| |
| testdbExpected := backup.Database{Oid: 0, Name: `"test'db"`, Tablespace: "pg_default", Encoding: "UTF8"} |
| structmatcher.ExpectStructsToMatchExcluding(&testdbExpected, &result, "Oid", "Collate", "CType") |
| }) |
| }) |
| Describe("GetResourceQueues", func() { |
| It("returns a slice for a resource queue with only ACTIVE_STATEMENTS", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE RESOURCE QUEUE "statementsQueue" WITH (ACTIVE_STATEMENTS=7);`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP RESOURCE QUEUE "statementsQueue"`) |
| |
| results := backup.GetResourceQueues(connectionPool) |
| |
| statementsQueue := backup.ResourceQueue{Oid: 1, Name: `"statementsQueue"`, ActiveStatements: 7, MaxCost: "-1.00", CostOvercommit: false, MinCost: "0.00", Priority: "medium", MemoryLimit: "-1"} |
| |
| //Since resource queues are global, we can't be sure this is the only one |
| for _, resultQueue := range results { |
| if resultQueue.Name == `"statementsQueue"` { |
| structmatcher.ExpectStructsToMatchExcluding(&statementsQueue, &resultQueue, "Oid") |
| return |
| } |
| } |
| Fail("Resource queue 'statementsQueue' was not found.") |
| }) |
| It("returns a slice for a resource queue with only MAX_COST", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE RESOURCE QUEUE "maxCostQueue" WITH (MAX_COST=32.8);`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP RESOURCE QUEUE "maxCostQueue"`) |
| |
| results := backup.GetResourceQueues(connectionPool) |
| |
| maxCostQueue := backup.ResourceQueue{Oid: 1, Name: `"maxCostQueue"`, ActiveStatements: -1, MaxCost: "32.80", CostOvercommit: false, MinCost: "0.00", Priority: "medium", MemoryLimit: "-1"} |
| |
| for _, resultQueue := range results { |
| if resultQueue.Name == `"maxCostQueue"` { |
| structmatcher.ExpectStructsToMatchExcluding(&maxCostQueue, &resultQueue, "Oid") |
| return |
| } |
| } |
| Fail("Resource queue 'maxCostQueue' was not found.") |
| }) |
| It("returns a slice for a resource queue with everything", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE RESOURCE QUEUE "everyQueue" WITH (ACTIVE_STATEMENTS=7, MAX_COST=3e+4, COST_OVERCOMMIT=TRUE, MIN_COST=22.53, PRIORITY=LOW, MEMORY_LIMIT='2GB');`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP RESOURCE QUEUE "everyQueue"`) |
| |
| results := backup.GetResourceQueues(connectionPool) |
| |
| everyQueue := backup.ResourceQueue{Oid: 1, Name: `"everyQueue"`, ActiveStatements: 7, MaxCost: "30000.00", CostOvercommit: true, MinCost: "22.53", Priority: "low", MemoryLimit: "2GB"} |
| |
| for _, resultQueue := range results { |
| if resultQueue.Name == `"everyQueue"` { |
| structmatcher.ExpectStructsToMatchExcluding(&everyQueue, &resultQueue, "Oid") |
| return |
| } |
| } |
| Fail("Resource queue 'everyQueue' was not found.") |
| }) |
| |
| }) |
| Describe("GetResourceGroups", func() { |
| BeforeEach(func() { |
| testutils.SkipIfBefore5(connectionPool) |
| }) |
| It("returns a slice for a resource group with everything", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE RESOURCE GROUP "someGroup" WITH (CPU_RATE_LIMIT=10, MEMORY_LIMIT=20, MEMORY_SHARED_QUOTA=25, MEMORY_SPILL_RATIO=30, CONCURRENCY=15);`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP RESOURCE GROUP "someGroup"`) |
| |
| results := backup.GetResourceGroups(connectionPool) |
| |
| someGroup := backup.ResourceGroup{Oid: 1, Name: `"someGroup"`, CPURateLimit: "10", MemoryLimit: "20", Concurrency: "15", MemorySharedQuota: "25", MemorySpillRatio: "30", MemoryAuditor: "0", Cpuset: "-1"} |
| |
| for _, resultGroup := range results { |
| if resultGroup.Name == `"someGroup"` { |
| structmatcher.ExpectStructsToMatchExcluding(&someGroup, &resultGroup, "Oid") |
| return |
| } |
| } |
| Fail("Resource group 'someGroup' was not found.") |
| }) |
| It("returns a slice for a resource group with memory_auditor=vmtracker", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE RESOURCE GROUP "someGroup" WITH (CPU_RATE_LIMIT=10, MEMORY_LIMIT=20, MEMORY_SHARED_QUOTA=25, MEMORY_SPILL_RATIO=30, CONCURRENCY=0, MEMORY_AUDITOR=vmtracker);`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP RESOURCE GROUP "someGroup"`) |
| |
| results := backup.GetResourceGroups(connectionPool) |
| |
| someGroup := backup.ResourceGroup{Oid: 1, Name: `"someGroup"`, CPURateLimit: "10", MemoryLimit: "20", Concurrency: "0", MemorySharedQuota: "25", MemorySpillRatio: "30", MemoryAuditor: "0", Cpuset: "-1"} |
| |
| for _, resultGroup := range results { |
| if resultGroup.Name == `"someGroup"` { |
| structmatcher.ExpectStructsToMatchExcluding(&someGroup, &resultGroup, "Oid") |
| return |
| } |
| } |
| Fail("Resource group 'someGroup' was not found.") |
| }) |
| It("returns a resource group with defaults", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE RESOURCE GROUP "someGroup" WITH (CPU_RATE_LIMIT=10, MEMORY_LIMIT=20);`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP RESOURCE GROUP "someGroup"`) |
| |
| results := backup.GetResourceGroups(connectionPool) |
| |
| expectedDefaults := backup.ResourceGroup{Oid: 1, Name: `"someGroup"`, CPURateLimit: "10", MemoryLimit: "20", Concurrency: concurrencyDefault, MemorySharedQuota: memSharedDefault, MemorySpillRatio: memSpillDefault, MemoryAuditor: memAuditDefault, Cpuset: cpuSetDefault} |
| |
| for _, resultGroup := range results { |
| if resultGroup.Name == `"someGroup"` { |
| structmatcher.ExpectStructsToMatchExcluding(&expectedDefaults, &resultGroup, "Oid") |
| return |
| } |
| } |
| Fail("Resource group 'someGroup' was not found.") |
| }) |
| }) |
| Describe("GetDatabaseRoles", func() { |
| It("returns a role with default properties", func() { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE ROLE role1 SUPERUSER NOINHERIT") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP ROLE role1") |
| |
| results := backup.GetRoles(connectionPool) |
| |
| roleOid := testutils.OidFromObjectName(connectionPool, "", "role1", backup.TYPE_ROLE) |
| expectedRole := backup.Role{ |
| Oid: roleOid, |
| Name: "role1", |
| Super: true, |
| Inherit: false, |
| CreateRole: false, |
| CreateDB: false, |
| CanLogin: false, |
| Replication: false, |
| ConnectionLimit: -1, |
| Password: "", |
| ValidUntil: "", |
| ResQueue: "pg_default", |
| ResGroup: "admin_group", |
| Createrexthttp: false, |
| Createrextgpfd: false, |
| Createwextgpfd: false, |
| Createrexthdfs: false, |
| Createwexthdfs: false, |
| TimeConstraints: nil, |
| } |
| if false { |
| expectedRole.ResGroup = "" |
| } |
| for _, role := range results { |
| if role.Name == "role1" { |
| structmatcher.ExpectStructsToMatch(&expectedRole, role) |
| return |
| } |
| } |
| Fail("Role 'role1' was not found") |
| }) |
| It("returns a role with all properties specified", func() { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE ROLE role1") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP ROLE role1") |
| setupQuery := ` |
| ALTER ROLE role1 WITH NOSUPERUSER INHERIT CREATEROLE CREATEDB LOGIN |
| CONNECTION LIMIT 4 PASSWORD 'swordfish' VALID UNTIL '2099-01-01 00:00:00-08' |
| CREATEEXTTABLE (protocol='http') |
| CREATEEXTTABLE (protocol='gpfdist', type='readable') |
| CREATEEXTTABLE (protocol='gpfdist', type='writable')` |
| if false { |
| setupQuery += ` |
| CREATEEXTTABLE (protocol='gphdfs', type='readable') |
| CREATEEXTTABLE (protocol='gphdfs', type='writable')` |
| } |
| testhelper.AssertQueryRuns(connectionPool, setupQuery) |
| testhelper.AssertQueryRuns(connectionPool, "ALTER ROLE role1 DENY BETWEEN DAY 'Sunday' TIME '1:30 PM' AND DAY 'Wednesday' TIME '14:30:00'") |
| testhelper.AssertQueryRuns(connectionPool, "ALTER ROLE role1 DENY DAY 'Friday'") |
| testhelper.AssertQueryRuns(connectionPool, "COMMENT ON ROLE role1 IS 'this is a role comment'") |
| |
| results := backup.GetRoles(connectionPool) |
| |
| roleOid := testutils.OidFromObjectName(connectionPool, "", "role1", backup.TYPE_ROLE) |
| expectedRole := backup.Role{ |
| Oid: roleOid, |
| Name: "role1", |
| Super: false, |
| Inherit: true, |
| CreateRole: true, |
| CreateDB: true, |
| CanLogin: true, |
| ConnectionLimit: 4, |
| Password: "md5a8b2c77dfeba4705f29c094592eb3369", |
| ValidUntil: "2099-01-01 08:00:00-00", |
| ResQueue: "pg_default", |
| ResGroup: "", |
| Createrexthttp: true, |
| Createrextgpfd: true, |
| Createwextgpfd: true, |
| Createrexthdfs: true, |
| Createwexthdfs: true, |
| TimeConstraints: []backup.TimeConstraint{ |
| { |
| Oid: 0, |
| StartDay: 0, |
| StartTime: "13:30:00", |
| EndDay: 3, |
| EndTime: "14:30:00", |
| }, { |
| Oid: 0, |
| StartDay: 5, |
| StartTime: "00:00:00", |
| EndDay: 5, |
| EndTime: "24:00:00", |
| }, |
| }, |
| } |
| |
| if true { |
| expectedRole.ResGroup = "default_group" |
| } |
| if true { |
| expectedRole.Createrexthdfs = false |
| expectedRole.Createwexthdfs = false |
| } |
| |
| for _, role := range results { |
| if role.Name == "role1" { |
| structmatcher.ExpectStructsToMatchExcluding(&expectedRole, role, "TimeConstraints.Oid") |
| return |
| } |
| } |
| Fail("Role 'role1' was not found") |
| }) |
| It("returns a role with replication", func() { |
| testutils.SkipIfBefore6(connectionPool) |
| |
| testhelper.AssertQueryRuns(connectionPool, "CREATE ROLE role1 WITH SUPERUSER NOINHERIT REPLICATION") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP ROLE role1") |
| |
| results := backup.GetRoles(connectionPool) |
| |
| roleOid := testutils.OidFromObjectName(connectionPool, "", "role1", backup.TYPE_ROLE) |
| expectedRole := backup.Role{ |
| Oid: roleOid, |
| Name: "role1", |
| Super: true, |
| Inherit: false, |
| CreateRole: false, |
| CreateDB: false, |
| CanLogin: false, |
| Replication: true, |
| ConnectionLimit: -1, |
| Password: "", |
| ValidUntil: "", |
| ResQueue: "pg_default", |
| ResGroup: "admin_group", |
| Createrexthttp: false, |
| Createrextgpfd: false, |
| Createwextgpfd: false, |
| Createrexthdfs: false, |
| Createwexthdfs: false, |
| TimeConstraints: nil, |
| } |
| |
| for _, role := range results { |
| if role.Name == "role1" { |
| structmatcher.ExpectStructsToMatch(&expectedRole, role) |
| return |
| } |
| } |
| Fail("Role 'role1' was not found") |
| }) |
| It("returns a role with inf/-inf ValidUntil field", func() { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE ROLE role1inf WITH VALID UNTIL 'infinity' SUPERUSER NOINHERIT") |
| testhelper.AssertQueryRuns(connectionPool, "CREATE ROLE role1neginf WITH VALID UNTIL '-infinity' SUPERUSER NOINHERIT") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP ROLE role1inf") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP ROLE role1neginf") |
| |
| results := backup.GetRoles(connectionPool) |
| |
| expectedRoleInf := backup.Role{ |
| Oid: 0, |
| Name: "role1inf", |
| Super: true, |
| Inherit: false, |
| CreateRole: false, |
| CreateDB: false, |
| CanLogin: false, |
| Replication: false, |
| ConnectionLimit: -1, |
| Password: "", |
| ValidUntil: "infinity", |
| ResQueue: "pg_default", |
| ResGroup: "", |
| Createrexthttp: false, |
| Createrextgpfd: false, |
| Createwextgpfd: false, |
| Createrexthdfs: false, |
| Createwexthdfs: false, |
| TimeConstraints: nil, |
| } |
| |
| expectedRoleNegInf := backup.Role{ |
| Oid: 0, |
| Name: "role1neginf", |
| Super: true, |
| Inherit: false, |
| CreateRole: false, |
| CreateDB: false, |
| CanLogin: false, |
| Replication: false, |
| ConnectionLimit: -1, |
| Password: "", |
| ValidUntil: "-infinity", |
| ResQueue: "pg_default", |
| ResGroup: "", |
| Createrexthttp: false, |
| Createrextgpfd: false, |
| Createwextgpfd: false, |
| Createrexthdfs: false, |
| Createwexthdfs: false, |
| TimeConstraints: nil, |
| } |
| var foundRoles int |
| for _, role := range results { |
| if role.Name == "role1inf" { |
| structmatcher.ExpectStructsToMatchExcluding(&expectedRoleInf, role, "Oid", "ResGroup") |
| foundRoles++ |
| } else if role.Name == "role1neginf" { |
| structmatcher.ExpectStructsToMatchExcluding(&expectedRoleNegInf, role, "Oid", "ResGroup") |
| foundRoles++ |
| } |
| } |
| if foundRoles != 2 { |
| Fail("Role 'role1inf' or 'role1neginf' was not found") |
| } |
| }) |
| It("handles implicit cast of timestamp to text", func() { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE ROLE role1 SUPERUSER NOINHERIT") |
| |
| // Function and cast already exist on 4x |
| if true { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE OR REPLACE FUNCTION pg_catalog.text(timestamp without time zone) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(timestamp_out($1));';") |
| testhelper.AssertQueryRuns(connectionPool, "CREATE CAST (timestamp without time zone AS text) WITH FUNCTION pg_catalog.text(timestamp without time zone) AS IMPLICIT;") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP FUNCTION pg_catalog.text(timestamp without time zone) CASCADE;") |
| } |
| |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP ROLE role1") |
| |
| results := backup.GetRoles(connectionPool) |
| |
| roleOid := testutils.OidFromObjectName(connectionPool, "", "role1", backup.TYPE_ROLE) |
| expectedRole := backup.Role{ |
| Oid: roleOid, |
| Name: "role1", |
| Super: true, |
| Inherit: false, |
| CreateRole: false, |
| CreateDB: false, |
| CanLogin: false, |
| Replication: false, |
| ConnectionLimit: -1, |
| Password: "", |
| ValidUntil: "", |
| ResQueue: "pg_default", |
| ResGroup: "admin_group", |
| Createrexthttp: false, |
| Createrextgpfd: false, |
| Createwextgpfd: false, |
| Createrexthdfs: false, |
| Createwexthdfs: false, |
| TimeConstraints: nil, |
| } |
| if false { |
| expectedRole.ResGroup = "" |
| } |
| for _, role := range results { |
| if role.Name == "role1" { |
| structmatcher.ExpectStructsToMatch(&expectedRole, role) |
| return |
| } |
| } |
| Fail("Role 'role1' was not found") |
| |
| }) |
| }) |
| Describe("GetRoleMembers", func() { |
| BeforeEach(func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE usergroup`) |
| testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE testuser`) |
| }) |
| AfterEach(func() { |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE usergroup`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE testuser`) |
| }) |
| It("returns a role without ADMIN OPTION", func() { |
| testhelper.AssertQueryRuns(connectionPool, "GRANT usergroup TO testuser") |
| expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: "testrole", IsAdmin: false} |
| |
| roleMembers := backup.GetRoleMembers(connectionPool) |
| |
| for _, roleMember := range roleMembers { |
| if roleMember.Role == "usergroup" { |
| structmatcher.ExpectStructsToMatch(&expectedRoleMember, &roleMember) |
| return |
| } |
| } |
| Fail("Role 'testuser' is not a member of role 'usergroup'") |
| }) |
| It("returns a role WITH ADMIN OPTION", func() { |
| testhelper.AssertQueryRuns(connectionPool, "GRANT usergroup TO testuser WITH ADMIN OPTION GRANTED BY testrole") |
| expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: "testrole", IsAdmin: true} |
| |
| roleMembers := backup.GetRoleMembers(connectionPool) |
| |
| for _, roleMember := range roleMembers { |
| if roleMember.Role == "usergroup" { |
| structmatcher.ExpectStructsToMatch(&expectedRoleMember, &roleMember) |
| return |
| } |
| } |
| Fail("Role 'testuser' is not a member of role 'usergroup'") |
| }) |
| It("returns properly quoted roles in GRANT statement", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE "1testrole" SUPERUSER`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE "1testrole"`) |
| testhelper.AssertQueryRuns(connectionPool, `SET ROLE "1testrole"`) |
| defer testhelper.AssertQueryRuns(connectionPool, `SET ROLE testrole`) |
| testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE "1usergroup"`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE "1usergroup"`) |
| testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE "1testuser"`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE "1testuser"`) |
| testhelper.AssertQueryRuns(connectionPool, `GRANT "1usergroup" TO "1testuser"`) |
| expectedRoleMember := backup.RoleMember{Role: `"1usergroup"`, Member: `"1testuser"`, Grantor: `"1testrole"`, IsAdmin: false} |
| |
| roleMembers := backup.GetRoleMembers(connectionPool) |
| |
| for _, roleMember := range roleMembers { |
| if roleMember.Role == `"1usergroup"` { |
| structmatcher.ExpectStructsToMatch(&expectedRoleMember, &roleMember) |
| return |
| } |
| } |
| Fail(`Role "1testuser" is not a member of role "1usergroup"`) |
| }) |
| It("handles dropped granter", func() { |
| testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE testdropgranter_role`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE testdropgranter_role`) |
| testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE testdropgranter_member`) |
| defer testhelper.AssertQueryRuns(connectionPool, `DROP ROLE testdropgranter_member`) |
| testhelper.AssertQueryRuns(connectionPool, `CREATE ROLE testdropgranter_granter`) |
| testhelper.AssertQueryRuns(connectionPool, `GRANT testdropgranter_role TO testdropgranter_member GRANTED BY testdropgranter_granter`) |
| testhelper.AssertQueryRuns(connectionPool, `DROP ROLE testdropgranter_granter`) |
| expectedRoleMember := backup.RoleMember{Role: `testdropgranter_role`, Member: `testdropgranter_member`, Grantor: ``, IsAdmin: false} |
| |
| roleMember := backup.GetRoleMembers(connectionPool) |
| Expect(len(roleMember)).To(Equal(1)) |
| structmatcher.ExpectStructsToMatch(&expectedRoleMember, &roleMember[0]) |
| }) |
| It("handles implicit cast of oid to text", func() { |
| // Function and cast already exist on 4x |
| if true { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE OR REPLACE FUNCTION pg_catalog.text(oid) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(oidout($1));';") |
| testhelper.AssertQueryRuns(connectionPool, "CREATE CAST (oid AS text) WITH FUNCTION pg_catalog.text(oid) AS IMPLICIT;") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP FUNCTION pg_catalog.text(oid) CASCADE;") |
| } |
| testhelper.AssertQueryRuns(connectionPool, "GRANT usergroup TO testuser") |
| expectedRoleMember := backup.RoleMember{Role: "usergroup", Member: "testuser", Grantor: "testrole", IsAdmin: false} |
| |
| roleMembers := backup.GetRoleMembers(connectionPool) |
| |
| for _, roleMember := range roleMembers { |
| if roleMember.Role == "usergroup" { |
| structmatcher.ExpectStructsToMatch(&expectedRoleMember, &roleMember) |
| return |
| } |
| } |
| Fail("Role 'testuser' is not a member of role 'usergroup'") |
| }) |
| }) |
| Describe("GetRoleGUCs", func() { |
| It("returns a slice of values for user level GUCs", func() { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE ROLE role1 SUPERUSER NOINHERIT") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP ROLE role1") |
| testhelper.AssertQueryRuns(connectionPool, "ALTER ROLE role1 SET search_path TO public") |
| testhelper.AssertQueryRuns(connectionPool, "ALTER ROLE role1 SET client_min_messages TO 'info'") |
| |
| defaultStorageOptionsString := "appendonly=true, compresslevel=6, orientation=row, compresstype=none" |
| if true { |
| defaultStorageOptionsString = "compresslevel=6, compresstype=none" |
| } |
| testhelper.AssertQueryRuns(connectionPool, fmt.Sprintf("ALTER ROLE role1 SET gp_default_storage_options TO '%s'", defaultStorageOptionsString)) |
| |
| results := backup.GetRoleGUCs(connectionPool) |
| roleConfig := results["role1"] |
| |
| Expect(roleConfig).To(HaveLen(3)) |
| expectedRoleConfig := []backup.RoleGUC{ |
| {RoleName: "role1", Config: `SET client_min_messages TO 'info'`}, |
| {RoleName: "role1", Config: fmt.Sprintf(`SET gp_default_storage_options TO '%s'`, defaultStorageOptionsString)}, |
| {RoleName: "role1", Config: `SET search_path TO public`}} |
| |
| Expect(roleConfig).To(ConsistOf(expectedRoleConfig)) |
| }) |
| It("returns a slice of values for db specific user level GUCs", func() { |
| testutils.SkipIfBefore6(connectionPool) |
| |
| testhelper.AssertQueryRuns(connectionPool, "CREATE ROLE role1 SUPERUSER NOINHERIT") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP ROLE role1") |
| testhelper.AssertQueryRuns(connectionPool, "ALTER ROLE role1 IN DATABASE testdb SET search_path TO public") |
| testhelper.AssertQueryRuns(connectionPool, "ALTER ROLE role1 IN DATABASE testdb SET client_min_messages TO 'info'") |
| |
| results := backup.GetRoleGUCs(connectionPool) |
| roleConfig := results["role1"] |
| |
| Expect(roleConfig).To(HaveLen(2)) |
| expectedRoleConfig := []backup.RoleGUC{ |
| {RoleName: "role1", DbName: "testdb", Config: `SET client_min_messages TO 'info'`}, |
| {RoleName: "role1", DbName: "testdb", Config: `SET search_path TO public`}} |
| |
| Expect(roleConfig).To(ConsistOf(expectedRoleConfig)) |
| }) |
| }) |
| Describe("GetTablespaces", func() { |
| It("returns a tablespace", func() { |
| var expectedTablespace backup.Tablespace |
| if false { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE TABLESPACE test_tablespace FILESPACE test_dir") |
| expectedTablespace = backup.Tablespace{Oid: 0, Tablespace: "test_tablespace", FileLocation: "test_dir"} |
| } else { |
| testhelper.AssertQueryRuns(connectionPool, "CREATE TABLESPACE test_tablespace LOCATION '/tmp/test_dir'") |
| expectedTablespace = backup.Tablespace{Oid: 0, Tablespace: "test_tablespace", FileLocation: "'/tmp/test_dir'", SegmentLocations: []string{}} |
| } |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLESPACE test_tablespace") |
| |
| resultTablespaces := backup.GetTablespaces(connectionPool) |
| |
| for _, tablespace := range resultTablespaces { |
| if tablespace.Tablespace == "test_tablespace" { |
| structmatcher.ExpectStructsToMatchExcluding(&expectedTablespace, &tablespace, "Oid") |
| return |
| } |
| } |
| Fail("Tablespace 'test_tablespace' was not created") |
| }) |
| It("returns a tablespace with segment locations and options", func() { |
| testutils.SkipIfBefore6(connectionPool) |
| |
| testhelper.AssertQueryRuns(connectionPool, "CREATE TABLESPACE test_tablespace LOCATION '/tmp/test_dir' WITH (content0='/tmp/test_dir1', seq_page_cost=123)") |
| expectedTablespace := backup.Tablespace{ |
| Oid: 0, Tablespace: "test_tablespace", FileLocation: "'/tmp/test_dir'", |
| SegmentLocations: []string{"content0='/tmp/test_dir1'"}, |
| Options: "seq_page_cost=123", |
| } |
| |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLESPACE test_tablespace") |
| |
| resultTablespaces := backup.GetTablespaces(connectionPool) |
| |
| for _, tablespace := range resultTablespaces { |
| if tablespace.Tablespace == "test_tablespace" { |
| structmatcher.ExpectStructsToMatchExcluding(&expectedTablespace, &tablespace, "Oid") |
| return |
| } |
| } |
| Fail("Tablespace 'test_tablespace' was not created") |
| }) |
| It("handles implicit cast of oid to text", func() { |
| testutils.SkipIfBefore6(connectionPool) |
| |
| testhelper.AssertQueryRuns(connectionPool, "CREATE TABLESPACE test_tablespace LOCATION '/tmp/test_dir' WITH (content0='/tmp/test_dir1', seq_page_cost=123)") |
| testhelper.AssertQueryRuns(connectionPool, "CREATE OR REPLACE FUNCTION pg_catalog.text(oid) RETURNS text STRICT IMMUTABLE LANGUAGE SQL AS 'SELECT textin(oidout($1));';") |
| testhelper.AssertQueryRuns(connectionPool, "CREATE CAST (oid AS text) WITH FUNCTION pg_catalog.text(oid) AS IMPLICIT;") |
| expectedTablespace := backup.Tablespace{ |
| Oid: 0, Tablespace: "test_tablespace", FileLocation: "'/tmp/test_dir'", |
| SegmentLocations: []string{"content0='/tmp/test_dir1'"}, |
| Options: "seq_page_cost=123", |
| } |
| |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP TABLESPACE test_tablespace") |
| defer testhelper.AssertQueryRuns(connectionPool, "DROP FUNCTION pg_catalog.text(oid) CASCADE;") |
| |
| resultTablespaces := backup.GetTablespaces(connectionPool) |
| |
| for _, tablespace := range resultTablespaces { |
| if tablespace.Tablespace == "test_tablespace" { |
| structmatcher.ExpectStructsToMatchExcluding(&expectedTablespace, &tablespace, "Oid") |
| return |
| } |
| } |
| Fail("Tablespace 'test_tablespace' was not created") |
| }) |
| }) |
| }) |