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 }