github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/manifest/version_test.go (about)

     1  // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package manifest
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  
    14  	"github.com/cockroachdb/datadriven"
    15  	"github.com/cockroachdb/pebble/internal/base"
    16  	"github.com/cockroachdb/pebble/internal/testkeys"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func levelMetadata(level int, files ...*FileMetadata) LevelMetadata {
    21  	return makeLevelMetadata(base.DefaultComparer.Compare, level, files)
    22  }
    23  
    24  func ikey(s string) InternalKey {
    25  	return base.MakeInternalKey([]byte(s), 0, base.InternalKeyKindSet)
    26  }
    27  
    28  func TestIkeyRange(t *testing.T) {
    29  	cmp := base.DefaultComparer.Compare
    30  	testCases := []struct {
    31  		input, want string
    32  	}{
    33  		{
    34  			"",
    35  			"-",
    36  		},
    37  		{
    38  			"a-e",
    39  			"a-e",
    40  		},
    41  		{
    42  			"a-e a-e",
    43  			"a-e",
    44  		},
    45  		{
    46  			"c-g a-e",
    47  			"a-g",
    48  		},
    49  		{
    50  			"a-e c-g a-e",
    51  			"a-g",
    52  		},
    53  		{
    54  			"b-d f-g",
    55  			"b-g",
    56  		},
    57  		{
    58  			"d-e b-d",
    59  			"b-e",
    60  		},
    61  		{
    62  			"e-e",
    63  			"e-e",
    64  		},
    65  		{
    66  			"f-g e-e d-e c-g b-d a-e",
    67  			"a-g",
    68  		},
    69  	}
    70  	for _, tc := range testCases {
    71  		var f []*FileMetadata
    72  		if tc.input != "" {
    73  			for i, s := range strings.Split(tc.input, " ") {
    74  				m := (&FileMetadata{
    75  					FileNum: base.FileNum(i),
    76  				}).ExtendPointKeyBounds(cmp, ikey(s[0:1]), ikey(s[2:3]))
    77  				m.InitPhysicalBacking()
    78  				f = append(f, m)
    79  			}
    80  		}
    81  		levelMetadata := makeLevelMetadata(base.DefaultComparer.Compare, 0, f)
    82  
    83  		sm, la := KeyRange(base.DefaultComparer.Compare, levelMetadata.Iter())
    84  		got := string(sm.UserKey) + "-" + string(la.UserKey)
    85  		if got != tc.want {
    86  			t.Errorf("KeyRange(%q) = %q, %q", tc.input, got, tc.want)
    87  		}
    88  	}
    89  }
    90  
    91  func TestOverlaps(t *testing.T) {
    92  	var v *Version
    93  	cmp := testkeys.Comparer.Compare
    94  	fmtKey := testkeys.Comparer.FormatKey
    95  	datadriven.RunTest(t, "testdata/overlaps", func(t *testing.T, d *datadriven.TestData) string {
    96  		switch d.Cmd {
    97  		case "define":
    98  			var err error
    99  			v, err = ParseVersionDebug(cmp, fmtKey, 64>>10 /* flush split bytes */, d.Input)
   100  			if err != nil {
   101  				return err.Error()
   102  			}
   103  			return v.String()
   104  		case "overlaps":
   105  			var level int
   106  			var start, end string
   107  			var exclusiveEnd bool
   108  			d.ScanArgs(t, "level", &level)
   109  			d.ScanArgs(t, "start", &start)
   110  			d.ScanArgs(t, "end", &end)
   111  			d.ScanArgs(t, "exclusive-end", &exclusiveEnd)
   112  			overlaps := v.Overlaps(level, testkeys.Comparer.Compare, []byte(start), []byte(end), exclusiveEnd)
   113  			var buf bytes.Buffer
   114  			fmt.Fprintf(&buf, "%d files:\n", overlaps.Len())
   115  			overlaps.Each(func(f *FileMetadata) {
   116  				fmt.Fprintf(&buf, "%s\n", f.DebugString(base.DefaultFormatter, false))
   117  			})
   118  			return buf.String()
   119  		default:
   120  			return fmt.Sprintf("unknown command: %s", d.Cmd)
   121  		}
   122  	})
   123  }
   124  
   125  func TestContains(t *testing.T) {
   126  	cmp := base.DefaultComparer.Compare
   127  	newFileMeta := func(fileNum base.FileNum, size uint64, smallest, largest base.InternalKey) *FileMetadata {
   128  		m := (&FileMetadata{
   129  			FileNum: fileNum,
   130  			Size:    size,
   131  		}).ExtendPointKeyBounds(cmp, smallest, largest)
   132  		m.InitPhysicalBacking()
   133  		return m
   134  	}
   135  	m00 := newFileMeta(
   136  		700,
   137  		1,
   138  		base.ParseInternalKey("b.SET.7008"),
   139  		base.ParseInternalKey("e.SET.7009"),
   140  	)
   141  	m01 := newFileMeta(
   142  		701,
   143  		1,
   144  		base.ParseInternalKey("c.SET.7018"),
   145  		base.ParseInternalKey("f.SET.7019"),
   146  	)
   147  	m02 := newFileMeta(
   148  		702,
   149  		1,
   150  		base.ParseInternalKey("f.SET.7028"),
   151  		base.ParseInternalKey("g.SET.7029"),
   152  	)
   153  	m03 := newFileMeta(
   154  		703,
   155  		1,
   156  		base.ParseInternalKey("x.SET.7038"),
   157  		base.ParseInternalKey("y.SET.7039"),
   158  	)
   159  	m04 := newFileMeta(
   160  		704,
   161  		1,
   162  		base.ParseInternalKey("n.SET.7048"),
   163  		base.ParseInternalKey("p.SET.7049"),
   164  	)
   165  	m05 := newFileMeta(
   166  		705,
   167  		1,
   168  		base.ParseInternalKey("p.SET.7058"),
   169  		base.ParseInternalKey("p.SET.7059"),
   170  	)
   171  	m06 := newFileMeta(
   172  		706,
   173  		1,
   174  		base.ParseInternalKey("p.SET.7068"),
   175  		base.ParseInternalKey("u.SET.7069"),
   176  	)
   177  	m07 := newFileMeta(
   178  		707,
   179  		1,
   180  		base.ParseInternalKey("r.SET.7078"),
   181  		base.ParseInternalKey("s.SET.7079"),
   182  	)
   183  
   184  	m10 := newFileMeta(
   185  		710,
   186  		1,
   187  		base.ParseInternalKey("d.SET.7108"),
   188  		base.ParseInternalKey("g.SET.7109"),
   189  	)
   190  	m11 := newFileMeta(
   191  		711,
   192  		1,
   193  		base.ParseInternalKey("g.SET.7118"),
   194  		base.ParseInternalKey("j.SET.7119"),
   195  	)
   196  	m12 := newFileMeta(
   197  		712,
   198  		1,
   199  		base.ParseInternalKey("n.SET.7128"),
   200  		base.ParseInternalKey("p.SET.7129"),
   201  	)
   202  	m13 := newFileMeta(
   203  		713,
   204  		1,
   205  		base.ParseInternalKey("p.SET.7148"),
   206  		base.ParseInternalKey("p.SET.7149"),
   207  	)
   208  	m14 := newFileMeta(
   209  		714,
   210  		1,
   211  		base.ParseInternalKey("p.SET.7138"),
   212  		base.ParseInternalKey("u.SET.7139"),
   213  	)
   214  
   215  	v := Version{
   216  		Levels: [NumLevels]LevelMetadata{
   217  			0: levelMetadata(0, m00, m01, m02, m03, m04, m05, m06, m07),
   218  			1: levelMetadata(1, m10, m11, m12, m13, m14),
   219  		},
   220  	}
   221  
   222  	testCases := []struct {
   223  		level int
   224  		file  *FileMetadata
   225  		want  bool
   226  	}{
   227  		// Level 0: m00=b-e, m01=c-f, m02=f-g, m03=x-y, m04=n-p, m05=p-p, m06=p-u, m07=r-s.
   228  		// Note that:
   229  		//   - the slice isn't sorted (e.g. m02=f-g, m03=x-y, m04=n-p),
   230  		//   - m00 and m01 overlap (not just touch),
   231  		//   - m06 contains m07,
   232  		//   - m00, m01 and m02 transitively overlap/touch each other, and
   233  		//   - m04, m05, m06 and m07 transitively overlap/touch each other.
   234  		{0, m00, true},
   235  		{0, m01, true},
   236  		{0, m02, true},
   237  		{0, m03, true},
   238  		{0, m04, true},
   239  		{0, m05, true},
   240  		{0, m06, true},
   241  		{0, m07, true},
   242  		{0, m10, false},
   243  		{0, m11, false},
   244  		{0, m12, false},
   245  		{0, m13, false},
   246  		{0, m14, false},
   247  		{1, m00, false},
   248  		{1, m01, false},
   249  		{1, m02, false},
   250  		{1, m03, false},
   251  		{1, m04, false},
   252  		{1, m05, false},
   253  		{1, m06, false},
   254  		{1, m07, false},
   255  		{1, m10, true},
   256  		{1, m11, true},
   257  		{1, m12, true},
   258  		{1, m13, true},
   259  		{1, m14, true},
   260  
   261  		// Level 2: empty.
   262  		{2, m00, false},
   263  		{2, m14, false},
   264  	}
   265  
   266  	for _, tc := range testCases {
   267  		got := v.Contains(tc.level, cmp, tc.file)
   268  		if got != tc.want {
   269  			t.Errorf("level=%d, file=%s\ngot %t\nwant %t", tc.level, tc.file, got, tc.want)
   270  		}
   271  	}
   272  }
   273  
   274  func TestVersionUnref(t *testing.T) {
   275  	list := &VersionList{}
   276  	list.Init(&sync.Mutex{})
   277  	v := &Version{Deleted: func([]*FileBacking) {}}
   278  	v.Ref()
   279  	list.PushBack(v)
   280  	v.Unref()
   281  	if !list.Empty() {
   282  		t.Fatalf("expected version list to be empty")
   283  	}
   284  }
   285  
   286  func TestCheckOrdering(t *testing.T) {
   287  	cmp := base.DefaultComparer.Compare
   288  	fmtKey := base.DefaultComparer.FormatKey
   289  	datadriven.RunTest(t, "testdata/version_check_ordering",
   290  		func(t *testing.T, d *datadriven.TestData) string {
   291  			switch d.Cmd {
   292  			case "check-ordering":
   293  				orderingInvariants := ProhibitSplitUserKeys
   294  				if d.HasArg("allow-split-user-keys") {
   295  					orderingInvariants = AllowSplitUserKeys
   296  				}
   297  				v, err := ParseVersionDebug(cmp, fmtKey, 10<<20, d.Input)
   298  				if err != nil {
   299  					return err.Error()
   300  				}
   301  				// L0 files compare on sequence numbers. Use the seqnums from the
   302  				// smallest / largest bounds for the table.
   303  				v.Levels[0].Slice().Each(func(m *FileMetadata) {
   304  					m.SmallestSeqNum = m.Smallest.SeqNum()
   305  					m.LargestSeqNum = m.Largest.SeqNum()
   306  				})
   307  				if err = v.CheckOrdering(cmp, base.DefaultFormatter, orderingInvariants); err != nil {
   308  					return err.Error()
   309  				}
   310  				return "OK"
   311  
   312  			default:
   313  				return fmt.Sprintf("unknown command: %s", d.Cmd)
   314  			}
   315  		})
   316  }
   317  
   318  func TestExtendBounds(t *testing.T) {
   319  	cmp := base.DefaultComparer.Compare
   320  	parseBounds := func(line string) (lower, upper InternalKey) {
   321  		parts := strings.Split(line, "-")
   322  		if len(parts) == 1 {
   323  			parts = strings.Split(parts[0], ":")
   324  			start, end := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
   325  			lower = base.ParseInternalKey(start)
   326  			switch k := lower.Kind(); k {
   327  			case base.InternalKeyKindRangeDelete:
   328  				upper = base.MakeRangeDeleteSentinelKey([]byte(end))
   329  			case base.InternalKeyKindRangeKeySet, base.InternalKeyKindRangeKeyUnset, base.InternalKeyKindRangeKeyDelete:
   330  				upper = base.MakeExclusiveSentinelKey(k, []byte(end))
   331  			default:
   332  				panic(fmt.Sprintf("unknown kind %s with end key", k))
   333  			}
   334  		} else {
   335  			l, u := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
   336  			lower, upper = base.ParseInternalKey(l), base.ParseInternalKey(u)
   337  		}
   338  		return
   339  	}
   340  	format := func(m *FileMetadata) string {
   341  		var b bytes.Buffer
   342  		var smallest, largest string
   343  		switch m.boundTypeSmallest {
   344  		case boundTypePointKey:
   345  			smallest = "point"
   346  		case boundTypeRangeKey:
   347  			smallest = "range"
   348  		default:
   349  			return fmt.Sprintf("unknown bound type %d", m.boundTypeSmallest)
   350  		}
   351  		switch m.boundTypeLargest {
   352  		case boundTypePointKey:
   353  			largest = "point"
   354  		case boundTypeRangeKey:
   355  			largest = "range"
   356  		default:
   357  			return fmt.Sprintf("unknown bound type %d", m.boundTypeLargest)
   358  		}
   359  		bounds, err := m.boundsMarker()
   360  		if err != nil {
   361  			panic(err)
   362  		}
   363  		fmt.Fprintf(&b, "%s\n", m.DebugString(base.DefaultFormatter, true))
   364  		fmt.Fprintf(&b, "  bounds: (smallest=%s,largest=%s) (0x%08b)\n", smallest, largest, bounds)
   365  		return b.String()
   366  	}
   367  	m := &FileMetadata{}
   368  	datadriven.RunTest(t, "testdata/file_metadata_bounds", func(t *testing.T, d *datadriven.TestData) string {
   369  		switch d.Cmd {
   370  		case "reset":
   371  			m = &FileMetadata{}
   372  			return ""
   373  		case "extend-point-key-bounds":
   374  			u, l := parseBounds(d.Input)
   375  			m.ExtendPointKeyBounds(cmp, u, l)
   376  			return format(m)
   377  		case "extend-range-key-bounds":
   378  			u, l := parseBounds(d.Input)
   379  			m.ExtendRangeKeyBounds(cmp, u, l)
   380  			return format(m)
   381  		default:
   382  			return fmt.Sprintf("unknown command %s\n", d.Cmd)
   383  		}
   384  	})
   385  }
   386  
   387  func TestFileMetadata_ParseRoundTrip(t *testing.T) {
   388  	testCases := []struct {
   389  		name   string
   390  		input  string
   391  		output string
   392  	}{
   393  		{
   394  			name:  "point keys only",
   395  			input: "000001:[a#0,SET-z#0,DEL] seqnums:[0-0] points:[a#0,SET-z#0,DEL]",
   396  		},
   397  		{
   398  			name:  "range keys only",
   399  			input: "000001:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL] seqnums:[0-0] ranges:[a#0,RANGEKEYSET-z#0,RANGEKEYDEL]",
   400  		},
   401  		{
   402  			name:  "point and range keys",
   403  			input: "000001:[a#0,RANGEKEYSET-d#0,DEL] seqnums:[0-0] points:[b#0,SET-d#0,DEL] ranges:[a#0,RANGEKEYSET-c#0,RANGEKEYDEL]",
   404  		},
   405  		{
   406  			name:  "point and range keys with nonzero senums",
   407  			input: "000001:[a#3,RANGEKEYSET-d#4,DEL] seqnums:[3-7] points:[b#3,SET-d#4,DEL] ranges:[a#3,RANGEKEYSET-c#5,RANGEKEYDEL]",
   408  		},
   409  		{
   410  			name:   "whitespace",
   411  			input:  " 000001 : [ a#0,SET - z#0,DEL] points : [ a#0,SET - z#0,DEL] ",
   412  			output: "000001:[a#0,SET-z#0,DEL] seqnums:[0-0] points:[a#0,SET-z#0,DEL]",
   413  		},
   414  	}
   415  	for _, tc := range testCases {
   416  		t.Run(tc.name, func(t *testing.T) {
   417  			m, err := ParseFileMetadataDebug(tc.input)
   418  			require.NoError(t, err)
   419  			err = m.Validate(base.DefaultComparer.Compare, base.DefaultFormatter)
   420  			require.NoError(t, err)
   421  			got := m.DebugString(base.DefaultFormatter, true)
   422  			want := tc.input
   423  			if tc.output != "" {
   424  				want = tc.output
   425  			}
   426  			require.Equal(t, want, got)
   427  		})
   428  	}
   429  }