@@ -6,10 +6,19 @@ package sstable
66
77import (
88 "context"
9+ "encoding/hex"
10+ "fmt"
11+ "runtime/debug"
12+ "strconv"
13+ "strings"
14+ "unicode"
915
16+ "github.com/cockroachdb/crlib/crstrings"
17+ "github.com/cockroachdb/errors"
1018 "github.com/cockroachdb/pebble/internal/base"
1119 "github.com/cockroachdb/pebble/internal/keyspan"
1220 "github.com/cockroachdb/pebble/objstorage"
21+ "github.com/cockroachdb/pebble/sstable/blob"
1322 "github.com/cockroachdb/pebble/sstable/block"
1423)
1524
@@ -80,3 +89,172 @@ func ReadAll(
8089 }
8190 return points , rangeDels , rangeKeys , nil
8291}
92+
93+ // ParsedKVOrSpan represents a KV or a key span produced by ParseTestKVsAndSpans.
94+ //
95+ // There are three possibilities:
96+ // - key span: only the Span field is set.
97+ // - KV without blob value: only the Key and Value fields are set (and
98+ // optionally ForceObsolete).
99+ // - KV with blob value: only the Key, BlobHandle, and Attr fields are set
100+ // (and optionally ForceObsolete).
101+ type ParsedKVOrSpan struct {
102+ // If Span is not nil, the rest of the fields are unset.
103+ Span * keyspan.Span
104+ Key base.InternalKey
105+ ForceObsolete bool
106+ // Either Value is set, or BlobHandle and Attr are set.
107+ Value []byte
108+ BlobHandle blob.InlineHandle
109+ Attr base.ShortAttribute
110+ }
111+
112+ func (kv ParsedKVOrSpan ) IsKeySpan () bool {
113+ return kv .Span != nil
114+ }
115+
116+ func (kv ParsedKVOrSpan ) HasBlobValue () bool {
117+ return kv .Span == nil && kv .Value == nil
118+ }
119+
120+ func (kv ParsedKVOrSpan ) String () string {
121+ if kv .IsKeySpan () {
122+ return fmt .Sprintf ("Span: %s" , kv .Span )
123+ }
124+ prefix := crstrings .If (kv .ForceObsolete , "force-obsolete: " )
125+ if ! kv .HasBlobValue () {
126+ return fmt .Sprintf ("%s%s = %s" , prefix , kv .Key , kv .Value )
127+ }
128+ return fmt .Sprintf ("%s%s = blobInlineHandle(%d, blk%d, %d, %d, 0x%02x)" , prefix , kv .Key ,
129+ kv .BlobHandle .ReferenceID , kv .BlobHandle .BlockNum , kv .BlobHandle .OffsetInBlock , kv .BlobHandle .ValueLen , kv .Attr ,
130+ )
131+ }
132+
133+ // ParseTestKVsAndSpans parses a multi-line string that defines SSTable contents.
134+ // The lines can be either key-value pairs or key spans.
135+ // Sample input showing the format:
136+ //
137+ // a#1,SET = a
138+ // force-obsolete: d#2,SET = d
139+ // f#3,SET = blobInlineHandle(0, blk1, 10, 100, 0x07)
140+ // Span: d-e:{(#4,RANGEDEL)}
141+ // Span: a-d:{(#11,RANGEKEYSET,@10,foo)}
142+ // Span: g-l:{(#5,RANGEDEL)}
143+ // Span: y-z:{(#12,RANGEKEYSET,@11,foo)}
144+ //
145+ // Note that the older KV format "<user-key>.<kind>.<seq-num> : <value>" is also supported
146+ // (for now).
147+ func ParseTestKVsAndSpans (input string ) (_ []ParsedKVOrSpan , err error ) {
148+ defer func () {
149+ if r := recover (); r != nil {
150+ err = errors .Newf ("%v\n %s" , r , debug .Stack ())
151+ }
152+ }()
153+ var result []ParsedKVOrSpan
154+ for _ , line := range crstrings .Lines (input ) {
155+ if strings .HasPrefix (line , "Span:" ) {
156+ span := keyspan .ParseSpan (strings .TrimPrefix (line , "Span:" ))
157+ result = append (result , ParsedKVOrSpan {Span : & span })
158+ continue
159+ }
160+
161+ var kv ParsedKVOrSpan
162+ line , kv .ForceObsolete = strings .CutPrefix (line , "force-obsolete:" )
163+ // There should be exactly one "=" or ":" in the remaining line.
164+ keyStr , valStr , ok := strings .Cut (line , "=" )
165+ if ! ok {
166+ keyStr , valStr , ok = strings .Cut (line , ":" )
167+ }
168+ if ! ok {
169+ return nil , errors .Newf ("KV format is [force-obsolete:] <key>=<value> (or <key>:<value>): %q" , line )
170+ }
171+ kv .Key = base .ParseInternalKey (strings .TrimSpace (keyStr ))
172+ valStr = strings .TrimSpace (valStr )
173+
174+ if kv .ForceObsolete && kv .Key .Kind () == InternalKeyKindRangeDelete {
175+ return nil , errors .Errorf ("force-obsolete is not allowed for RANGEDEL" )
176+ }
177+
178+ if strings .HasPrefix (valStr , "blobInlineHandle(" ) {
179+ handle , attr , err := decodeBlobInlineHandleAndAttribute (valStr )
180+ if err != nil {
181+ return nil , err
182+ }
183+ kv .BlobHandle = handle
184+ kv .Attr = attr
185+ } else {
186+ kv .Value = []byte (valStr )
187+ }
188+ result = append (result , kv )
189+ }
190+ return result , nil
191+ }
192+
193+ // ParseTestSST parses the KVs and spans in the input (see ParseTestKVAndSpans)
194+ // and writes them to an sstable.
195+ func ParseTestSST (w RawWriter , input string ) error {
196+ kvs , err := ParseTestKVsAndSpans (input )
197+ if err != nil {
198+ return err
199+ }
200+ for _ , kv := range kvs {
201+ var err error
202+ switch {
203+ case kv .IsKeySpan ():
204+ err = w .EncodeSpan (* kv .Span )
205+ case kv .HasBlobValue ():
206+ err = w .AddWithBlobHandle (kv .Key , kv .BlobHandle , kv .Attr , kv .ForceObsolete )
207+ default :
208+ err = w .Add (kv .Key , kv .Value , kv .ForceObsolete )
209+ }
210+ if err != nil {
211+ return errors .Wrapf (err , "failed to write %s" , kv )
212+ }
213+ }
214+ return nil
215+ }
216+
217+ // decodeBlobInlineHandleAndAttribute decodes a blob handle (in its inline form)
218+ // and its short attribute from a debug string. It expects a value of the form:
219+ // blobInlineHandle(<refIndex>, blk<blocknum>, <offset>, <valLen>, <attr>). For example:
220+ //
221+ // blobInlineHandle(24, blk255, 10, 9235, 0x07)
222+ func decodeBlobInlineHandleAndAttribute (
223+ ref string ,
224+ ) (blob.InlineHandle , base.ShortAttribute , error ) {
225+ fields := strings .FieldsFunc (strings .TrimSuffix (strings .TrimPrefix (ref , "blobInlineHandle(" ), ")" ),
226+ func (r rune ) bool { return r == ',' || unicode .IsSpace (r ) })
227+ if len (fields ) != 5 {
228+ return blob.InlineHandle {}, base .ShortAttribute (0 ), errors .New ("expected 5 fields" )
229+ }
230+ refIdx , err := strconv .ParseUint (fields [0 ], 10 , 32 )
231+ if err != nil {
232+ return blob.InlineHandle {}, base .ShortAttribute (0 ), errors .Wrap (err , "failed to parse file offset" )
233+ }
234+ blockNum , err := strconv .ParseUint (strings .TrimPrefix (fields [1 ], "blk" ), 10 , 32 )
235+ if err != nil {
236+ return blob.InlineHandle {}, base .ShortAttribute (0 ), errors .Wrap (err , "failed to parse block number" )
237+ }
238+ off , err := strconv .ParseUint (fields [2 ], 10 , 32 )
239+ if err != nil {
240+ return blob.InlineHandle {}, base .ShortAttribute (0 ), errors .Wrap (err , "failed to parse offset" )
241+ }
242+ valLen , err := strconv .ParseUint (fields [3 ], 10 , 32 )
243+ if err != nil {
244+ return blob.InlineHandle {}, base .ShortAttribute (0 ), errors .Wrap (err , "failed to parse value length" )
245+ }
246+ attr , err := hex .DecodeString (strings .TrimPrefix (fields [4 ], "0x" ))
247+ if err != nil {
248+ return blob.InlineHandle {}, base .ShortAttribute (0 ), errors .Wrap (err , "failed to parse attribute" )
249+ }
250+ return blob.InlineHandle {
251+ InlineHandlePreface : blob.InlineHandlePreface {
252+ ReferenceID : blob .ReferenceID (refIdx ),
253+ ValueLen : uint32 (valLen ),
254+ },
255+ HandleSuffix : blob.HandleSuffix {
256+ BlockNum : uint32 (blockNum ),
257+ OffsetInBlock : uint32 (off ),
258+ },
259+ }, base .ShortAttribute (attr [0 ]), nil
260+ }
0 commit comments