github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/temporary_schema_test.go (about)

     1  // Copyright 2020 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  
    11  package sql
    12  
    13  import (
    14  	"context"
    15  	gosql "database/sql"
    16  	"fmt"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/base"
    22  	"github.com/cockroachdb/cockroach/pkg/kv"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sessiondata"
    24  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    25  	"github.com/cockroachdb/cockroach/pkg/sql/tests"
    26  	"github.com/cockroachdb/cockroach/pkg/testutils"
    27  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    28  	"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
    29  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    30  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    31  	"github.com/cockroachdb/errors"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  func TestCleanupSchemaObjects(t *testing.T) {
    37  	defer leaktest.AfterTest(t)()
    38  
    39  	ctx := context.Background()
    40  	params, _ := tests.CreateTestServerParams()
    41  	s, db, kvDB := serverutils.StartServer(t, params)
    42  	defer s.Stopper().Stop(ctx)
    43  
    44  	conn, err := db.Conn(ctx)
    45  	require.NoError(t, err)
    46  
    47  	_, err = conn.ExecContext(ctx, `
    48  SET experimental_enable_temp_tables=true;
    49  SET serial_normalization='sql_sequence';
    50  CREATE TEMP TABLE a (a SERIAL, c INT);
    51  ALTER TABLE a ADD COLUMN b SERIAL;
    52  CREATE TEMP SEQUENCE a_sequence;
    53  CREATE TEMP VIEW a_view AS SELECT a FROM a;
    54  CREATE TABLE perm_table (a int DEFAULT nextval('a_sequence'), b int);
    55  INSERT INTO perm_table VALUES (DEFAULT, 1);
    56  `)
    57  	require.NoError(t, err)
    58  
    59  	rows, err := conn.QueryContext(ctx, `SELECT id, name FROM system.namespace`)
    60  	require.NoError(t, err)
    61  
    62  	namesToID := make(map[string]sqlbase.ID)
    63  	var schemaName string
    64  	for rows.Next() {
    65  		var id int64
    66  		var name string
    67  		err := rows.Scan(&id, &name)
    68  		require.NoError(t, err)
    69  
    70  		namesToID[name] = sqlbase.ID(id)
    71  		if strings.HasPrefix(name, sessiondata.PgTempSchemaName) {
    72  			schemaName = name
    73  		}
    74  	}
    75  
    76  	require.NotEqual(t, "", schemaName)
    77  
    78  	tempNames := []string{
    79  		"a",
    80  		"a_view",
    81  		"a_sequence",
    82  		"a_a_seq",
    83  		"a_b_seq",
    84  	}
    85  	selectableTempNames := []string{"a", "a_view"}
    86  	for _, name := range append(tempNames, schemaName) {
    87  		require.Contains(t, namesToID, name)
    88  	}
    89  	for _, name := range selectableTempNames {
    90  		// Check tables are accessible.
    91  		_, err = conn.QueryContext(ctx, fmt.Sprintf("SELECT * FROM %s.%s", schemaName, name))
    92  		require.NoError(t, err)
    93  	}
    94  
    95  	require.NoError(
    96  		t,
    97  		kvDB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error {
    98  			execCfg := s.ExecutorConfig().(ExecutorConfig)
    99  			err = cleanupSchemaObjects(
   100  				ctx,
   101  				execCfg.Settings,
   102  				txn,
   103  				execCfg.Codec,
   104  				s.InternalExecutor().(*InternalExecutor),
   105  				namesToID["defaultdb"],
   106  				schemaName,
   107  			)
   108  			require.NoError(t, err)
   109  			return nil
   110  		}),
   111  	)
   112  
   113  	for _, name := range selectableTempNames {
   114  		// Ensure all the entries for the given temporary structures are gone.
   115  		// This can take a longer amount of time if the job takes time / lease doesn't expire in time.
   116  		testutils.SucceedsSoon(t, func() error {
   117  			_, err := conn.QueryContext(ctx, fmt.Sprintf("SELECT * FROM %s.%s", schemaName, name))
   118  			if err != nil {
   119  				if !strings.Contains(err.Error(), fmt.Sprintf(`relation "%s.%s" does not exist`, schemaName, name)) {
   120  					return errors.Errorf("expected %s.%s error to resolve relation not existing", schemaName, name)
   121  				}
   122  				return nil //nolint:returnerrcheck
   123  			}
   124  			return errors.Errorf("expected %s.%s to be deleted", schemaName, name)
   125  		})
   126  	}
   127  
   128  	// Check perm_table performs correctly, and has the right schema.
   129  	_, err = db.Query("SELECT * FROM perm_table")
   130  	require.NoError(t, err)
   131  
   132  	var colDefault gosql.NullString
   133  	err = db.QueryRow(
   134  		`SELECT column_default FROM information_schema.columns
   135  		WHERE table_name = 'perm_table' and column_name = 'a'`,
   136  	).Scan(&colDefault)
   137  	require.NoError(t, err)
   138  	assert.False(t, colDefault.Valid)
   139  }
   140  
   141  func TestTemporaryObjectCleaner(t *testing.T) {
   142  	defer leaktest.AfterTest(t)()
   143  
   144  	numNodes := 3
   145  	ch := make(chan time.Time)
   146  	finishedCh := make(chan struct{})
   147  	knobs := base.TestingKnobs{
   148  		SQLExecutor: &ExecutorTestingKnobs{
   149  			DisableTempObjectsCleanupOnSessionExit: true,
   150  			TempObjectsCleanupCh:                   ch,
   151  			OnTempObjectsCleanupDone: func() {
   152  				finishedCh <- struct{}{}
   153  			},
   154  		},
   155  	}
   156  	tc := serverutils.StartTestCluster(
   157  		t,
   158  		numNodes,
   159  		base.TestClusterArgs{
   160  			ServerArgs: base.TestServerArgs{
   161  				UseDatabase: "defaultdb",
   162  				Knobs:       knobs,
   163  			},
   164  		},
   165  	)
   166  	defer tc.Stopper().Stop(context.Background())
   167  
   168  	// Start and close two temporary schemas.
   169  	for _, dbID := range []int{0, 1} {
   170  		db := tc.ServerConn(dbID)
   171  		sqlDB := sqlutils.MakeSQLRunner(db)
   172  		sqlDB.Exec(t, `SET experimental_enable_temp_tables=true`)
   173  		sqlDB.Exec(t, `CREATE TEMP TABLE t (x INT)`)
   174  		// Close the client connection. Normally the temporary data would immediately
   175  		// be cleaned up on session exit, but this is disabled via the
   176  		// DisableTempObjectsCleanupOnSessionExit testing knob.
   177  		require.NoError(t, db.Close())
   178  	}
   179  
   180  	// Sanity check: there should still be all temporary schemas present from above.
   181  	// dbConn 2 should have a db connection living longer, which we don't delete.
   182  	db := tc.ServerConn(2)
   183  	sqlDB := sqlutils.MakeSQLRunner(db)
   184  	sqlDB.Exec(t, `SET experimental_enable_temp_tables=true`)
   185  	sqlDB.Exec(t, `CREATE TEMP TABLE t (x INT); INSERT INTO t VALUES (1)`)
   186  	tempSchemaQuery := `SELECT count(*) FROM system.namespace WHERE name LIKE 'pg_temp%'`
   187  	var tempSchemaCount int
   188  	sqlDB.QueryRow(t, tempSchemaQuery).Scan(&tempSchemaCount)
   189  	require.Equal(t, tempSchemaCount, 3)
   190  
   191  	// Verify that the asynchronous cleanup job kicks in and removes the temporary
   192  	// data.
   193  	testutils.SucceedsSoon(t, func() error {
   194  		// Now force a cleanup run (by default, it is every 30mins).
   195  		// Send this to every node, in case one is not the leaseholder.
   196  		// This needs to be sent on each run, in case the lease master
   197  		// has not been decided.
   198  		for i := 0; i < numNodes; i++ {
   199  			ch <- timeutil.Now()
   200  		}
   201  		// Block until all nodes have responded.
   202  		// This prevents the stress tests running into #28033, where
   203  		// ListSessions races with the QueryRow.
   204  		for i := 0; i < numNodes; i++ {
   205  			<-finishedCh
   206  		}
   207  		sqlDB.QueryRow(t, tempSchemaQuery).Scan(&tempSchemaCount)
   208  		if tempSchemaCount != 1 {
   209  			return errors.Errorf("expected 1 temp schemas, found %d", tempSchemaCount)
   210  		}
   211  		return nil
   212  	})
   213  	var tRowCount int
   214  	sqlDB.QueryRow(t, "SELECT count(*) FROM t").Scan(&tRowCount)
   215  	require.Equal(t, 1, tRowCount)
   216  	require.NoError(t, db.Close())
   217  }
   218  
   219  // TestTemporarySchemaDropDatabase tests having a temporary schema on one session
   220  // whilst dropping a database on another session will have the database drop
   221  // succeed.
   222  func TestTemporarySchemaDropDatabase(t *testing.T) {
   223  	defer leaktest.AfterTest(t)()
   224  
   225  	numNodes := 3
   226  	tc := serverutils.StartTestCluster(
   227  		t,
   228  		numNodes,
   229  		base.TestClusterArgs{
   230  			ServerArgs: base.TestServerArgs{
   231  				UseDatabase: "defaultdb",
   232  			},
   233  		},
   234  	)
   235  	defer tc.Stopper().Stop(context.Background())
   236  
   237  	// Create a database to drop that has a temporary table inside.
   238  	{
   239  		db := tc.ServerConn(0)
   240  		sqlDB := sqlutils.MakeSQLRunner(db)
   241  		sqlDB.Exec(t, `CREATE DATABASE drop_me`)
   242  		sqlDB.Exec(t, `USE drop_me`)
   243  		sqlDB.Exec(t, `SET experimental_enable_temp_tables=true`)
   244  		sqlDB.Exec(t, `CREATE TEMP TABLE t (x INT)`)
   245  	}
   246  
   247  	// On another session, only leave the schema behind.
   248  	{
   249  		db := tc.ServerConn(1)
   250  		sqlDB := sqlutils.MakeSQLRunner(db)
   251  		sqlDB.Exec(t, `USE drop_me`)
   252  		sqlDB.Exec(t, `SET experimental_enable_temp_tables=true`)
   253  		sqlDB.Exec(t, `CREATE TEMP TABLE t2 (x INT)`)
   254  		sqlDB.Exec(t, `DROP TABLE t2`)
   255  	}
   256  
   257  	// On another session, drop the database.
   258  	{
   259  		db := tc.ServerConn(2)
   260  		sqlDB := sqlutils.MakeSQLRunner(db)
   261  		sqlDB.Exec(t, `DROP DATABASE drop_me CASCADE`)
   262  
   263  		var tempObjectCount int
   264  		sqlDB.QueryRow(
   265  			t,
   266  			`SELECT count(1) FROM system.namespace WHERE name LIKE 'pg_temp%' OR name IN ('t', 't2')`,
   267  		).Scan(&tempObjectCount)
   268  		assert.Equal(t, 0, tempObjectCount)
   269  	}
   270  }