5252)
5353
5454// ChecksumFlag is a flag on the checksum to ensure it is non-zero.
55- const ChecksumFlag uint64 = 1 << 63
55+ const ChecksumFlag Checksum = 1 << 63
5656
5757// internal reader/writer states
5858const (
@@ -65,11 +65,11 @@ const (
6565// Pos represents the transactional position of a database.
6666type Pos struct {
6767 TXID TXID
68- PostApplyChecksum uint64
68+ PostApplyChecksum Checksum
6969}
7070
7171// NewPos returns a new instance of Pos.
72- func NewPos (txID TXID , postApplyChecksum uint64 ) Pos {
72+ func NewPos (txID TXID , postApplyChecksum Checksum ) Pos {
7373 return Pos {
7474 TXID : txID ,
7575 PostApplyChecksum : postApplyChecksum ,
@@ -87,9 +87,9 @@ func ParsePos(s string) (Pos, error) {
8787 return Pos {}, err
8888 }
8989
90- checksum , err := strconv . ParseUint (s [17 :], 16 , 64 )
90+ checksum , err := ParseChecksum (s [17 :])
9191 if err != nil {
92- return Pos {}, fmt . Errorf ( "invalid checksum format: %q" , s [ 17 :])
92+ return Pos {}, err
9393 }
9494
9595 return Pos {
@@ -100,43 +100,14 @@ func ParsePos(s string) (Pos, error) {
100100
101101// String returns a string representation of the position.
102102func (p Pos ) String () string {
103- return fmt .Sprintf ("%s/%016x " , p .TXID , p .PostApplyChecksum )
103+ return fmt .Sprintf ("%s/%s " , p .TXID , p .PostApplyChecksum )
104104}
105105
106106// IsZero returns true if the position is empty.
107107func (p Pos ) IsZero () bool {
108108 return p == (Pos {})
109109}
110110
111- // Marshal serializes the position into JSON.
112- func (p Pos ) MarshalJSON () ([]byte , error ) {
113- var v posJSON
114- v .TXID = p .TXID .String ()
115- v .PostApplyChecksum = fmt .Sprintf ("%016x" , p .PostApplyChecksum )
116- return json .Marshal (v )
117- }
118-
119- // Unmarshal deserializes the position from JSON.
120- func (p * Pos ) UnmarshalJSON (data []byte ) (err error ) {
121- var v posJSON
122- if err := json .Unmarshal (data , & v ); err != nil {
123- return err
124- }
125-
126- if p .TXID , err = ParseTXID (v .TXID ); err != nil {
127- return fmt .Errorf ("cannot parse txid: %q" , v .TXID )
128- }
129- if p .PostApplyChecksum , err = strconv .ParseUint (v .PostApplyChecksum , 16 , 64 ); err != nil {
130- return fmt .Errorf ("cannot parse post-apply checksum: %q" , v .PostApplyChecksum )
131- }
132- return nil
133- }
134-
135- type posJSON struct {
136- TXID string `json:"txid"`
137- PostApplyChecksum string `json:"postApplyChecksum"`
138- }
139-
140111// PosMismatchError is returned when an LTX file is not contiguous with the current position.
141112type PosMismatchError struct {
142113 Pos Pos `json:"pos"`
@@ -197,6 +168,51 @@ func (t *TXID) UnmarshalJSON(data []byte) (err error) {
197168 return nil
198169}
199170
171+ // Checksum represents an LTX checksum.
172+ type Checksum uint64
173+
174+ // ParseChecksum parses a 16-character hex string into a checksum.
175+ func ParseChecksum (s string ) (Checksum , error ) {
176+ if len (s ) != 16 {
177+ return 0 , fmt .Errorf ("invalid formatted checksum length: %q" , s )
178+ }
179+ v , err := strconv .ParseUint (s , 16 , 64 )
180+ if err != nil {
181+ return 0 , fmt .Errorf ("invalid checksum format: %q" , s )
182+ }
183+ return Checksum (v ), nil
184+ }
185+
186+ // String returns c formatted as a fixed-width hex number.
187+ func (c Checksum ) String () string {
188+ return fmt .Sprintf ("%016x" , uint64 (c ))
189+ }
190+
191+ func (c Checksum ) MarshalJSON () ([]byte , error ) {
192+ return []byte (`"` + c .String () + `"` ), nil
193+ }
194+
195+ func (c * Checksum ) UnmarshalJSON (data []byte ) (err error ) {
196+ var s * string
197+ if err := json .Unmarshal (data , & s ); err != nil {
198+ return fmt .Errorf ("cannot unmarshal checksum from JSON value" )
199+ }
200+
201+ // Set to zero if value is nil.
202+ if s == nil {
203+ * c = 0
204+ return nil
205+ }
206+
207+ chksum , err := ParseChecksum (* s )
208+ if err != nil {
209+ return fmt .Errorf ("cannot parse checksum from JSON string: %q" , * s )
210+ }
211+ * c = Checksum (chksum )
212+
213+ return nil
214+ }
215+
200216// Header flags.
201217const (
202218 HeaderFlagMask = uint32 (0x00000001 )
@@ -206,19 +222,19 @@ const (
206222
207223// Header represents the header frame of an LTX file.
208224type Header struct {
209- Version int // based on magic
210- Flags uint32 // reserved flags
211- PageSize uint32 // page size, in bytes
212- Commit uint32 // db size after transaction, in pages
213- MinTXID TXID // minimum transaction ID
214- MaxTXID TXID // maximum transaction ID
215- Timestamp int64 // milliseconds since unix epoch
216- PreApplyChecksum uint64 // rolling checksum of database before applying this LTX file
217- WALOffset int64 // file offset from original WAL; zero if journal
218- WALSize int64 // size of original WAL segment; zero if journal
219- WALSalt1 uint32 // header salt-1 from original WAL; zero if journal or compaction
220- WALSalt2 uint32 // header salt-2 from original WAL; zero if journal or compaction
221- NodeID uint64 // node id where the LTX file was created, zero if unset
225+ Version int // based on magic
226+ Flags uint32 // reserved flags
227+ PageSize uint32 // page size, in bytes
228+ Commit uint32 // db size after transaction, in pages
229+ MinTXID TXID // minimum transaction ID
230+ MaxTXID TXID // maximum transaction ID
231+ Timestamp int64 // milliseconds since unix epoch
232+ PreApplyChecksum Checksum // rolling checksum of database before applying this LTX file
233+ WALOffset int64 // file offset from original WAL; zero if journal
234+ WALSize int64 // size of original WAL segment; zero if journal
235+ WALSalt1 uint32 // header salt-1 from original WAL; zero if journal or compaction
236+ WALSalt2 uint32 // header salt-2 from original WAL; zero if journal or compaction
237+ NodeID uint64 // node id where the LTX file was created, zero if unset
222238}
223239
224240// IsSnapshot returns true if header represents a complete database snapshot.
@@ -313,7 +329,7 @@ func (h *Header) MarshalBinary() ([]byte, error) {
313329 binary .BigEndian .PutUint64 (b [16 :], uint64 (h .MinTXID ))
314330 binary .BigEndian .PutUint64 (b [24 :], uint64 (h .MaxTXID ))
315331 binary .BigEndian .PutUint64 (b [32 :], uint64 (h .Timestamp ))
316- binary .BigEndian .PutUint64 (b [40 :], h .PreApplyChecksum )
332+ binary .BigEndian .PutUint64 (b [40 :], uint64 ( h .PreApplyChecksum ) )
317333 binary .BigEndian .PutUint64 (b [48 :], uint64 (h .WALOffset ))
318334 binary .BigEndian .PutUint64 (b [56 :], uint64 (h .WALSize ))
319335 binary .BigEndian .PutUint32 (b [64 :], h .WALSalt1 )
@@ -334,7 +350,7 @@ func (h *Header) UnmarshalBinary(b []byte) error {
334350 h .MinTXID = TXID (binary .BigEndian .Uint64 (b [16 :]))
335351 h .MaxTXID = TXID (binary .BigEndian .Uint64 (b [24 :]))
336352 h .Timestamp = int64 (binary .BigEndian .Uint64 (b [32 :]))
337- h .PreApplyChecksum = binary .BigEndian .Uint64 (b [40 :])
353+ h .PreApplyChecksum = Checksum ( binary .BigEndian .Uint64 (b [40 :]) )
338354 h .WALOffset = int64 (binary .BigEndian .Uint64 (b [48 :]))
339355 h .WALSize = int64 (binary .BigEndian .Uint64 (b [56 :]))
340356 h .WALSalt1 = binary .BigEndian .Uint32 (b [64 :])
@@ -371,8 +387,8 @@ func IsValidHeaderFlags(flags uint32) bool {
371387
372388// Trailer represents the ending frame of an LTX file.
373389type Trailer struct {
374- PostApplyChecksum uint64 // rolling checksum of database after this LTX file is applied
375- FileChecksum uint64 // crc64 checksum of entire file
390+ PostApplyChecksum Checksum // rolling checksum of database after this LTX file is applied
391+ FileChecksum Checksum // crc64 checksum of entire file
376392}
377393
378394// Validate returns an error if t is invalid.
@@ -394,8 +410,8 @@ func (t *Trailer) Validate() error {
394410// MarshalBinary encodes h to a byte slice.
395411func (t * Trailer ) MarshalBinary () ([]byte , error ) {
396412 b := make ([]byte , TrailerSize )
397- binary .BigEndian .PutUint64 (b [0 :], t .PostApplyChecksum )
398- binary .BigEndian .PutUint64 (b [8 :], t .FileChecksum )
413+ binary .BigEndian .PutUint64 (b [0 :], uint64 ( t .PostApplyChecksum ) )
414+ binary .BigEndian .PutUint64 (b [8 :], uint64 ( t .FileChecksum ) )
399415 return b , nil
400416}
401417
@@ -405,8 +421,8 @@ func (t *Trailer) UnmarshalBinary(b []byte) error {
405421 return io .ErrShortBuffer
406422 }
407423
408- t .PostApplyChecksum = binary .BigEndian .Uint64 (b [0 :])
409- t .FileChecksum = binary .BigEndian .Uint64 (b [8 :])
424+ t .PostApplyChecksum = Checksum ( binary .BigEndian .Uint64 (b [0 :]) )
425+ t .FileChecksum = Checksum ( binary .BigEndian .Uint64 (b [8 :]) )
410426 return nil
411427}
412428
@@ -464,23 +480,23 @@ func NewHasher() hash.Hash64 {
464480}
465481
466482// ChecksumPage returns a CRC64 checksum that combines the page number & page data.
467- func ChecksumPage (pgno uint32 , data []byte ) uint64 {
483+ func ChecksumPage (pgno uint32 , data []byte ) Checksum {
468484 return ChecksumPageWithHasher (NewHasher (), pgno , data )
469485}
470486
471487// ChecksumPageWithHasher returns a CRC64 checksum that combines the page number & page data.
472- func ChecksumPageWithHasher (h hash.Hash64 , pgno uint32 , data []byte ) uint64 {
488+ func ChecksumPageWithHasher (h hash.Hash64 , pgno uint32 , data []byte ) Checksum {
473489 h .Reset ()
474490 _ = binary .Write (h , binary .BigEndian , pgno )
475491 _ , _ = h .Write (data )
476- return ChecksumFlag | h .Sum64 ()
492+ return ChecksumFlag | Checksum ( h .Sum64 () )
477493}
478494
479495// ChecksumReader reads an entire database file from r and computes its rolling checksum.
480- func ChecksumReader (r io.Reader , pageSize int ) (uint64 , error ) {
496+ func ChecksumReader (r io.Reader , pageSize int ) (Checksum , error ) {
481497 data := make ([]byte , pageSize )
482498
483- var chksum uint64
499+ var chksum Checksum
484500 for pgno := uint32 (1 ); ; pgno ++ {
485501 if _ , err := io .ReadFull (r , data ); err == io .EOF {
486502 break
0 commit comments