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  }