github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/conn_executor_internal_test.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 package sql 11 12 import ( 13 "context" 14 "math" 15 "testing" 16 "time" 17 18 "github.com/cockroachdb/cockroach/pkg/base" 19 "github.com/cockroachdb/cockroach/pkg/gossip" 20 "github.com/cockroachdb/cockroach/pkg/keys" 21 "github.com/cockroachdb/cockroach/pkg/kv" 22 "github.com/cockroachdb/cockroach/pkg/roachpb" 23 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 24 "github.com/cockroachdb/cockroach/pkg/sql/distsql" 25 "github.com/cockroachdb/cockroach/pkg/sql/execinfra" 26 "github.com/cockroachdb/cockroach/pkg/sql/parser" 27 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgwirebase" 28 "github.com/cockroachdb/cockroach/pkg/sql/querycache" 29 "github.com/cockroachdb/cockroach/pkg/sql/stmtdiagnostics" 30 "github.com/cockroachdb/cockroach/pkg/testutils" 31 "github.com/cockroachdb/cockroach/pkg/util/hlc" 32 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 33 "github.com/cockroachdb/cockroach/pkg/util/log" 34 "github.com/cockroachdb/cockroach/pkg/util/mon" 35 "github.com/cockroachdb/cockroach/pkg/util/stop" 36 "github.com/cockroachdb/cockroach/pkg/util/uuid" 37 ) 38 39 // Test portal implicit destruction. Unless destroying a portal is explicitly 40 // requested, portals live until the end of the transaction in which 41 // they'recreated. If they're created outside of a transaction, they live until 42 // the next transaction completes (so until the next statement is executed, 43 // which statement is expected to be the execution of the portal that was just 44 // created). 45 // For the non-transactional case, our behavior is different than Postgres', 46 // which states that, outside of transactions, portals live until the next Sync 47 // protocol command. 48 func TestPortalsDestroyedOnTxnFinish(t *testing.T) { 49 defer leaktest.AfterTest(t)() 50 51 ctx := context.Background() 52 buf, syncResults, finished, stopper, err := startConnExecutor(ctx) 53 if err != nil { 54 t.Fatal(err) 55 } 56 defer stopper.Stop(ctx) 57 defer func() { 58 buf.Close() 59 }() 60 61 // First we test the non-transactional case. We'll send a 62 // Parse/Bind/Describe/Execute/Describe. We expect the first Describe to 63 // succeed and the 2nd one to fail (since the portal is destroyed after the 64 // Execute). 65 cmdPos := 0 66 stmt := mustParseOne("SELECT 1") 67 if err != nil { 68 t.Fatal(err) 69 } 70 if err = buf.Push(ctx, PrepareStmt{Name: "ps_nontxn", Statement: stmt}); err != nil { 71 t.Fatal(err) 72 } 73 74 cmdPos++ 75 if err = buf.Push(ctx, BindStmt{ 76 PreparedStatementName: "ps_nontxn", 77 PortalName: "portal1", 78 }); err != nil { 79 t.Fatal(err) 80 } 81 82 cmdPos++ 83 successfulDescribePos := cmdPos 84 if err = buf.Push(ctx, DescribeStmt{ 85 Name: "portal1", 86 Type: pgwirebase.PreparePortal, 87 }); err != nil { 88 t.Fatal(err) 89 } 90 91 cmdPos++ 92 successfulDescribePos = cmdPos 93 if err = buf.Push(ctx, ExecPortal{ 94 Name: "portal1", 95 }); err != nil { 96 t.Fatal(err) 97 } 98 99 cmdPos++ 100 failedDescribePos := cmdPos 101 if err = buf.Push(ctx, DescribeStmt{ 102 Name: "portal1", 103 Type: pgwirebase.PreparePortal, 104 }); err != nil { 105 t.Fatal(err) 106 } 107 108 cmdPos++ 109 if err = buf.Push(ctx, Sync{}); err != nil { 110 t.Fatal(err) 111 } 112 113 results := <-syncResults 114 numResults := len(results) 115 if numResults != cmdPos+1 { 116 t.Fatalf("expected %d results, got: %d", cmdPos+1, len(results)) 117 } 118 if err := results[successfulDescribePos].err; err != nil { 119 t.Fatalf("expected first Describe to succeed, got err: %s", err) 120 } 121 if !testutils.IsError(results[failedDescribePos].err, "unknown portal") { 122 t.Fatalf("expected error \"unknown portal\", got: %v", results[failedDescribePos].err) 123 } 124 125 // Now we test the transactional case. We'll send a 126 // BEGIN/Parse/Bind/SELECT/Describe/COMMIT/Describe. We expect the first 127 // Describe to succeed and the 2nd one to fail (since the portal is destroyed 128 // after the COMMIT). The point of the SELECT is to show that the portal 129 // survives execution of a statement. 130 cmdPos++ 131 stmt = mustParseOne("BEGIN") 132 if err != nil { 133 t.Fatal(err) 134 } 135 if err := buf.Push(ctx, ExecStmt{Statement: stmt}); err != nil { 136 t.Fatal(err) 137 } 138 139 cmdPos++ 140 stmt = mustParseOne("SELECT 1") 141 if err != nil { 142 t.Fatal(err) 143 } 144 if err = buf.Push(ctx, PrepareStmt{Name: "ps1", Statement: stmt}); err != nil { 145 t.Fatal(err) 146 } 147 148 cmdPos++ 149 if err = buf.Push(ctx, BindStmt{ 150 PreparedStatementName: "ps1", 151 PortalName: "portal1", 152 }); err != nil { 153 t.Fatal(err) 154 } 155 156 cmdPos++ 157 stmt = mustParseOne("SELECT 2") 158 if err != nil { 159 t.Fatal(err) 160 } 161 if err := buf.Push(ctx, ExecStmt{Statement: stmt}); err != nil { 162 t.Fatal(err) 163 } 164 165 cmdPos++ 166 successfulDescribePos = cmdPos 167 if err = buf.Push(ctx, DescribeStmt{ 168 Name: "portal1", 169 Type: pgwirebase.PreparePortal, 170 }); err != nil { 171 t.Fatal(err) 172 } 173 174 cmdPos++ 175 stmt = mustParseOne("COMMIT") 176 if err != nil { 177 t.Fatal(err) 178 } 179 if err := buf.Push(ctx, ExecStmt{Statement: stmt}); err != nil { 180 t.Fatal(err) 181 } 182 183 cmdPos++ 184 failedDescribePos = cmdPos 185 if err = buf.Push(ctx, DescribeStmt{ 186 Name: "portal1", 187 Type: pgwirebase.PreparePortal, 188 }); err != nil { 189 t.Fatal(err) 190 } 191 192 cmdPos++ 193 if err = buf.Push(ctx, Sync{}); err != nil { 194 t.Fatal(err) 195 } 196 197 results = <-syncResults 198 199 exp := cmdPos + 1 - numResults 200 if len(results) != exp { 201 t.Fatalf("expected %d results, got: %d", exp, len(results)) 202 } 203 succDescIdx := successfulDescribePos - numResults 204 if err := results[succDescIdx].err; err != nil { 205 t.Fatalf("expected first Describe to succeed, got err: %s", err) 206 } 207 failDescIdx := failedDescribePos - numResults 208 if !testutils.IsError(results[failDescIdx].err, "unknown portal") { 209 t.Fatalf("expected error \"unknown portal\", got: %v", results[failDescIdx].err) 210 } 211 212 buf.Close() 213 if err := <-finished; err != nil { 214 t.Fatal(err) 215 } 216 } 217 218 func mustParseOne(s string) parser.Statement { 219 stmts, err := parser.Parse(s) 220 if err != nil { 221 log.Fatalf(context.Background(), "%v", err) 222 } 223 return stmts[0] 224 } 225 226 // startConnExecutor start a goroutine running a connExecutor. This connExecutor 227 // is using a mocked KV that can't really do anything, so it can't run 228 // statements that need to "access the database". It can only execute things 229 // like `SELECT 1`. It's intended for testing interactions with the network 230 // protocol. 231 // 232 // It returns a StmtBuf which is to be used to providing input to the executor, 233 // a channel for getting results after sending Sync commands, a channel that 234 // gets the error from closing down the executor once the StmtBuf is closed, a 235 // stopper that must be stopped when the test completes (this does not stop the 236 // executor but stops other background work). 237 func startConnExecutor( 238 ctx context.Context, 239 ) (*StmtBuf, <-chan []resWithPos, <-chan error, *stop.Stopper, error) { 240 // A lot of boilerplate for creating a connExecutor. 241 stopper := stop.NewStopper() 242 clock := hlc.NewClock(hlc.UnixNano, 0 /* maxOffset */) 243 factory := kv.MakeMockTxnSenderFactory( 244 func(context.Context, *roachpb.Transaction, roachpb.BatchRequest, 245 ) (*roachpb.BatchResponse, *roachpb.Error) { 246 return nil, nil 247 }) 248 db := kv.NewDB(testutils.MakeAmbientCtx(), factory, clock) 249 st := cluster.MakeTestingClusterSettings() 250 nodeID := base.TestingIDContainer 251 distSQLMetrics := execinfra.MakeDistSQLMetrics(time.Hour /* histogramWindow */) 252 gw := gossip.MakeExposedGossip(nil) 253 cfg := &ExecutorConfig{ 254 AmbientCtx: testutils.MakeAmbientCtx(), 255 Settings: st, 256 Clock: clock, 257 DB: db, 258 SessionRegistry: NewSessionRegistry(), 259 NodeInfo: NodeInfo{ 260 NodeID: nodeID, 261 ClusterID: func() uuid.UUID { return uuid.UUID{} }, 262 }, 263 Codec: keys.SystemSQLCodec, 264 DistSQLPlanner: NewDistSQLPlanner( 265 ctx, execinfra.Version, st, roachpb.NodeDescriptor{NodeID: 1}, 266 nil, /* rpcCtx */ 267 distsql.NewServer(ctx, execinfra.ServerConfig{ 268 AmbientContext: testutils.MakeAmbientCtx(), 269 Settings: st, 270 Stopper: stopper, 271 Metrics: &distSQLMetrics, 272 NodeID: nodeID, 273 }), 274 nil, /* distSender */ 275 gw, 276 stopper, 277 func(roachpb.NodeID) (bool, error) { return true, nil }, // everybody is live 278 nil, /* nodeDialer */ 279 ), 280 QueryCache: querycache.New(0), 281 TestingKnobs: ExecutorTestingKnobs{}, 282 StmtDiagnosticsRecorder: stmtdiagnostics.NewRegistry(nil, nil, gw, st), 283 } 284 pool := mon.MakeUnlimitedMonitor( 285 context.Background(), "test", mon.MemoryResource, 286 nil /* curCount */, nil /* maxHist */, math.MaxInt64, st, 287 ) 288 // This pool should never be Stop()ed because, if the test is failing, memory 289 // is not properly released. 290 291 s := NewServer(cfg, &pool) 292 buf := NewStmtBuf() 293 syncResults := make(chan []resWithPos, 1) 294 var cc ClientComm = &internalClientComm{ 295 sync: func(res []resWithPos) { 296 syncResults <- res 297 }, 298 } 299 sqlMetrics := MakeMemMetrics("test" /* endpoint */, time.Second /* histogramWindow */) 300 301 conn, err := s.SetupConn(ctx, SessionArgs{}, buf, cc, sqlMetrics) 302 if err != nil { 303 return nil, nil, nil, nil, err 304 } 305 finished := make(chan error) 306 307 // We're going to run the connExecutor in the background. On the main test 308 // routine, we're going to push commands into the StmtBuf and, from time to 309 // time, collect and check their results. 310 go func() { 311 finished <- s.ServeConn(ctx, conn, mon.BoundAccount{}, nil /* cancel */) 312 }() 313 return buf, syncResults, finished, stopper, nil 314 }