99 "context"
1010 "hash/fnv"
1111 "testing"
12+ "time"
1213
1314 "github.com/cockroachdb/cockroach/pkg/base"
1415 "github.com/cockroachdb/cockroach/pkg/kv/kvclient/rangefeed"
@@ -21,6 +22,7 @@ import (
2122 "github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
2223 "github.com/cockroachdb/cockroach/pkg/util/leaktest"
2324 "github.com/cockroachdb/cockroach/pkg/util/log"
25+ "github.com/cockroachdb/cockroach/pkg/util/randutil"
2426 "github.com/cockroachdb/errors"
2527 "github.com/stretchr/testify/require"
2628)
@@ -372,6 +374,98 @@ func TestHintCacheMultiTenant(t *testing.T) {
372374 require .Nil (t , hc2 .MaybeGetStatementHints (ctx , fingerprintShared ))
373375}
374376
377+ // TestHintCacheGeneration tests that the cache generation is incremented
378+ // correctly when hints are added and removed, and that the generation is not
379+ // incremented when there are no updates.
380+ func TestHintCacheGeneration (t * testing.T ) {
381+ defer leaktest .AfterTest (t )()
382+ defer log .Scope (t ).Close (t )
383+
384+ ctx := context .Background ()
385+ rnd , _ := randutil .NewTestRand ()
386+ srv , db , _ := serverutils .StartServer (t , base.TestServerArgs {})
387+ defer srv .Stopper ().Stop (ctx )
388+ ts := srv .ApplicationLayer ()
389+ r := sqlutils .MakeSQLRunner (db )
390+ setTestDefaults (t , srv )
391+
392+ // Create a hints cache.
393+ hc := createHintsCache (t , ctx , ts )
394+
395+ // Helper that retrieves the generation and verifies that it doesn't change
396+ // over a short period.
397+ getGenerationAssertNoChange := func () int64 {
398+ t .Helper ()
399+ startGeneration := hc .GetGeneration ()
400+ time .Sleep (time .Duration (rnd .Intn (int (time .Millisecond ))))
401+ require .Equal (t , startGeneration , hc .GetGeneration ())
402+ return startGeneration
403+ }
404+ // Helper that waits until the generation increments from the given start
405+ // point.
406+ waitForGenerationInc := func (prevGeneration int64 ) {
407+ t .Helper ()
408+ testutils .SucceedsSoon (t , func () error {
409+ t .Helper ()
410+ if gen := hc .GetGeneration (); gen <= prevGeneration {
411+ return errors .Errorf ("expected generation >= %d, got %d" , prevGeneration , gen )
412+ }
413+ return nil
414+ })
415+ }
416+
417+ // The initial scan should increment the generation.
418+ waitForGenerationInc (0 )
419+ generationAfterInitialScan := getGenerationAssertNoChange ()
420+
421+ // Insert a hint - generation should increment.
422+ fingerprint1 := "SELECT a FROM t WHERE b = $1"
423+ insertStatementHint (t , r , fingerprint1 )
424+ waitForGenerationInc (generationAfterInitialScan )
425+ waitForUpdateOnFingerprintHash (t , ctx , hc , fingerprint1 , 1 )
426+ generationAfterInsert := getGenerationAssertNoChange ()
427+
428+ // Insert another hint for the same fingerprint - generation should increment
429+ // again.
430+ insertStatementHint (t , r , fingerprint1 )
431+ waitForGenerationInc (generationAfterInsert )
432+ waitForUpdateOnFingerprintHash (t , ctx , hc , fingerprint1 , 2 )
433+ generationAfterSecondInsert := getGenerationAssertNoChange ()
434+
435+ // Add a hint for a different fingerprint - generation should increment.
436+ fingerprint2 := "SELECT c FROM t WHERE d = $1"
437+ insertStatementHint (t , r , fingerprint2 )
438+ waitForGenerationInc (generationAfterSecondInsert )
439+ waitForUpdateOnFingerprintHash (t , ctx , hc , fingerprint2 , 1 )
440+ generationAfterDifferentFingerprint := getGenerationAssertNoChange ()
441+
442+ // Delete one hint - generation should increment.
443+ deleteStatementHints (t , r , fingerprint1 , 1 )
444+ waitForGenerationInc (generationAfterDifferentFingerprint )
445+ waitForUpdateOnFingerprintHash (t , ctx , hc , fingerprint1 , 1 )
446+ generationAfterDelete := getGenerationAssertNoChange ()
447+
448+ // Delete all remaining hints for fingerprint1 - generation should increment.
449+ deleteStatementHints (t , r , fingerprint1 , 0 )
450+ waitForGenerationInc (generationAfterDelete )
451+ waitForUpdateOnFingerprintHash (t , ctx , hc , fingerprint1 , 0 )
452+ generationAfterDeleteAll := getGenerationAssertNoChange ()
453+
454+ // Query for hints (cache access) should NOT increment generation.
455+ hc .MaybeGetStatementHints (ctx , fingerprint2 )
456+ getGenerationAssertNoChange ()
457+
458+ // Accessing a non-existent fingerprint should also NOT increment generation.
459+ hc .MaybeGetStatementHints (ctx , "SELECT nonexistent FROM t" )
460+ getGenerationAssertNoChange ()
461+
462+ // Delete all remaining hints.
463+ deleteStatementHints (t , r , fingerprint2 , 0 )
464+ waitForGenerationInc (generationAfterDeleteAll )
465+ waitForUpdateOnFingerprintHash (t , ctx , hc , fingerprint2 , 0 )
466+ getGenerationAssertNoChange ()
467+ }
468+
375469func setTestDefaults (t * testing.T , srv serverutils.TestServerInterface ) {
376470 // These settings can only be set on the system tenant.
377471 r := sqlutils .MakeSQLRunner (srv .SystemLayer ().SQLConn (t ))
0 commit comments