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

Generated by: LCOV version 1.14