LCOV - code coverage report
Current view: top level - pebble/tool - sstable.go (source / functions) Hit Total Coverage
Test: 2024-04-21 08:15Z e7c4ec2e - tests + meta.lcov Lines: 374 454 82.4 %
Date: 2024-04-21 08:16:20 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2019 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 tool
       6             : 
       7             : import (
       8             :         "bytes"
       9             :         "fmt"
      10             :         "io"
      11             :         "os"
      12             :         "path/filepath"
      13             :         "slices"
      14             :         "text/tabwriter"
      15             : 
      16             :         "github.com/cockroachdb/pebble"
      17             :         "github.com/cockroachdb/pebble/internal/base"
      18             :         "github.com/cockroachdb/pebble/internal/humanize"
      19             :         "github.com/cockroachdb/pebble/internal/keyspan"
      20             :         "github.com/cockroachdb/pebble/internal/private"
      21             :         "github.com/cockroachdb/pebble/internal/rangedel"
      22             :         "github.com/cockroachdb/pebble/sstable"
      23             :         "github.com/cockroachdb/pebble/vfs"
      24             :         "github.com/spf13/cobra"
      25             : )
      26             : 
      27             : // sstableT implements sstable-level tools, including both configuration state
      28             : // and the commands themselves.
      29             : type sstableT struct {
      30             :         Root       *cobra.Command
      31             :         Check      *cobra.Command
      32             :         Layout     *cobra.Command
      33             :         Properties *cobra.Command
      34             :         Scan       *cobra.Command
      35             :         Space      *cobra.Command
      36             : 
      37             :         // Configuration and state.
      38             :         opts      *pebble.Options
      39             :         comparers sstable.Comparers
      40             :         mergers   sstable.Mergers
      41             : 
      42             :         // Flags.
      43             :         fmtKey   keyFormatter
      44             :         fmtValue valueFormatter
      45             :         start    key
      46             :         end      key
      47             :         filter   key
      48             :         count    int64
      49             :         verbose  bool
      50             : }
      51             : 
      52             : func newSSTable(
      53             :         opts *pebble.Options, comparers sstable.Comparers, mergers sstable.Mergers,
      54           1 : ) *sstableT {
      55           1 :         s := &sstableT{
      56           1 :                 opts:      opts,
      57           1 :                 comparers: comparers,
      58           1 :                 mergers:   mergers,
      59           1 :         }
      60           1 :         s.fmtKey.mustSet("quoted")
      61           1 :         s.fmtValue.mustSet("[%x]")
      62           1 : 
      63           1 :         s.Root = &cobra.Command{
      64           1 :                 Use:   "sstable",
      65           1 :                 Short: "sstable introspection tools",
      66           1 :         }
      67           1 :         s.Check = &cobra.Command{
      68           1 :                 Use:   "check <sstables>",
      69           1 :                 Short: "verify checksums and metadata",
      70           1 :                 Long:  ``,
      71           1 :                 Args:  cobra.MinimumNArgs(1),
      72           1 :                 Run:   s.runCheck,
      73           1 :         }
      74           1 :         s.Layout = &cobra.Command{
      75           1 :                 Use:   "layout <sstables>",
      76           1 :                 Short: "print sstable block and record layout",
      77           1 :                 Long: `
      78           1 : Print the layout for the sstables. The -v flag controls whether record layout
      79           1 : is displayed or omitted.
      80           1 : `,
      81           1 :                 Args: cobra.MinimumNArgs(1),
      82           1 :                 Run:  s.runLayout,
      83           1 :         }
      84           1 :         s.Properties = &cobra.Command{
      85           1 :                 Use:   "properties <sstables>",
      86           1 :                 Short: "print sstable properties",
      87           1 :                 Long: `
      88           1 : Print the properties for the sstables. The -v flag controls whether the
      89           1 : properties are pretty-printed or displayed in a verbose/raw format.
      90           1 : `,
      91           1 :                 Args: cobra.MinimumNArgs(1),
      92           1 :                 Run:  s.runProperties,
      93           1 :         }
      94           1 :         s.Scan = &cobra.Command{
      95           1 :                 Use:   "scan <sstables>",
      96           1 :                 Short: "print sstable records",
      97           1 :                 Long: `
      98           1 : Print the records in the sstables. The sstables are scanned in command line
      99           1 : order which means the records will be printed in that order. Raw range
     100           1 : tombstones are displayed interleaved with point records.
     101           1 : `,
     102           1 :                 Args: cobra.MinimumNArgs(1),
     103           1 :                 Run:  s.runScan,
     104           1 :         }
     105           1 :         s.Space = &cobra.Command{
     106           1 :                 Use:   "space <sstables>",
     107           1 :                 Short: "print filesystem space used",
     108           1 :                 Long: `
     109           1 : Print the estimated space usage in the specified files for the
     110           1 : inclusive-inclusive range specified by --start and --end.
     111           1 : `,
     112           1 :                 Args: cobra.MinimumNArgs(1),
     113           1 :                 Run:  s.runSpace,
     114           1 :         }
     115           1 : 
     116           1 :         s.Root.AddCommand(s.Check, s.Layout, s.Properties, s.Scan, s.Space)
     117           1 :         s.Root.PersistentFlags().BoolVarP(&s.verbose, "verbose", "v", false, "verbose output")
     118           1 : 
     119           1 :         s.Check.Flags().Var(
     120           1 :                 &s.fmtKey, "key", "key formatter")
     121           1 :         s.Layout.Flags().Var(
     122           1 :                 &s.fmtKey, "key", "key formatter")
     123           1 :         s.Layout.Flags().Var(
     124           1 :                 &s.fmtValue, "value", "value formatter")
     125           1 :         s.Scan.Flags().Var(
     126           1 :                 &s.fmtKey, "key", "key formatter")
     127           1 :         s.Scan.Flags().Var(
     128           1 :                 &s.fmtValue, "value", "value formatter")
     129           1 :         for _, cmd := range []*cobra.Command{s.Scan, s.Space} {
     130           1 :                 cmd.Flags().Var(
     131           1 :                         &s.start, "start", "start key for the range")
     132           1 :                 cmd.Flags().Var(
     133           1 :                         &s.end, "end", "end key for the range")
     134           1 :         }
     135           1 :         s.Scan.Flags().Var(
     136           1 :                 &s.filter, "filter", "only output records with matching prefix or overlapping range tombstones")
     137           1 :         s.Scan.Flags().Int64Var(
     138           1 :                 &s.count, "count", 0, "key count for scan (0 is unlimited)")
     139           1 : 
     140           1 :         return s
     141             : }
     142             : 
     143           1 : func (s *sstableT) newReader(f vfs.File) (*sstable.Reader, error) {
     144           1 :         readable, err := sstable.NewSimpleReadable(f)
     145           1 :         if err != nil {
     146           0 :                 return nil, err
     147           0 :         }
     148           1 :         o := sstable.ReaderOptions{
     149           1 :                 Cache:    pebble.NewCache(128 << 20 /* 128 MB */),
     150           1 :                 Comparer: s.opts.Comparer,
     151           1 :                 Filters:  s.opts.Filters,
     152           1 :         }
     153           1 :         defer o.Cache.Unref()
     154           1 :         return sstable.NewReader(readable, o, s.comparers, s.mergers,
     155           1 :                 private.SSTableRawTombstonesOpt.(sstable.ReaderOption))
     156             : }
     157             : 
     158           1 : func (s *sstableT) runCheck(cmd *cobra.Command, args []string) {
     159           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     160           1 :         s.foreachSstable(stderr, args, func(arg string) {
     161           1 :                 f, err := s.opts.FS.Open(arg)
     162           1 :                 if err != nil {
     163           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     164           0 :                         return
     165           0 :                 }
     166             : 
     167           1 :                 fmt.Fprintf(stdout, "%s\n", arg)
     168           1 : 
     169           1 :                 r, err := s.newReader(f)
     170           1 : 
     171           1 :                 if err != nil {
     172           1 :                         fmt.Fprintf(stdout, "%s\n", err)
     173           1 :                         return
     174           1 :                 }
     175           1 :                 defer r.Close()
     176           1 : 
     177           1 :                 // Update the internal formatter if this comparator has one specified.
     178           1 :                 s.fmtKey.setForComparer(r.Properties.ComparerName, s.comparers)
     179           1 :                 s.fmtValue.setForComparer(r.Properties.ComparerName, s.comparers)
     180           1 : 
     181           1 :                 iter, err := r.NewIter(sstable.NoTransforms, nil, nil)
     182           1 :                 if err != nil {
     183           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     184           0 :                         return
     185           0 :                 }
     186             : 
     187             :                 // Verify that SeekPrefixGE can find every key in the table.
     188           1 :                 prefixIter, err := r.NewIter(sstable.NoTransforms, nil, nil)
     189           1 :                 if err != nil {
     190           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     191           0 :                         return
     192           0 :                 }
     193             : 
     194           1 :                 var lastKey base.InternalKey
     195           1 :                 for kv := iter.First(); kv != nil; kv = iter.Next() {
     196           1 :                         if base.InternalCompare(r.Compare, lastKey, kv.K) >= 0 {
     197           1 :                                 fmt.Fprintf(stdout, "WARNING: OUT OF ORDER KEYS!\n")
     198           1 :                                 if s.fmtKey.spec != "null" {
     199           1 :                                         fmt.Fprintf(stdout, "    %s >= %s\n",
     200           1 :                                                 lastKey.Pretty(s.fmtKey.fn), kv.K.Pretty(s.fmtKey.fn))
     201           1 :                                 }
     202             :                         }
     203           1 :                         lastKey.Trailer = kv.K.Trailer
     204           1 :                         lastKey.UserKey = append(lastKey.UserKey[:0], kv.K.UserKey...)
     205           1 : 
     206           1 :                         n := r.Split(kv.K.UserKey)
     207           1 :                         prefix := kv.K.UserKey[:n]
     208           1 :                         kv2 := prefixIter.SeekPrefixGE(prefix, kv.K.UserKey, base.SeekGEFlagsNone)
     209           1 :                         if kv2 == nil {
     210           0 :                                 fmt.Fprintf(stdout, "WARNING: PREFIX ITERATION FAILURE!\n")
     211           0 :                                 if s.fmtKey.spec != "null" {
     212           0 :                                         fmt.Fprintf(stdout, "    %s not found\n", kv.K.Pretty(s.fmtKey.fn))
     213           0 :                                 }
     214             :                         }
     215             :                 }
     216             : 
     217           1 :                 if err := iter.Close(); err != nil {
     218           0 :                         fmt.Fprintf(stdout, "%s\n", err)
     219           0 :                 }
     220           1 :                 if err := prefixIter.Close(); err != nil {
     221           0 :                         fmt.Fprintf(stdout, "%s\n", err)
     222           0 :                 }
     223             :         })
     224             : }
     225             : 
     226           1 : func (s *sstableT) runLayout(cmd *cobra.Command, args []string) {
     227           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     228           1 :         s.foreachSstable(stderr, args, func(arg string) {
     229           1 :                 f, err := s.opts.FS.Open(arg)
     230           1 :                 if err != nil {
     231           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     232           0 :                         return
     233           0 :                 }
     234             : 
     235           1 :                 fmt.Fprintf(stdout, "%s\n", arg)
     236           1 : 
     237           1 :                 r, err := s.newReader(f)
     238           1 :                 if err != nil {
     239           0 :                         fmt.Fprintf(stdout, "%s\n", err)
     240           0 :                         return
     241           0 :                 }
     242           1 :                 defer r.Close()
     243           1 : 
     244           1 :                 // Update the internal formatter if this comparator has one specified.
     245           1 :                 s.fmtKey.setForComparer(r.Properties.ComparerName, s.comparers)
     246           1 :                 s.fmtValue.setForComparer(r.Properties.ComparerName, s.comparers)
     247           1 : 
     248           1 :                 l, err := r.Layout()
     249           1 :                 if err != nil {
     250           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     251           0 :                         return
     252           0 :                 }
     253           1 :                 fmtRecord := func(key *base.InternalKey, value []byte) {
     254           1 :                         formatKeyValue(stdout, s.fmtKey, s.fmtValue, key, value)
     255           1 :                 }
     256           1 :                 if s.fmtKey.spec == "null" && s.fmtValue.spec == "null" {
     257           0 :                         fmtRecord = nil
     258           0 :                 }
     259           1 :                 l.Describe(stdout, s.verbose, r, fmtRecord)
     260             :         })
     261             : }
     262             : 
     263           1 : func (s *sstableT) runProperties(cmd *cobra.Command, args []string) {
     264           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     265           1 :         s.foreachSstable(stderr, args, func(arg string) {
     266           1 :                 f, err := s.opts.FS.Open(arg)
     267           1 :                 if err != nil {
     268           1 :                         fmt.Fprintf(stderr, "%s\n", err)
     269           1 :                         return
     270           1 :                 }
     271             : 
     272           1 :                 fmt.Fprintf(stdout, "%s\n", arg)
     273           1 : 
     274           1 :                 r, err := s.newReader(f)
     275           1 :                 if err != nil {
     276           1 :                         fmt.Fprintf(stdout, "%s\n", err)
     277           1 :                         return
     278           1 :                 }
     279           1 :                 defer r.Close()
     280           1 : 
     281           1 :                 if s.verbose {
     282           1 :                         fmt.Fprintf(stdout, "%s", r.Properties.String())
     283           1 :                         return
     284           1 :                 }
     285             : 
     286           1 :                 stat, err := f.Stat()
     287           1 :                 if err != nil {
     288           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     289           0 :                         return
     290           0 :                 }
     291             : 
     292           1 :                 formatNull := func(s string) string {
     293           1 :                         switch s {
     294           1 :                         case "", "nullptr":
     295           1 :                                 return "-"
     296             :                         }
     297           1 :                         return s
     298             :                 }
     299             : 
     300           1 :                 tw := tabwriter.NewWriter(stdout, 2, 1, 2, ' ', 0)
     301           1 :                 fmt.Fprintf(tw, "size\t\n")
     302           1 :                 fmt.Fprintf(tw, "  file\t%s\n", humanize.Bytes.Int64(stat.Size()))
     303           1 :                 fmt.Fprintf(tw, "  data\t%s\n", humanize.Bytes.Uint64(r.Properties.DataSize))
     304           1 :                 fmt.Fprintf(tw, "    blocks\t%d\n", r.Properties.NumDataBlocks)
     305           1 :                 fmt.Fprintf(tw, "  index\t%s\n", humanize.Bytes.Uint64(r.Properties.IndexSize))
     306           1 :                 fmt.Fprintf(tw, "    blocks\t%d\n", 1+r.Properties.IndexPartitions)
     307           1 :                 fmt.Fprintf(tw, "    top-level\t%s\n", humanize.Bytes.Uint64(r.Properties.TopLevelIndexSize))
     308           1 :                 fmt.Fprintf(tw, "  filter\t%s\n", humanize.Bytes.Uint64(r.Properties.FilterSize))
     309           1 :                 fmt.Fprintf(tw, "  raw-key\t%s\n", humanize.Bytes.Uint64(r.Properties.RawKeySize))
     310           1 :                 fmt.Fprintf(tw, "  raw-value\t%s\n", humanize.Bytes.Uint64(r.Properties.RawValueSize))
     311           1 :                 fmt.Fprintf(tw, "  pinned-key\t%d\n", r.Properties.SnapshotPinnedKeySize)
     312           1 :                 fmt.Fprintf(tw, "  pinned-val\t%d\n", r.Properties.SnapshotPinnedValueSize)
     313           1 :                 fmt.Fprintf(tw, "  point-del-key-size\t%d\n", r.Properties.RawPointTombstoneKeySize)
     314           1 :                 fmt.Fprintf(tw, "  point-del-value-size\t%d\n", r.Properties.RawPointTombstoneValueSize)
     315           1 :                 fmt.Fprintf(tw, "records\t%d\n", r.Properties.NumEntries)
     316           1 :                 fmt.Fprintf(tw, "  set\t%d\n", r.Properties.NumEntries-
     317           1 :                         (r.Properties.NumDeletions+r.Properties.NumMergeOperands))
     318           1 :                 fmt.Fprintf(tw, "  delete\t%d\n", r.Properties.NumPointDeletions())
     319           1 :                 fmt.Fprintf(tw, "  delete-sized\t%d\n", r.Properties.NumSizedDeletions)
     320           1 :                 fmt.Fprintf(tw, "  range-delete\t%d\n", r.Properties.NumRangeDeletions)
     321           1 :                 fmt.Fprintf(tw, "  range-key-set\t%d\n", r.Properties.NumRangeKeySets)
     322           1 :                 fmt.Fprintf(tw, "  range-key-unset\t%d\n", r.Properties.NumRangeKeyUnsets)
     323           1 :                 fmt.Fprintf(tw, "  range-key-delete\t%d\n", r.Properties.NumRangeKeyDels)
     324           1 :                 fmt.Fprintf(tw, "  merge\t%d\n", r.Properties.NumMergeOperands)
     325           1 :                 fmt.Fprintf(tw, "  pinned\t%d\n", r.Properties.SnapshotPinnedKeys)
     326           1 :                 fmt.Fprintf(tw, "index\t\n")
     327           1 :                 fmt.Fprintf(tw, "  key\t")
     328           1 :                 fmt.Fprintf(tw, "  value\t")
     329           1 :                 fmt.Fprintf(tw, "comparer\t%s\n", r.Properties.ComparerName)
     330           1 :                 fmt.Fprintf(tw, "merger\t%s\n", formatNull(r.Properties.MergerName))
     331           1 :                 fmt.Fprintf(tw, "filter\t%s\n", formatNull(r.Properties.FilterPolicyName))
     332           1 :                 fmt.Fprintf(tw, "compression\t%s\n", r.Properties.CompressionName)
     333           1 :                 fmt.Fprintf(tw, "  options\t%s\n", r.Properties.CompressionOptions)
     334           1 :                 fmt.Fprintf(tw, "user properties\t\n")
     335           1 :                 fmt.Fprintf(tw, "  collectors\t%s\n", r.Properties.PropertyCollectorNames)
     336           1 :                 keys := make([]string, 0, len(r.Properties.UserProperties))
     337           1 :                 for key := range r.Properties.UserProperties {
     338           1 :                         keys = append(keys, key)
     339           1 :                 }
     340           1 :                 slices.Sort(keys)
     341           1 :                 for _, key := range keys {
     342           1 :                         fmt.Fprintf(tw, "  %s\t%s\n", key, r.Properties.UserProperties[key])
     343           1 :                 }
     344           1 :                 tw.Flush()
     345             :         })
     346             : }
     347             : 
     348           1 : func (s *sstableT) runScan(cmd *cobra.Command, args []string) {
     349           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     350           1 :         s.foreachSstable(stderr, args, func(arg string) {
     351           1 :                 f, err := s.opts.FS.Open(arg)
     352           1 :                 if err != nil {
     353           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     354           0 :                         return
     355           0 :                 }
     356             : 
     357             :                 // In filter-mode, we prefix ever line that is output with the sstable
     358             :                 // filename.
     359           1 :                 var prefix string
     360           1 :                 if s.filter == nil {
     361           1 :                         fmt.Fprintf(stdout, "%s\n", arg)
     362           1 :                 } else {
     363           1 :                         prefix = fmt.Sprintf("%s: ", arg)
     364           1 :                 }
     365             : 
     366           1 :                 r, err := s.newReader(f)
     367           1 :                 if err != nil {
     368           0 :                         fmt.Fprintf(stdout, "%s%s\n", prefix, err)
     369           0 :                         return
     370           0 :                 }
     371           1 :                 defer r.Close()
     372           1 : 
     373           1 :                 // Update the internal formatter if this comparator has one specified.
     374           1 :                 s.fmtKey.setForComparer(r.Properties.ComparerName, s.comparers)
     375           1 :                 s.fmtValue.setForComparer(r.Properties.ComparerName, s.comparers)
     376           1 : 
     377           1 :                 iter, err := r.NewIter(sstable.NoTransforms, nil, s.end)
     378           1 :                 if err != nil {
     379           0 :                         fmt.Fprintf(stderr, "%s%s\n", prefix, err)
     380           0 :                         return
     381           0 :                 }
     382           1 :                 iterCloser := base.CloseHelper(iter)
     383           1 :                 defer iterCloser.Close()
     384           1 :                 kv := iter.SeekGE(s.start, base.SeekGEFlagsNone)
     385           1 : 
     386           1 :                 // We configured sstable.Reader to return raw tombstones which requires a
     387           1 :                 // bit more work here to put them in a form that can be iterated in
     388           1 :                 // parallel with the point records.
     389           1 :                 rangeDelIter, err := func() (keyspan.FragmentIterator, error) {
     390           1 :                         iter, err := r.NewRawRangeDelIter(sstable.NoTransforms)
     391           1 :                         if err != nil {
     392           0 :                                 return nil, err
     393           0 :                         }
     394           1 :                         if iter == nil {
     395           1 :                                 return keyspan.NewIter(r.Compare, nil), nil
     396           1 :                         }
     397           1 :                         defer iter.Close()
     398           1 : 
     399           1 :                         var tombstones []keyspan.Span
     400           1 :                         t, err := iter.First()
     401           1 :                         for ; t != nil; t, err = iter.Next() {
     402           1 :                                 if s.end != nil && r.Compare(s.end, t.Start) <= 0 {
     403           1 :                                         // The range tombstone lies after the scan range.
     404           1 :                                         continue
     405             :                                 }
     406           1 :                                 if r.Compare(s.start, t.End) >= 0 {
     407           1 :                                         // The range tombstone lies before the scan range.
     408           1 :                                         continue
     409             :                                 }
     410           1 :                                 tombstones = append(tombstones, t.ShallowClone())
     411             :                         }
     412           1 :                         if err != nil {
     413           0 :                                 return nil, err
     414           0 :                         }
     415             : 
     416           1 :                         slices.SortFunc(tombstones, func(a, b keyspan.Span) int {
     417           1 :                                 return r.Compare(a.Start, b.Start)
     418           1 :                         })
     419           1 :                         return keyspan.NewIter(r.Compare, tombstones), nil
     420             :                 }()
     421           1 :                 if err != nil {
     422           0 :                         fmt.Fprintf(stdout, "%s%s\n", prefix, err)
     423           0 :                         return
     424           0 :                 }
     425             : 
     426           1 :                 defer rangeDelIter.Close()
     427           1 :                 rangeDel, err := rangeDelIter.First()
     428           1 :                 if err != nil {
     429           0 :                         fmt.Fprintf(stdout, "%s%s\n", prefix, err)
     430           0 :                         return
     431           0 :                 }
     432           1 :                 count := s.count
     433           1 : 
     434           1 :                 var lastKey base.InternalKey
     435           1 :                 for kv != nil || rangeDel != nil {
     436           1 :                         if kv != nil && (rangeDel == nil || r.Compare(kv.K.UserKey, rangeDel.Start) < 0) {
     437           1 :                                 // The filter specifies a prefix of the key.
     438           1 :                                 //
     439           1 :                                 // TODO(peter): Is using prefix comparison like this kosher for all
     440           1 :                                 // comparers? Probably not, but it is for common ones such as the
     441           1 :                                 // Pebble default and CockroachDB's comparer.
     442           1 :                                 if s.filter == nil || bytes.HasPrefix(kv.K.UserKey, s.filter) {
     443           1 :                                         fmt.Fprint(stdout, prefix)
     444           1 :                                         v, _, err := kv.Value(nil)
     445           1 :                                         if err != nil {
     446           0 :                                                 fmt.Fprintf(stdout, "%s%s\n", prefix, err)
     447           0 :                                                 return
     448           0 :                                         }
     449           1 :                                         formatKeyValue(stdout, s.fmtKey, s.fmtValue, &kv.K, v)
     450             : 
     451             :                                 }
     452           1 :                                 if base.InternalCompare(r.Compare, lastKey, kv.K) >= 0 {
     453           1 :                                         fmt.Fprintf(stdout, "%s    WARNING: OUT OF ORDER KEYS!\n", prefix)
     454           1 :                                 }
     455           1 :                                 lastKey.Trailer = kv.K.Trailer
     456           1 :                                 lastKey.UserKey = append(lastKey.UserKey[:0], kv.K.UserKey...)
     457           1 :                                 kv = iter.Next()
     458           1 :                         } else {
     459           1 :                                 // If a filter is specified, we want to output any range tombstone
     460           1 :                                 // which overlaps the prefix. The comparison on the start key is
     461           1 :                                 // somewhat complex. Consider the tombstone [aaa,ccc). We want to
     462           1 :                                 // output this tombstone if filter is "aa", and if it "bbb".
     463           1 :                                 if s.filter == nil ||
     464           1 :                                         ((r.Compare(s.filter, rangeDel.Start) >= 0 ||
     465           1 :                                                 bytes.HasPrefix(rangeDel.Start, s.filter)) &&
     466           1 :                                                 r.Compare(s.filter, rangeDel.End) < 0) {
     467           1 :                                         fmt.Fprint(stdout, prefix)
     468           1 :                                         if err := rangedel.Encode(rangeDel, func(k base.InternalKey, v []byte) error {
     469           1 :                                                 formatKeyValue(stdout, s.fmtKey, s.fmtValue, &k, v)
     470           1 :                                                 return nil
     471           1 :                                         }); err != nil {
     472           0 :                                                 fmt.Fprintf(stdout, "%s\n", err)
     473           0 :                                                 os.Exit(1)
     474           0 :                                         }
     475             :                                 }
     476           1 :                                 rangeDel, err = rangeDelIter.Next()
     477           1 :                                 if err != nil {
     478           0 :                                         fmt.Fprintf(stdout, "%s\n", err)
     479           0 :                                         os.Exit(1)
     480           0 :                                 }
     481             :                         }
     482             : 
     483           1 :                         if count > 0 {
     484           1 :                                 count--
     485           1 :                                 if count == 0 {
     486           1 :                                         break
     487             :                                 }
     488             :                         }
     489             :                 }
     490             : 
     491             :                 // Handle range keys.
     492           1 :                 rkIter, err := r.NewRawRangeKeyIter(sstable.NoTransforms)
     493           1 :                 if err != nil {
     494           0 :                         fmt.Fprintf(stdout, "%s\n", err)
     495           0 :                         os.Exit(1)
     496           0 :                 }
     497           1 :                 if rkIter != nil {
     498           1 :                         defer rkIter.Close()
     499           1 :                         span, err := rkIter.SeekGE(s.start)
     500           1 :                         for ; span != nil; span, err = rkIter.Next() {
     501           1 :                                 // By default, emit the key, unless there is a filter.
     502           1 :                                 emit := s.filter == nil
     503           1 :                                 // Skip spans that start after the end key (if provided). End keys are
     504           1 :                                 // exclusive, e.g. [a, b), so we consider the interval [b, +inf).
     505           1 :                                 if s.end != nil && r.Compare(span.Start, s.end) >= 0 {
     506           0 :                                         emit = false
     507           0 :                                 }
     508             :                                 // Filters override the provided start / end bounds, if provided.
     509           1 :                                 if s.filter != nil && bytes.HasPrefix(span.Start, s.filter) {
     510           1 :                                         // In filter mode, each line is prefixed with the filename.
     511           1 :                                         fmt.Fprint(stdout, prefix)
     512           1 :                                         emit = true
     513           1 :                                 }
     514           1 :                                 if emit {
     515           1 :                                         formatSpan(stdout, s.fmtKey, s.fmtValue, span)
     516           1 :                                 }
     517             :                         }
     518           1 :                         if err != nil {
     519           0 :                                 fmt.Fprintf(stdout, "%s\n", err)
     520           0 :                                 os.Exit(1)
     521           0 :                         }
     522             :                 }
     523             : 
     524           1 :                 if err := iterCloser.Close(); err != nil {
     525           0 :                         fmt.Fprintf(stdout, "%s\n", err)
     526           0 :                 }
     527             :         })
     528             : }
     529             : 
     530           1 : func (s *sstableT) runSpace(cmd *cobra.Command, args []string) {
     531           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     532           1 :         s.foreachSstable(stderr, args, func(arg string) {
     533           1 :                 f, err := s.opts.FS.Open(arg)
     534           1 :                 if err != nil {
     535           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     536           0 :                         return
     537           0 :                 }
     538           1 :                 r, err := s.newReader(f)
     539           1 :                 if err != nil {
     540           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     541           0 :                         return
     542           0 :                 }
     543           1 :                 defer r.Close()
     544           1 : 
     545           1 :                 bytes, err := r.EstimateDiskUsage(s.start, s.end)
     546           1 :                 if err != nil {
     547           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     548           0 :                         return
     549           0 :                 }
     550           1 :                 fmt.Fprintf(stdout, "%s: %d\n", arg, bytes)
     551             :         })
     552             : }
     553             : 
     554           1 : func (s *sstableT) foreachSstable(stderr io.Writer, args []string, fn func(arg string)) {
     555           1 :         // Loop over args, invoking fn for each file. Each directory is recursively
     556           1 :         // listed and fn is invoked on any file with an .sst or .ldb suffix.
     557           1 :         for _, arg := range args {
     558           1 :                 info, err := s.opts.FS.Stat(arg)
     559           1 :                 if err != nil || !info.IsDir() {
     560           1 :                         fn(arg)
     561           1 :                         continue
     562             :                 }
     563           1 :                 walk(stderr, s.opts.FS, arg, func(path string) {
     564           1 :                         switch filepath.Ext(path) {
     565           1 :                         case ".sst", ".ldb":
     566           1 :                                 fn(path)
     567             :                         }
     568             :                 })
     569             :         }
     570             : }

Generated by: LCOV version 1.14