LCOV - code coverage report
Current view: top level - pebble/tool - sstable.go (source / functions) Hit Total Coverage
Test: 2023-09-22 08:17Z d038189d - tests only.lcov Lines: 374 444 84.2 %
Date: 2023-09-22 08:17:55 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             :         "sort"
      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(nil, nil)
     182           1 :                 if err != nil {
     183           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     184           0 :                         return
     185           0 :                 }
     186             : 
     187             :                 // If a split function is defined for the comparer, verify that
     188             :                 // SeekPrefixGE can find every key in the table.
     189           1 :                 var prefixIter sstable.Iterator
     190           1 :                 if r.Split != nil {
     191           1 :                         var err error
     192           1 :                         prefixIter, err = r.NewIter(nil, nil)
     193           1 :                         if err != nil {
     194           0 :                                 fmt.Fprintf(stderr, "%s\n", err)
     195           0 :                                 return
     196           0 :                         }
     197             :                 }
     198             : 
     199           1 :                 var lastKey base.InternalKey
     200           1 :                 for key, _ := iter.First(); key != nil; key, _ = iter.Next() {
     201           1 :                         if base.InternalCompare(r.Compare, lastKey, *key) >= 0 {
     202           1 :                                 fmt.Fprintf(stdout, "WARNING: OUT OF ORDER KEYS!\n")
     203           1 :                                 if s.fmtKey.spec != "null" {
     204           1 :                                         fmt.Fprintf(stdout, "    %s >= %s\n",
     205           1 :                                                 lastKey.Pretty(s.fmtKey.fn), key.Pretty(s.fmtKey.fn))
     206           1 :                                 }
     207             :                         }
     208           1 :                         lastKey.Trailer = key.Trailer
     209           1 :                         lastKey.UserKey = append(lastKey.UserKey[:0], key.UserKey...)
     210           1 : 
     211           1 :                         if prefixIter != nil {
     212           1 :                                 n := r.Split(key.UserKey)
     213           1 :                                 prefix := key.UserKey[:n]
     214           1 :                                 key2, _ := prefixIter.SeekPrefixGE(prefix, key.UserKey, base.SeekGEFlagsNone)
     215           1 :                                 if key2 == nil {
     216           0 :                                         fmt.Fprintf(stdout, "WARNING: PREFIX ITERATION FAILURE!\n")
     217           0 :                                         if s.fmtKey.spec != "null" {
     218           0 :                                                 fmt.Fprintf(stdout, "    %s not found\n", key.Pretty(s.fmtKey.fn))
     219           0 :                                         }
     220             :                                 }
     221             :                         }
     222             :                 }
     223             : 
     224           1 :                 if err := iter.Close(); err != nil {
     225           1 :                         fmt.Fprintf(stdout, "%s\n", err)
     226           1 :                 }
     227           1 :                 if prefixIter != nil {
     228           1 :                         if err := prefixIter.Close(); err != nil {
     229           0 :                                 fmt.Fprintf(stdout, "%s\n", err)
     230           0 :                         }
     231             :                 }
     232             :         })
     233             : }
     234             : 
     235           1 : func (s *sstableT) runLayout(cmd *cobra.Command, args []string) {
     236           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     237           1 :         s.foreachSstable(stderr, args, func(arg string) {
     238           1 :                 f, err := s.opts.FS.Open(arg)
     239           1 :                 if err != nil {
     240           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     241           0 :                         return
     242           0 :                 }
     243             : 
     244           1 :                 fmt.Fprintf(stdout, "%s\n", arg)
     245           1 : 
     246           1 :                 r, err := s.newReader(f)
     247           1 :                 if err != nil {
     248           0 :                         fmt.Fprintf(stdout, "%s\n", err)
     249           0 :                         return
     250           0 :                 }
     251           1 :                 defer r.Close()
     252           1 : 
     253           1 :                 // Update the internal formatter if this comparator has one specified.
     254           1 :                 s.fmtKey.setForComparer(r.Properties.ComparerName, s.comparers)
     255           1 :                 s.fmtValue.setForComparer(r.Properties.ComparerName, s.comparers)
     256           1 : 
     257           1 :                 l, err := r.Layout()
     258           1 :                 if err != nil {
     259           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     260           0 :                         return
     261           0 :                 }
     262           1 :                 fmtRecord := func(key *base.InternalKey, value []byte) {
     263           1 :                         formatKeyValue(stdout, s.fmtKey, s.fmtValue, key, value)
     264           1 :                 }
     265           1 :                 if s.fmtKey.spec == "null" && s.fmtValue.spec == "null" {
     266           0 :                         fmtRecord = nil
     267           0 :                 }
     268           1 :                 l.Describe(stdout, s.verbose, r, fmtRecord)
     269             :         })
     270             : }
     271             : 
     272           1 : func (s *sstableT) runProperties(cmd *cobra.Command, args []string) {
     273           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     274           1 :         s.foreachSstable(stderr, args, func(arg string) {
     275           1 :                 f, err := s.opts.FS.Open(arg)
     276           1 :                 if err != nil {
     277           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     278           0 :                         return
     279           0 :                 }
     280             : 
     281           1 :                 fmt.Fprintf(stdout, "%s\n", arg)
     282           1 : 
     283           1 :                 r, err := s.newReader(f)
     284           1 :                 if err != nil {
     285           1 :                         fmt.Fprintf(stdout, "%s\n", err)
     286           1 :                         return
     287           1 :                 }
     288           1 :                 defer r.Close()
     289           1 : 
     290           1 :                 if s.verbose {
     291           1 :                         fmt.Fprintf(stdout, "%s", r.Properties.String())
     292           1 :                         return
     293           1 :                 }
     294             : 
     295           1 :                 stat, err := f.Stat()
     296           1 :                 if err != nil {
     297           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     298           0 :                         return
     299           0 :                 }
     300             : 
     301           1 :                 formatNull := func(s string) string {
     302           1 :                         switch s {
     303           1 :                         case "", "nullptr":
     304           1 :                                 return "-"
     305             :                         }
     306           1 :                         return s
     307             :                 }
     308             : 
     309           1 :                 tw := tabwriter.NewWriter(stdout, 2, 1, 2, ' ', 0)
     310           1 :                 fmt.Fprintf(tw, "size\t\n")
     311           1 :                 fmt.Fprintf(tw, "  file\t%s\n", humanize.Bytes.Int64(stat.Size()))
     312           1 :                 fmt.Fprintf(tw, "  data\t%s\n", humanize.Bytes.Uint64(r.Properties.DataSize))
     313           1 :                 fmt.Fprintf(tw, "    blocks\t%d\n", r.Properties.NumDataBlocks)
     314           1 :                 fmt.Fprintf(tw, "  index\t%s\n", humanize.Bytes.Uint64(r.Properties.IndexSize))
     315           1 :                 fmt.Fprintf(tw, "    blocks\t%d\n", 1+r.Properties.IndexPartitions)
     316           1 :                 fmt.Fprintf(tw, "    top-level\t%s\n", humanize.Bytes.Uint64(r.Properties.TopLevelIndexSize))
     317           1 :                 fmt.Fprintf(tw, "  filter\t%s\n", humanize.Bytes.Uint64(r.Properties.FilterSize))
     318           1 :                 fmt.Fprintf(tw, "  raw-key\t%s\n", humanize.Bytes.Uint64(r.Properties.RawKeySize))
     319           1 :                 fmt.Fprintf(tw, "  raw-value\t%s\n", humanize.Bytes.Uint64(r.Properties.RawValueSize))
     320           1 :                 fmt.Fprintf(tw, "  pinned-key\t%d\n", r.Properties.SnapshotPinnedKeySize)
     321           1 :                 fmt.Fprintf(tw, "  pinned-val\t%d\n", r.Properties.SnapshotPinnedValueSize)
     322           1 :                 fmt.Fprintf(tw, "  point-del-key-size\t%d\n", r.Properties.RawPointTombstoneKeySize)
     323           1 :                 fmt.Fprintf(tw, "  point-del-value-size\t%d\n", r.Properties.RawPointTombstoneValueSize)
     324           1 :                 fmt.Fprintf(tw, "records\t%d\n", r.Properties.NumEntries)
     325           1 :                 fmt.Fprintf(tw, "  set\t%d\n", r.Properties.NumEntries-
     326           1 :                         (r.Properties.NumDeletions+r.Properties.NumMergeOperands))
     327           1 :                 fmt.Fprintf(tw, "  delete\t%d\n", r.Properties.NumPointDeletions())
     328           1 :                 fmt.Fprintf(tw, "  delete-sized\t%d\n", r.Properties.NumSizedDeletions)
     329           1 :                 fmt.Fprintf(tw, "  range-delete\t%d\n", r.Properties.NumRangeDeletions)
     330           1 :                 fmt.Fprintf(tw, "  range-key-set\t%d\n", r.Properties.NumRangeKeySets)
     331           1 :                 fmt.Fprintf(tw, "  range-key-unset\t%d\n", r.Properties.NumRangeKeyUnsets)
     332           1 :                 fmt.Fprintf(tw, "  range-key-delete\t%d\n", r.Properties.NumRangeKeyDels)
     333           1 :                 fmt.Fprintf(tw, "  merge\t%d\n", r.Properties.NumMergeOperands)
     334           1 :                 fmt.Fprintf(tw, "  global-seq-num\t%d\n", r.Properties.GlobalSeqNum)
     335           1 :                 fmt.Fprintf(tw, "  pinned\t%d\n", r.Properties.SnapshotPinnedKeys)
     336           1 :                 fmt.Fprintf(tw, "index\t\n")
     337           1 :                 fmt.Fprintf(tw, "  key\t")
     338           1 :                 fmt.Fprintf(tw, "  value\t")
     339           1 :                 fmt.Fprintf(tw, "comparer\t%s\n", r.Properties.ComparerName)
     340           1 :                 fmt.Fprintf(tw, "merger\t%s\n", formatNull(r.Properties.MergerName))
     341           1 :                 fmt.Fprintf(tw, "filter\t%s\n", formatNull(r.Properties.FilterPolicyName))
     342           1 :                 fmt.Fprintf(tw, "  prefix\t%t\n", r.Properties.PrefixFiltering)
     343           1 :                 fmt.Fprintf(tw, "  whole-key\t%t\n", r.Properties.WholeKeyFiltering)
     344           1 :                 fmt.Fprintf(tw, "compression\t%s\n", r.Properties.CompressionName)
     345           1 :                 fmt.Fprintf(tw, "  options\t%s\n", r.Properties.CompressionOptions)
     346           1 :                 fmt.Fprintf(tw, "user properties\t\n")
     347           1 :                 fmt.Fprintf(tw, "  collectors\t%s\n", r.Properties.PropertyCollectorNames)
     348           1 :                 keys := make([]string, 0, len(r.Properties.UserProperties))
     349           1 :                 for key := range r.Properties.UserProperties {
     350           1 :                         keys = append(keys, key)
     351           1 :                 }
     352           1 :                 sort.Strings(keys)
     353           1 :                 for _, key := range keys {
     354           1 :                         fmt.Fprintf(tw, "  %s\t%s\n", key, r.Properties.UserProperties[key])
     355           1 :                 }
     356           1 :                 tw.Flush()
     357             :         })
     358             : }
     359             : 
     360           1 : func (s *sstableT) runScan(cmd *cobra.Command, args []string) {
     361           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     362           1 :         s.foreachSstable(stderr, args, func(arg string) {
     363           1 :                 f, err := s.opts.FS.Open(arg)
     364           1 :                 if err != nil {
     365           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     366           0 :                         return
     367           0 :                 }
     368             : 
     369             :                 // In filter-mode, we prefix ever line that is output with the sstable
     370             :                 // filename.
     371           1 :                 var prefix string
     372           1 :                 if s.filter == nil {
     373           1 :                         fmt.Fprintf(stdout, "%s\n", arg)
     374           1 :                 } else {
     375           1 :                         prefix = fmt.Sprintf("%s: ", arg)
     376           1 :                 }
     377             : 
     378           1 :                 r, err := s.newReader(f)
     379           1 :                 if err != nil {
     380           0 :                         fmt.Fprintf(stdout, "%s%s\n", prefix, err)
     381           0 :                         return
     382           0 :                 }
     383           1 :                 defer r.Close()
     384           1 : 
     385           1 :                 // Update the internal formatter if this comparator has one specified.
     386           1 :                 s.fmtKey.setForComparer(r.Properties.ComparerName, s.comparers)
     387           1 :                 s.fmtValue.setForComparer(r.Properties.ComparerName, s.comparers)
     388           1 : 
     389           1 :                 iter, err := r.NewIter(nil, s.end)
     390           1 :                 if err != nil {
     391           0 :                         fmt.Fprintf(stderr, "%s%s\n", prefix, err)
     392           0 :                         return
     393           0 :                 }
     394           1 :                 defer iter.Close()
     395           1 :                 key, value := iter.SeekGE(s.start, base.SeekGEFlagsNone)
     396           1 : 
     397           1 :                 // We configured sstable.Reader to return raw tombstones which requires a
     398           1 :                 // bit more work here to put them in a form that can be iterated in
     399           1 :                 // parallel with the point records.
     400           1 :                 rangeDelIter, err := func() (keyspan.FragmentIterator, error) {
     401           1 :                         iter, err := r.NewRawRangeDelIter()
     402           1 :                         if err != nil {
     403           0 :                                 return nil, err
     404           0 :                         }
     405           1 :                         if iter == nil {
     406           1 :                                 return keyspan.NewIter(r.Compare, nil), nil
     407           1 :                         }
     408           1 :                         defer iter.Close()
     409           1 : 
     410           1 :                         var tombstones []keyspan.Span
     411           1 :                         for t := iter.First(); t != nil; t = iter.Next() {
     412           1 :                                 if s.end != nil && r.Compare(s.end, t.Start) <= 0 {
     413           1 :                                         // The range tombstone lies after the scan range.
     414           1 :                                         continue
     415             :                                 }
     416           1 :                                 if r.Compare(s.start, t.End) >= 0 {
     417           1 :                                         // The range tombstone lies before the scan range.
     418           1 :                                         continue
     419             :                                 }
     420           1 :                                 tombstones = append(tombstones, t.ShallowClone())
     421             :                         }
     422             : 
     423           1 :                         sort.Slice(tombstones, func(i, j int) bool {
     424           1 :                                 return r.Compare(tombstones[i].Start, tombstones[j].Start) < 0
     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 := rangeDelIter.First()
     435           1 :                 count := s.count
     436           1 : 
     437           1 :                 var lastKey base.InternalKey
     438           1 :                 for key != nil || rangeDel != nil {
     439           1 :                         if key != nil && (rangeDel == nil || r.Compare(key.UserKey, rangeDel.Start) < 0) {
     440           1 :                                 // The filter specifies a prefix of the key.
     441           1 :                                 //
     442           1 :                                 // TODO(peter): Is using prefix comparison like this kosher for all
     443           1 :                                 // comparers? Probably not, but it is for common ones such as the
     444           1 :                                 // Pebble default and CockroachDB's comparer.
     445           1 :                                 if s.filter == nil || bytes.HasPrefix(key.UserKey, s.filter) {
     446           1 :                                         fmt.Fprint(stdout, prefix)
     447           1 :                                         v, _, err := value.Value(nil)
     448           1 :                                         if err != nil {
     449           0 :                                                 fmt.Fprintf(stdout, "%s%s\n", prefix, err)
     450           0 :                                                 return
     451           0 :                                         }
     452           1 :                                         formatKeyValue(stdout, s.fmtKey, s.fmtValue, key, v)
     453             : 
     454             :                                 }
     455           1 :                                 if base.InternalCompare(r.Compare, lastKey, *key) >= 0 {
     456           1 :                                         fmt.Fprintf(stdout, "%s    WARNING: OUT OF ORDER KEYS!\n", prefix)
     457           1 :                                 }
     458           1 :                                 lastKey.Trailer = key.Trailer
     459           1 :                                 lastKey.UserKey = append(lastKey.UserKey[:0], key.UserKey...)
     460           1 :                                 key, value = iter.Next()
     461           1 :                         } else {
     462           1 :                                 // If a filter is specified, we want to output any range tombstone
     463           1 :                                 // which overlaps the prefix. The comparison on the start key is
     464           1 :                                 // somewhat complex. Consider the tombstone [aaa,ccc). We want to
     465           1 :                                 // output this tombstone if filter is "aa", and if it "bbb".
     466           1 :                                 if s.filter == nil ||
     467           1 :                                         ((r.Compare(s.filter, rangeDel.Start) >= 0 ||
     468           1 :                                                 bytes.HasPrefix(rangeDel.Start, s.filter)) &&
     469           1 :                                                 r.Compare(s.filter, rangeDel.End) < 0) {
     470           1 :                                         fmt.Fprint(stdout, prefix)
     471           1 :                                         if err := rangedel.Encode(rangeDel, func(k base.InternalKey, v []byte) error {
     472           1 :                                                 formatKeyValue(stdout, s.fmtKey, s.fmtValue, &k, v)
     473           1 :                                                 return nil
     474           1 :                                         }); err != nil {
     475           0 :                                                 fmt.Fprintf(stdout, "%s\n", err)
     476           0 :                                                 os.Exit(1)
     477           0 :                                         }
     478             :                                 }
     479           1 :                                 rangeDel = rangeDelIter.Next()
     480             :                         }
     481             : 
     482           1 :                         if count > 0 {
     483           1 :                                 count--
     484           1 :                                 if count == 0 {
     485           1 :                                         break
     486             :                                 }
     487             :                         }
     488             :                 }
     489             : 
     490             :                 // Handle range keys.
     491           1 :                 rkIter, err := r.NewRawRangeKeyIter()
     492           1 :                 if err != nil {
     493           0 :                         fmt.Fprintf(stdout, "%s\n", err)
     494           0 :                         os.Exit(1)
     495           0 :                 }
     496           1 :                 if rkIter != nil {
     497           1 :                         defer rkIter.Close()
     498           1 :                         for span := rkIter.SeekGE(s.start); span != nil; span = rkIter.Next() {
     499           1 :                                 // By default, emit the key, unless there is a filter.
     500           1 :                                 emit := s.filter == nil
     501           1 :                                 // Skip spans that start after the end key (if provided). End keys are
     502           1 :                                 // exclusive, e.g. [a, b), so we consider the interval [b, +inf).
     503           1 :                                 if s.end != nil && r.Compare(span.Start, s.end) >= 0 {
     504           0 :                                         emit = false
     505           0 :                                 }
     506             :                                 // Filters override the provided start / end bounds, if provided.
     507           1 :                                 if s.filter != nil && bytes.HasPrefix(span.Start, s.filter) {
     508           1 :                                         // In filter mode, each line is prefixed with the filename.
     509           1 :                                         fmt.Fprint(stdout, prefix)
     510           1 :                                         emit = true
     511           1 :                                 }
     512           1 :                                 if emit {
     513           1 :                                         formatSpan(stdout, s.fmtKey, s.fmtValue, span)
     514           1 :                                 }
     515             :                         }
     516             :                 }
     517             : 
     518           1 :                 if err := iter.Close(); err != nil {
     519           0 :                         fmt.Fprintf(stdout, "%s\n", err)
     520           0 :                 }
     521             :         })
     522             : }
     523             : 
     524           1 : func (s *sstableT) runSpace(cmd *cobra.Command, args []string) {
     525           1 :         stdout, stderr := cmd.OutOrStdout(), cmd.OutOrStderr()
     526           1 :         s.foreachSstable(stderr, args, func(arg string) {
     527           1 :                 f, err := s.opts.FS.Open(arg)
     528           1 :                 if err != nil {
     529           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     530           0 :                         return
     531           0 :                 }
     532           1 :                 r, err := s.newReader(f)
     533           1 :                 if err != nil {
     534           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     535           0 :                         return
     536           0 :                 }
     537           1 :                 defer r.Close()
     538           1 : 
     539           1 :                 bytes, err := r.EstimateDiskUsage(s.start, s.end)
     540           1 :                 if err != nil {
     541           0 :                         fmt.Fprintf(stderr, "%s\n", err)
     542           0 :                         return
     543           0 :                 }
     544           1 :                 fmt.Fprintf(stdout, "%s: %d\n", arg, bytes)
     545             :         })
     546             : }
     547             : 
     548           1 : func (s *sstableT) foreachSstable(stderr io.Writer, args []string, fn func(arg string)) {
     549           1 :         // Loop over args, invoking fn for each file. Each directory is recursively
     550           1 :         // listed and fn is invoked on any file with an .sst or .ldb suffix.
     551           1 :         for _, arg := range args {
     552           1 :                 info, err := s.opts.FS.Stat(arg)
     553           1 :                 if err != nil || !info.IsDir() {
     554           1 :                         fn(arg)
     555           1 :                         continue
     556             :                 }
     557           1 :                 walk(stderr, s.opts.FS, arg, func(path string) {
     558           1 :                         switch filepath.Ext(path) {
     559           1 :                         case ".sst", ".ldb":
     560           1 :                                 fn(path)
     561             :                         }
     562             :                 })
     563             :         }
     564             : }

Generated by: LCOV version 1.14