github.com/cloudberrydb/gpbackup@v1.0.3-0.20240118031043-5410fd45eed6/integration/parallel_queries_test.go (about) 1 package integration 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 "github.com/cloudberrydb/gp-common-go-libs/dbconn" 9 "github.com/cloudberrydb/gp-common-go-libs/testhelper" 10 "github.com/cloudberrydb/gpbackup/options" 11 "github.com/cloudberrydb/gpbackup/restore" 12 "github.com/cloudberrydb/gpbackup/toc" 13 "github.com/cloudberrydb/gpbackup/utils" 14 15 . "github.com/onsi/ginkgo/v2" 16 . "github.com/onsi/gomega" 17 . "github.com/onsi/gomega/gbytes" 18 ) 19 20 var _ = Describe("backup, utils, and restore integration tests related to parallelism", func() { 21 Describe("Connection pooling tests", func() { 22 var tempConn *dbconn.DBConn 23 BeforeEach(func() { 24 tempConn = dbconn.NewDBConnFromEnvironment("testdb") 25 tempConn.MustConnect(2) 26 }) 27 AfterEach(func() { 28 tempConn.Close() 29 tempConn = nil 30 }) 31 It("exhibits session-like behavior when successive queries are executed on the same connection", func() { 32 _, _ = tempConn.Exec("SET client_min_messages TO error;", 1) 33 /* 34 * The default value of client_min_messages is "notice", so now connection 1 35 * should have it set to "error" and 0 should still have it set to "notice". 36 */ 37 notInSession := dbconn.MustSelectString(tempConn, "SELECT setting AS string FROM pg_settings WHERE name = 'client_min_messages';", 0) 38 inSession := dbconn.MustSelectString(tempConn, "SELECT setting AS string FROM pg_settings WHERE name = 'client_min_messages';", 1) 39 Expect(notInSession).To(Equal("notice")) 40 Expect(inSession).To(Equal("error")) 41 }) 42 }) 43 Describe("Parallel statement execution tests", func() { 44 /* 45 * We can't inspect goroutines directly to check for parallelism without 46 * adding runtime hooks to the code, so we test parallelism by executing 47 * statements with varying pg_sleep durations. In the serial case these 48 * statements will complete in order of execution, while in the parallel 49 * case they will complete in order of increasing sleep duration. 50 * 51 * Because a call to now() will record the timestamp at the start of the 52 * session instead of the timestamp after the pg_sleep call, we must add 53 * the sleep duration to the now() timestamp to get an accurate result. 54 * 55 * Using sleep durations on the order of 0.5 seconds will slow down test 56 * runs slightly, but this is necessary to overcome query execution time 57 * fluctuations. 58 */ 59 /* 60 * We use a separate connection even for serial runs to avoid losing the 61 * configuration of the main connection variable. 62 */ 63 var tempConn *dbconn.DBConn 64 orderQuery := "SELECT exec_index AS string FROM public.timestamps ORDER BY exec_time;" 65 BeforeEach(func() { 66 tempConn = dbconn.NewDBConnFromEnvironment("testdb") 67 restore.SetConnection(tempConn) 68 tempConn.MustConnect(4) 69 testhelper.AssertQueryRuns(tempConn, "SET ROLE testrole") 70 71 }) 72 AfterEach(func() { 73 tempConn.Close() 74 tempConn = nil 75 restore.SetConnection(connectionPool) 76 }) 77 Context("no errors", func() { 78 first := "SELECT pg_sleep(0.5); INSERT INTO public.timestamps VALUES (1, now() + '0.5 seconds'::interval);" 79 second := "SELECT pg_sleep(1.5); INSERT INTO public.timestamps VALUES (2, now() + '1.5 seconds'::interval);" 80 third := "INSERT INTO public.timestamps VALUES (3, now());" 81 fourth := "SELECT pg_sleep(1); INSERT INTO public.timestamps VALUES (4, now() + '1 second'::interval);" 82 statements := []toc.StatementWithType{ 83 {ObjectType: "TABLE", Statement: first}, 84 {ObjectType: "DATABASE", Statement: second}, 85 {ObjectType: "SEQUENCE", Statement: third}, 86 {ObjectType: "DATABASE", Statement: fourth}, 87 } 88 BeforeEach(func() { 89 testhelper.AssertQueryRuns(tempConn, "CREATE TABLE public.timestamps(exec_index int, exec_time timestamp);") 90 }) 91 AfterEach(func() { 92 testhelper.AssertQueryRuns(tempConn, "DROP TABLE public.timestamps;") 93 }) 94 It("can execute all statements in the list serially", func() { 95 expectedOrderArray := []string{"1", "2", "3", "4"} 96 restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, false) 97 resultOrderArray := dbconn.MustSelectStringSlice(tempConn, orderQuery) 98 Expect(resultOrderArray).To(Equal(expectedOrderArray)) 99 }) 100 101 It("can execute all statements in the list in parallel", func() { 102 expectedOrderArray := []string{"3", "1", "4", "2"} 103 restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, true) 104 resultOrderArray := dbconn.MustSelectStringSlice(tempConn, orderQuery) 105 Expect(resultOrderArray).To(Equal(expectedOrderArray)) 106 }) 107 108 }) 109 Context("error conditions", func() { 110 goodStmt := "SELECT * FROM pg_class LIMIT 1;" 111 syntaxError := "BAD SYNTAX;" 112 statements := []toc.StatementWithType{ 113 {ObjectType: "TABLE", Statement: goodStmt}, 114 {ObjectType: "INDEX", Statement: syntaxError}, 115 } 116 Context("on-error-continue is not set", func() { 117 It("panics after exiting goroutines when running serially", func() { 118 errorMessage := "" 119 defer func() { 120 if r := recover(); r != nil { 121 errorMessage = strings.TrimSpace(fmt.Sprintf("%v", r)) 122 Expect(logFile).To(Say(regexp.QuoteMeta(`[DEBUG]:-Error encountered when executing statement: BAD SYNTAX; Error was: ERROR: syntax error at or near "BAD"`))) 123 Expect(errorMessage).To(ContainSubstring(`[CRITICAL]:-ERROR: syntax error at or near "BAD"`)) 124 Expect(errorMessage).To(Not(ContainSubstring("goroutine"))) 125 } 126 }() 127 restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, false) 128 }) 129 It("panics after exiting goroutines when running in parallel", func() { 130 errorMessage := "" 131 defer func() { 132 if r := recover(); r != nil { 133 errorMessage = strings.TrimSpace(fmt.Sprintf("%v", r)) 134 Expect(logFile).To(Say(regexp.QuoteMeta(`[DEBUG]:-Error encountered when executing statement: BAD SYNTAX; Error was: ERROR: syntax error at or near "BAD"`))) 135 Expect(errorMessage).To(ContainSubstring(`[CRITICAL]:-ERROR: syntax error at or near "BAD"`)) 136 Expect(errorMessage).To(Not(ContainSubstring("goroutine"))) 137 } 138 }() 139 restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, true) 140 }) 141 }) 142 Context("on-error-continue is set", func() { 143 BeforeEach(func() { 144 _ = restoreCmdFlags.Set(options.ON_ERROR_CONTINUE, "true") 145 }) 146 It("does not panic, but logs errors when running serially", func() { 147 restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, false) 148 Expect(logFile).To(Say(regexp.QuoteMeta(`[DEBUG]:-Error encountered when executing statement: BAD SYNTAX; Error was: ERROR: syntax error at or near "BAD"`))) 149 Expect(stderr).To(Say(regexp.QuoteMeta("[ERROR]:-Encountered 1 errors during metadata restore; see log file gbytes.Buffer for a list of failed statements."))) 150 Expect(stderr).To(Not(Say(regexp.QuoteMeta("goroutine")))) 151 }) 152 It("does not panic, but logs errors when running in parallel", func() { 153 restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, true) 154 Expect(logFile).To(Say(regexp.QuoteMeta(`[DEBUG]:-Error encountered when executing statement: BAD SYNTAX; Error was: ERROR: syntax error at or near "BAD"`))) 155 Expect(stderr).To(Say(regexp.QuoteMeta("[ERROR]:-Encountered 1 errors during metadata restore; see log file gbytes.Buffer for a list of failed statements."))) 156 Expect(stderr).To(Not(Say(regexp.QuoteMeta("goroutine")))) 157 }) 158 }) 159 160 }) 161 }) 162 })