Commit 7792a133 authored by Kirill Smelkov's avatar Kirill Smelkov

go/zodb/fs1: Actual FileStorage ZODB driver

Build FileStorage ZODB driver out of format record loading/decoding
and index routines we just added in previous patches.

The driver supports only read-only mode so far.

Promised tests for data format interoperability with ZODB/py are added.
parent d3bf6538
// Copyright (C) 2017 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.
// Package fs1 provides so-called FileStorage version 1 ZODB storage.
//
// FileStorage is a single file organized as a simple append-only log of
// transactions with data changes. Every transaction record consists of:
//
// - transaction record header represented by TxnHeader,
// - several data records corresponding to modified objects,
// - redundant transaction length at the end of transaction record.
//
// Every data record consists of:
//
// - data record header represented by DataHeader,
// - actual data following the header.
//
// The "actual data" in addition to raw content, can be a back-pointer
// indicating that the actual content should be retrieved from a past revision.
//
// In addition to append-only transaction/data log, an index is automatically
// maintained mapping oid -> latest data record which modified this oid. The
// index is used to implement zodb.IStorage.Load for latest data without linear
// scan.
//
// The data format is bit-to-bit identical to FileStorage format implemented in ZODB/py.
// Please see the following links for original FileStorage format definition:
//
// https://github.com/zopefoundation/ZODB/blob/a89485c1/src/ZODB/FileStorage/format.py
// https://github.com/zopefoundation/ZODB/blob/a89485c1/src/ZODB/fstools.py
//
// The index format is interoperable with ZODB/py (index uses pickles which
// allow various valid encodings of a given object). Please see the following
// links for original FileStorage/py index definition:
//
// https://github.com/zopefoundation/ZODB/blob/a89485c1/src/ZODB/fsIndex.py
// https://github.com/zopefoundation/ZODB/commit/1bb14faf
//
// Unless one is doing something FileStorage-specific, it is advised not to use
// fs1 package directly, and instead link-in lab.nexedi.com/kirr/neo/go/zodb/wks,
// open storage by zodb.OpenStorage and use it by way of zodb.IStorage interface.
//
// The fs1 package exposes all FileStorage data format details and most of
// internal workings so that it is possible to implement FileStorage-specific
// tools.
package fs1
import (
"context"
"fmt"
"io"
"log"
"os"
"sync"
"lab.nexedi.com/kirr/neo/go/zodb"
"lab.nexedi.com/kirr/go123/mem"
"lab.nexedi.com/kirr/go123/xerr"
)
// FileStorage is a ZODB storage which stores data in simple append-only file
// organized as transactional log.
//
// It is on-disk compatible with FileStorage from ZODB/py.
type FileStorage struct {
file *os.File
index *Index // oid -> data record position in transaction which last changed oid
// transaction headers for min/max transactions committed
// (both with .Len=0 & .Tid=0 if database is empty)
txnhMin TxnHeader
txnhMax TxnHeader
}
// IStorageDriver
var _ zodb.IStorageDriver = (*FileStorage)(nil)
func (fs *FileStorage) LastTid(_ context.Context) (zodb.Tid, error) {
// XXX must be under lock
return fs.txnhMax.Tid, nil // txnhMax.Tid = 0, if empty
}
func (fs *FileStorage) LastOid(_ context.Context) (zodb.Oid, error) {
// XXX must be under lock
lastOid, _ := fs.index.Last() // returns zero-value, if empty
return lastOid, nil
}
func (fs *FileStorage) URL() string {
return fs.file.Name()
}
// freelist(DataHeader)
var dhPool = sync.Pool{New: func() interface{} { return &DataHeader{} }}
// DataHeaderAlloc allocates DataHeader from freelist.
func DataHeaderAlloc() *DataHeader {
return dhPool.Get().(*DataHeader)
}
// Free puts dh back into DataHeader freelist.
//
// Caller must not use dh after call to Free.
func (dh *DataHeader) Free() {
dhPool.Put(dh)
}
func (fs *FileStorage) Load(_ context.Context, xid zodb.Xid) (buf *mem.Buf, serial zodb.Tid, err error) {
// FIXME zodb.TidMax is only 7fff... tid from outside can be ffff...
// -> TODO reject tid out of range
buf, serial, err = fs.load(xid)
if err != nil {
err = &zodb.OpError{URL: fs.URL(), Op: "load", Args: xid, Err: err}
}
return buf, serial, err
}
func (fs *FileStorage) load(xid zodb.Xid) (buf *mem.Buf, serial zodb.Tid, err error) {
// lookup in index position of oid data record within latest transaction which changed this oid
dataPos, ok := fs.index.Get(xid.Oid)
if !ok {
return nil, 0, &zodb.NoObjectError{Oid: xid.Oid}
}
// XXX go compiler cannot deduce dh should be on stack here
//dh := DataHeader{Oid: xid.Oid, Tid: zodb.TidMax, PrevRevPos: dataPos}
dh := DataHeaderAlloc()
dh.Oid = xid.Oid
dh.Tid = zodb.TidMax
dh.PrevRevPos = dataPos
//defer dh.Free()
buf, serial, err = fs._load(dh, xid)
dh.Free()
return buf, serial, err
}
func (fs *FileStorage) _load(dh *DataHeader, xid zodb.Xid) (*mem.Buf, zodb.Tid, error) {
// search backwards for when we first have data record with tid satisfying xid.At
for {
err := dh.LoadPrevRev(fs.file)
if err != nil {
if err == io.EOF {
// object was created after xid.At
err = &zodb.NoDataError{Oid: xid.Oid, DeletedAt: 0}
}
return nil, 0, err
}
if dh.Tid <= xid.At {
break
}
}
// even if we will scan back via backpointers, the serial returned should
// be of first-found transaction
serial := dh.Tid
buf, err := dh.LoadData(fs.file)
if err != nil {
return nil, 0, err
}
if buf.Data == nil {
// object was deleted
return nil, 0, &zodb.NoDataError{Oid: xid.Oid, DeletedAt: serial}
}
return buf, serial, nil
}
// --- ZODB-level iteration ---
// zIter is combined transaction/data-records iterator as specified by zodb.IStorage.Iterate
type zIter struct {
iter Iter
tidStop zodb.Tid // iterate up to tid <= tidStop | tid >= tidStop depending on iter.dir
zFlags zIterFlags
// data header for data loading
// ( NOTE: need to use separate dh because x.LoadData() changes x state
// while going through backpointers.
//
// here to avoid allocations )
dhLoading DataHeader
datai zodb.DataInfo // ptr to this will be returned by .NextData
dataBuf *mem.Buf
}
type zIterFlags int
const (
zIterEOF zIterFlags = 1 << iota // EOF reached
zIterPreloaded // data for this iteration was already preloaded
)
// NextTxn iterates to next/previous transaction record according to iteration direction
func (zi *zIter) NextTxn(_ context.Context) (*zodb.TxnInfo, zodb.IDataIterator, error) {
// TODO err -> OpError("iter", tidmin..tidmax)
switch {
case zi.zFlags & zIterEOF != 0:
return nil, nil, io.EOF
case zi.zFlags & zIterPreloaded != 0:
// first element is already there - preloaded by who initialized TxnIter
zi.zFlags &= ^zIterPreloaded
default:
err := zi.iter.NextTxn(LoadAll)
// XXX EOF ^^^ is not expected (range pre-cut to valid tids) ?
if err != nil {
return nil, nil, err
}
}
if (zi.iter.Dir == IterForward && zi.iter.Txnh.Tid > zi.tidStop) ||
(zi.iter.Dir == IterBackward && zi.iter.Txnh.Tid < zi.tidStop) {
zi.zFlags |= zIterEOF
return nil, nil, io.EOF
}
return &zi.iter.Txnh.TxnInfo, zi, nil
}
// NextData iterates to next data record and loads data content
func (zi *zIter) NextData(_ context.Context) (*zodb.DataInfo, error) {
// TODO err -> OpError("iter", tidmin..tidmax)
err := zi.iter.NextData()
if err != nil {
return nil, err
}
zi.datai.Oid = zi.iter.Datah.Oid
zi.datai.Tid = zi.iter.Datah.Tid
// NOTE dh.LoadData() changes dh state while going through backpointers -
// - need to use separate dh because of this.
zi.dhLoading = zi.iter.Datah
if zi.dataBuf != nil {
zi.dataBuf.Release()
zi.dataBuf = nil
}
zi.dataBuf, err = zi.dhLoading.LoadData(zi.iter.R)
if err != nil {
return nil, err
}
zi.datai.Data = zi.dataBuf.Data
if zi.dhLoading.Tid != zi.datai.Tid {
zi.datai.DataTidHint = zi.dhLoading.Tid
} else {
zi.datai.DataTidHint = 0
}
return &zi.datai, nil
}
// iterStartError is the iterator created when there are preparatory errors.
//
// this way we offload clients, besides handling NextTxn errors, from also
// handling error cases from Iterate.
//
// XXX bad idea? (e.g. it will prevent from devirtualizing what Iterate returns)
type iterStartError struct {
err error
}
func (e *iterStartError) NextTxn(_ context.Context) (*zodb.TxnInfo, zodb.IDataIterator, error) {
return nil, nil, e.err
}
// findTxnRecord finds transaction record with min(txn.tid): txn.tid >= tid
//
// if there is no such transaction returned error will be EOF.
func (fs *FileStorage) findTxnRecord(r io.ReaderAt, tid zodb.Tid) (TxnHeader, error) {
// XXX read snapshot under lock
// check for empty database
if fs.txnhMin.Len == 0 {
// empty database - no such record
return TxnHeader{}, io.EOF
}
// now we know the database is not empty and thus txnh min & max are valid
// clone them to unalias strings memory
var tmin, tmax TxnHeader
tmin.CloneFrom(&fs.txnhMin)
tmax.CloneFrom(&fs.txnhMax)
if tmax.Tid < tid {
return TxnHeader{}, io.EOF // no such record
}
if tmin.Tid >= tid {
return tmin, nil // tmin satisfies
}
// now we know tid ∈ (tmin, tmax]
// iterate and scan either from tmin or tmax, depending which way it is
// likely closer, to searched tid.
iter := &Iter{R: r}
if (tid - tmin.Tid) < (tmax.Tid - tid) {
//fmt.Printf("forward %.1f%%\n", 100 * float64(tid - tmin.Tid) / float64(tmax.Tid - tmin.Tid))
iter.Dir = IterForward
iter.Txnh = tmin // ok not to clone - memory is already ours
} else {
//fmt.Printf("backward %.1f%%\n", 100 * float64(tid - tmin.Tid) / float64(tmax.Tid - tmin.Tid))
iter.Dir = IterBackward
iter.Txnh = tmax // ok not to clone - ... ^^^
}
var txnhPrev TxnHeader
for {
txnhPrev = iter.Txnh // ok not to clone - we'll reload strings in the end
err := iter.NextTxn(LoadNoStrings)
if err != nil {
return TxnHeader{}, noEOF(err)
}
if (iter.Dir == IterForward && iter.Txnh.Tid >= tid) ||
(iter.Dir == IterBackward && iter.Txnh.Tid < tid) {
break // found (prev for backward)
}
}
// found
var txnhFound TxnHeader
if iter.Dir == IterForward {
txnhFound = iter.Txnh
} else {
txnhFound = txnhPrev
}
// load strings to make sure not to return txnh with strings data from
// another transaction
err := txnhFound.loadStrings(iter.R)
if err != nil {
return TxnHeader{}, noEOF(err)
}
return txnhFound, nil
}
// Iterate creates zodb-level iterator for tidMin..tidMax range
func (fs *FileStorage) Iterate(_ context.Context, tidMin, tidMax zodb.Tid) zodb.ITxnIterator {
// when iterating use IO optimized for sequential access
fsSeq := seqReadAt(fs.file)
ziter := &zIter{iter: Iter{R: fsSeq}}
iter := &ziter.iter
// find first txn : txn.tid >= tidMin
txnh, err := fs.findTxnRecord(fsSeq, tidMin)
switch {
case err == io.EOF:
ziter.zFlags |= zIterEOF // empty
return ziter
case err != nil:
return &iterStartError{&zodb.OpError{
URL: fs.URL(),
Op: "iter",
// XXX (?) add TidRange type which prints as
// "tidmin..tidmax" with omitting ends if it is either 0 or ∞
Args: []zodb.Tid{tidMin, tidMax},
Err: err,
}}
}
// setup iter from what findTxnRecord found
iter.Txnh = txnh
iter.Datah.Pos = txnh.DataPos() // XXX dup wrt Iter.NextTxn
iter.Datah.DataLen = -DataHeaderSize // first iteration will go to first data record
iter.Dir = IterForward // TODO allow both ways iteration at ZODB level
ziter.zFlags |= zIterPreloaded
ziter.tidStop = tidMax
return ziter
}
// --- open + rebuild index ---
// open opens FileStorage without loading index
func open(path string) (_ *FileStorage, err error) {
fs := &FileStorage{}
f, err := os.Open(path)
if err != nil {
return nil, err
}
fs.file = f
defer func() {
if err != nil {
f.Close() // XXX -> lclose
}
}()
// check file magic
fh := FileHeader{}
err = fh.Load(f)
if err != nil {
return nil, err
}
// FIXME rework opening logic to support case when last txn was committed only partially
// determine topPos from file size
fi, err := f.Stat()
if err != nil {
return nil, err
}
topPos := fi.Size()
// read tidMin/tidMax
err = fs.txnhMin.Load(f, txnValidFrom, LoadAll)
err = okEOF(err) // e.g. it is EOF when file is empty
if err != nil {
return nil, err
}
err = fs.txnhMax.Load(f, topPos, LoadAll)
// expect EOF forward
// FIXME ^^^ it will be no EOF if a txn was committed only partially
if err != io.EOF {
if err == nil {
err = fmt.Errorf("%s: no EOF after topPos", f.Name())
}
return nil, fmt.Errorf("%s: %s", f.Name(), err)
}
// .LenPrev must be good or EOF backward
switch fs.txnhMax.LenPrev {
case -1:
return nil, fmt.Errorf("%s: could not read LenPrev @%d (last transaction)", f.Name(), fs.txnhMax.Pos)
case 0:
// ok - EOF backward
default:
// .LenPrev is ok - read last previous record
err = fs.txnhMax.LoadPrev(f, LoadAll)
if err != nil {
return nil, err
}
}
return fs, nil
}
// Open opens FileStorage @path.
//
// TODO read-write support
func Open(ctx context.Context, path string) (_ *FileStorage, err error) {
// open data file
fs, err := open(path)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
fs.file.Close() // XXX lclose
}
}()
// load-verify / rebuild index
err = fs.loadIndex(ctx)
if err != nil {
log.Print(err)
log.Printf("%s: index recompute...", path)
fs.index, err = fs.computeIndex(ctx)
if err != nil {
return nil, err
}
// TODO if opened !ro -> .saveIndex()
}
return fs, nil
}
func (fs *FileStorage) Close() error {
err := fs.file.Close()
if err != nil {
return &zodb.OpError{URL: fs.URL(), Op: "close", Args: nil, Err: err}
}
// TODO if opened !ro -> .saveIndex()
return nil
}
func (fs *FileStorage) computeIndex(ctx context.Context) (index *Index, err error) {
// XXX lock?
return BuildIndex(ctx, seqReadAt(fs.file), nil/*no progress; XXX somehow log it? */)
}
// loadIndex loads on-disk index to RAM and verifies it against data lightly
func (fs *FileStorage) loadIndex(ctx context.Context) (err error) {
// XXX lock?
defer xerr.Contextf(&err, "%s", fs.file.Name())
index, err := LoadIndexFile(fs.file.Name() + ".index")
if err != nil {
return err
}
topPos := fs.txnhMax.Pos + fs.txnhMax.Len
if index.TopPos != topPos {
return fmt.Errorf("inconsistent index topPos: data=%d index=%d", topPos, index.TopPos)
}
// quickly verify index sanity for last 100 transactions
_, err = index.Verify(ctx, seqReadAt(fs.file), 100, nil/*no progress*/)
if err != nil {
return err
}
fs.index = index
return nil
}
// saveIndex flushes in-RAM index to disk
func (fs *FileStorage) saveIndex() (err error) {
// XXX lock?
defer xerr.Contextf(&err, "%s", fs.file.Name())
err = fs.index.SaveFile(fs.file.Name() + ".index")
if err != nil {
return err
}
// XXX fsync here?
return nil
}
// Copyright (C) 2017 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.
package fs1
import (
"context"
"fmt"
"io"
"reflect"
"testing"
"lab.nexedi.com/kirr/neo/go/zodb"
"lab.nexedi.com/kirr/go123/exc"
)
// one database transaction record
type dbEntry struct {
Header TxnHeader
Entryv []txnEntry
}
// one entry inside transaction
type txnEntry struct {
Header DataHeader
rawData []byte // what is on disk, e.g. it can be backpointer
userData []byte // data client should see on load; `sameAsRaw` means same as RawData
DataTidHint zodb.Tid // data tid client should see on iter
}
var sameAsRaw = []byte{0}
// Data returns data a client should see
func (txe *txnEntry) Data() []byte {
data := txe.userData
if len(data) > 0 && &data[0] == &sameAsRaw[0] {
data = txe.rawData
}
return data
}
// state of an object in the database for some particular revision
type objState struct {
tid zodb.Tid
data []byte // nil if obj was deleted
}
// checkLoad verifies that fs.Load(xid) returns expected result
func checkLoad(t *testing.T, fs *FileStorage, xid zodb.Xid, expect objState) {
t.Helper()
buf, tid, err := fs.Load(context.Background(), xid)
// deleted obj - it should load with "no data"
if expect.data == nil {
errOk := &zodb.OpError{
URL: fs.URL(),
Op: "load",
Args: xid,
Err: &zodb.NoDataError{Oid: xid.Oid, DeletedAt: expect.tid},
}
if !reflect.DeepEqual(err, errOk) {
t.Errorf("load %v: returned err unexpected: %v ; want: %v", xid, err, errOk)
}
if tid != 0 {
t.Errorf("load %v: returned tid unexpected: %v ; want: %v", xid, tid, expect.tid)
}
if buf != nil {
t.Errorf("load %v: returned buf != nil", xid)
}
// regular load
} else {
if err != nil {
t.Errorf("load %v: returned err unexpected: %v ; want: nil", xid, err)
}
if tid != expect.tid {
t.Errorf("load %v: returned tid unexpected: %v ; want: %v", xid, tid, expect.tid)
}
switch {
case buf == nil:
t.Errorf("load %v: returned buf = nil", xid)
case !reflect.DeepEqual(buf.Data, expect.data): // NOTE reflect to catch nil != ""
t.Errorf("load %v: different data:\nhave: %q\nwant: %q", xid, buf.Data, expect.data)
}
}
}
func xfsopen(t testing.TB, path string) *FileStorage {
fs, err := Open(context.Background(), path)
if err != nil {
t.Fatal(err)
}
return fs
}
func TestLoad(t *testing.T) {
fs := xfsopen(t, "testdata/1.fs") // TODO open read-only
defer exc.XRun(fs.Close)
// current knowledge of what was "before" for an oid as we scan over
// data base entries
before := map[zodb.Oid]objState{}
for _, dbe := range _1fs_dbEntryv {
for _, txe := range dbe.Entryv {
txh := txe.Header
// XXX check Load finds data at correct .Pos / etc ?
// ~ loadSerial
xid := zodb.Xid{txh.Tid, txh.Oid}
checkLoad(t, fs, xid, objState{txh.Tid, txe.Data()})
// ~ loadBefore
xid = zodb.Xid{txh.Tid - 1, txh.Oid}
expect, ok := before[txh.Oid]
if ok {
checkLoad(t, fs, xid, expect)
}
before[txh.Oid] = objState{txh.Tid, txe.Data()}
}
}
// load at ∞ with TidMax
// XXX should we get "no such transaction" with at > head?
for oid, expect := range before {
xid := zodb.Xid{zodb.TidMax, oid}
checkLoad(t, fs, xid, expect)
}
}
// iterate tidMin..tidMax and expect db entries in expectv
func testIterate(t *testing.T, fs *FileStorage, tidMin, tidMax zodb.Tid, expectv []dbEntry) {
ctx := context.Background()
iter := fs.Iterate(ctx, tidMin, tidMax)
fsi, ok := iter.(*zIter)
if !ok {
_, _, err := iter.NextTxn(ctx)
t.Errorf("iterating %v..%v: iter type is %T ; want zIter\nNextTxn gives: _, _, %v", tidMin, tidMax, iter, err)
return
}
for k := 0; ; k++ {
txnErrorf := func(format string, a ...interface{}) {
t.Helper()
subj := fmt.Sprintf("iterating %v..%v: step %v#%v", tidMin, tidMax, k, len(expectv))
msg := fmt.Sprintf(format, a...)
t.Errorf("%v: %v", subj, msg)
}
txni, dataIter, err := iter.NextTxn(ctx)
if err != nil {
if err == io.EOF {
if k != len(expectv) {
txnErrorf("steps underrun")
}
break
}
txnErrorf("%v", err)
}
if k >= len(expectv) {
txnErrorf("steps overrun")
}
dbe := expectv[k]
// assert txni points to where we expect - this will allow us
// not only to check .TxnInfo but also .Pos, .LenPrev, .Len etc in
// whole expected TxnHeader
if txni != &fsi.iter.Txnh.TxnInfo {
t.Fatal("unexpected txni pointer")
}
// compare transaction headers modulo .workMem
// (workMem is not initialized in _1fs_dbEntryv)
txnh1 := fsi.iter.Txnh
txnh2 := dbe.Header
txnh1.workMem = nil
txnh2.workMem = nil
if !reflect.DeepEqual(txnh1, txnh2) {
txnErrorf("unexpected txn entry:\nhave: %q\nwant: %q", txnh1, txnh2)
}
for kdata := 0; ; kdata++ {
dataErrorf := func(format string, a ...interface{}) {
t.Helper()
dsubj := fmt.Sprintf("dstep %v#%v", kdata, len(dbe.Entryv))
msg := fmt.Sprintf(format, a...)
txnErrorf("%v: %v", dsubj, msg)
}
datai, err := dataIter.NextData(ctx)
if err != nil {
if err == io.EOF {
if kdata != len(dbe.Entryv) {
dataErrorf("dsteps underrun")
}
break
}
dataErrorf("%v", err)
}
if kdata > len(dbe.Entryv) {
dataErrorf("dsteps overrun")
}
txe := dbe.Entryv[kdata]
dh := txe.Header
// assert datai points to where we expect - this will allow us
// not only to check oid/tid/data but also to check whole data header.
if datai != &fsi.datai {
t.Fatal("unexpected datai pointer")
}
// compare data headers modulo .workMem
// (workMem is not initialized in _1fs_dbEntryv)
fsi.iter.Datah.workMem = dh.workMem
if !reflect.DeepEqual(fsi.iter.Datah, dh) {
dataErrorf("unexpected data entry:\nhave: %q\nwant: %q", fsi.iter.Datah, dh)
}
// check what was actually returned - since it is not in ^^^ data structure
if datai.Oid != dh.Oid {
dataErrorf("oid mismatch: have %v; want %v", datai.Oid, dh.Oid)
}
if datai.Tid != dh.Tid {
dataErrorf("tid mismatch: have %v; want %v", datai.Tid, dh.Tid)
}
if !reflect.DeepEqual(datai.Data, txe.Data()) { // NOTE reflect to catch nil != ""
dataErrorf("data mismatch:\nhave %q\nwant %q", datai.Data, txe.Data())
}
if datai.DataTidHint != txe.DataTidHint {
dataErrorf("data tid hint mismatch: have %v; want %v", datai.DataTidHint, txe.DataTidHint)
}
}
}
}
func TestIterate(t *testing.T) {
fs := xfsopen(t, "testdata/1.fs") // TODO open ro
defer exc.XRun(fs.Close)
// all []tids in test database
tidv := []zodb.Tid{}
for _, dbe := range _1fs_dbEntryv {
tidv = append(tidv, dbe.Header.Tid)
}
// check all i,j pairs in tidv
// for every tid also check ±1 to test edge cases
for i, tidMin := range tidv {
for j, tidMax := range tidv {
minv := []zodb.Tid{tidMin-1, tidMin, tidMin+1}
maxv := []zodb.Tid{tidMax-1, tidMax, tidMax+1}
for ii, tmin := range minv {
for jj, tmax := range maxv {
// expected number of txn iteration steps
nsteps := j - i + 1
nsteps -= ii / 2 // one less point for tidMin+1
nsteps -= (2 - jj) / 2 // one less point for tidMax-1
if nsteps < 0 {
nsteps = 0 // j < i and j == i and ii/jj
}
//fmt.Printf("%d%+d .. %d%+d\t -> %d steps\n", i, ii-1, j, jj-1, nsteps)
testIterate(t, fs, tmin, tmax, _1fs_dbEntryv[i + ii/2:][:nsteps])
}}
}}
// also check 0..tidMax
testIterate(t, fs, 0, zodb.TidMax, _1fs_dbEntryv[:])
}
func BenchmarkIterate(b *testing.B) {
fs := xfsopen(b, "testdata/1.fs") // TODO open ro
defer exc.XRun(fs.Close)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
iter := fs.Iterate(ctx, zodb.Tid(0), zodb.TidMax)
for {
txni, dataIter, err := iter.NextTxn(ctx)
if err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
// use txni
_ = txni.Tid
for {
datai, err := dataIter.NextData(ctx)
if err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
// use datai
_ = datai.Data
}
}
}
b.StopTimer()
}
...@@ -48,6 +48,7 @@ def main(): ...@@ -48,6 +48,7 @@ def main():
print >>f, v print >>f, v
emit("// Code generated by %s; DO NOT EDIT." % __file__) emit("// Code generated by %s; DO NOT EDIT." % __file__)
emit("package fs1\n") emit("package fs1\n")
emit("import \"lab.nexedi.com/kirr/neo/go/zodb\"\n")
# index # index
emit("const _1fs_indexTopPos = %i" % stor._pos) emit("const _1fs_indexTopPos = %i" % stor._pos)
...@@ -56,6 +57,83 @@ def main(): ...@@ -56,6 +57,83 @@ def main():
emit("\t{%8i, %8i}," % (unpack64(k), v)) emit("\t{%8i, %8i}," % (unpack64(k), v))
emit("}") emit("}")
# database records
emit("\nvar _1fs_dbEntryv = [...]dbEntry{")
txnLenPrev = 0
for txn in stor.iterator(): # txn is TransactionRecord
# txn.extension is already depickled dict - we want to put raw data from file
# also we need to access txn record length which is not provided by higher-level iterator
# do deep-dive into FileStorage
th = stor._read_txn_header(txn._tpos)
assert th.tid == txn.tid
assert th.tlen == txn._tend - txn._tpos
# fs1/go keeps in RAM whole txn length, not len-8 as it is on disk
txnLen = th.tlen + 8
emit("\t{")
# -> TxnHeader
emit("\t\tTxnHeader{")
emit("\t\t\tPos:\t %i," % txn._tpos)
emit("\t\t\tLenPrev: %i," % txnLenPrev)
emit("\t\t\tLen:\t %i," % txnLen)
emit("\t\t\tTxnInfo:\tzodb.TxnInfo{")
emit("\t\t\t\tTid:\t%s," % hex64(txn.tid))
emit("\t\t\t\tStatus:\t'%s'," % txn.status)
emit("\t\t\t\tUser:\t\t[]byte(%s)," % escapeqq(txn.user))
emit("\t\t\t\tDescription:\t[]byte(%s)," % escapeqq(txn.description))
emit("\t\t\t\tExtension:\t[]byte(%s)," % escapeqq(th.ext))
emit("\t\t\t},")
emit("\t\t},")
txnLenPrev = txnLen
# -> DataHeader + payload
emit("\n\t\t[]txnEntry{")
for drec in txn: # drec is itemof(TransactionRecordIterator) = Record
# same as with txn - not everything is possible to get via
# higher-level api
dh = stor._read_data_header(drec.pos)
assert dh.oid == drec.oid
assert dh.tid == drec.tid
assert dh.tloc == txn._tpos
emit("\t\t\t{")
emit("\t\t\t\tDataHeader{")
emit("\t\t\t\t\tPos:\t%i," % drec.pos)
emit("\t\t\t\t\tOid:\t%i," % unpack64(drec.oid))
emit("\t\t\t\t\tTid:\t%s," % hex64(drec.tid))
emit("\t\t\t\t\tPrevRevPos:\t%i," % dh.prev)
emit("\t\t\t\t\tTxnPos:\t%i," % txn._tpos)
assert drec.version == ''
emit("\t\t\t\t\tDataLen:\t%i," % dh.plen)
emit("\t\t\t\t},")
plen = dh.plen
if plen == 0:
rawdata = p64(dh.back) # back-pointer or 0 (= delete)
if drec.data is None:
data = "/* deleted */ nil"
datatid = "/* deleted */ 0"
else:
data = "[]byte(%s)" % escapeqq(drec.data)
datatid = "/* copy from */ " + hex64(drec.data_txn)
else:
rawdata = drec.data
data = "/* same as ^^^ */ sameAsRaw"
datatid = "/* no copy */ 0"
emit("\t\t\t\t[]byte(%s)," % escapeqq(rawdata))
emit("\t\t\t\t%s," % data)
emit("\t\t\t\t%s," % datatid)
emit("\t\t\t},")
emit("\t\t},")
emit("\t},")
emit("}")
if __name__ == '__main__': if __name__ == '__main__':
main() main()
// Code generated by ./py/gen-testdata; DO NOT EDIT. // Code generated by ./py/gen-testdata; DO NOT EDIT.
package fs1 package fs1
import "lab.nexedi.com/kirr/neo/go/zodb"
const _1fs_indexTopPos = 13703 const _1fs_indexTopPos = 13703
var _1fs_indexEntryv = [...]indexEntry{ var _1fs_indexEntryv = [...]indexEntry{
{ 0, 13080}, { 0, 13080},
...@@ -14,3 +16,1893 @@ var _1fs_indexEntryv = [...]indexEntry{ ...@@ -14,3 +16,1893 @@ var _1fs_indexEntryv = [...]indexEntry{
{ 8, 10132}, { 8, 10132},
{ 9, 13354}, { 9, 13354},
} }
var _1fs_dbEntryv = [...]dbEntry{
{
TxnHeader{
Pos: 4,
LenPrev: 0,
Len: 159,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac258bf266,
Status: ' ',
User: []byte(""),
Description: []byte("initial database creation"),
Extension: []byte(""),
},
},
[]txnEntry{
{
DataHeader{
Pos: 52,
Oid: 0,
Tid: 0x0285cbac258bf266,
PrevRevPos: 0,
TxnPos: 4,
DataLen: 61,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04s."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 163,
LenPrev: 159,
Len: 354,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac3d0369e6,
Status: ' ',
User: []byte("user0.0"),
Description: []byte("step 0.0"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieSU\x05RF9IEU\x0bx-generatorq\x02U\x0czodb/py2 (f)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 256,
Oid: 0,
Tid: 0x0285cbac3d0369e6,
PrevRevPos: 52,
TxnPos: 163,
DataLen: 117,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04U\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86Qss."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 415,
Oid: 1,
Tid: 0x0285cbac3d0369e6,
PrevRevPos: 0,
TxnPos: 163,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04f0.0q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 517,
LenPrev: 354,
Len: 374,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac41b4e833,
Status: ' ',
User: []byte("user0.1"),
Description: []byte("step 0.1"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieMU\x05LWIARU\x0bx-generatorq\x02U\x0czodb/py2 (d)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 610,
Oid: 0,
Tid: 0x0285cbac41b4e833,
PrevRevPos: 256,
TxnPos: 517,
DataLen: 137,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04(U\x01dU\x08\x00\x00\x00\x00\x00\x00\x00\x02q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86QU\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\x07h\x06\x86Qus."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 789,
Oid: 2,
Tid: 0x0285cbac41b4e833,
PrevRevPos: 0,
TxnPos: 517,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04d0.1q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 891,
LenPrev: 374,
Len: 393,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac46666680,
Status: ' ',
User: []byte("user0.2"),
Description: []byte("step 0.2"),
Extension: []byte("\x80\x02}q\x01(U\x0bx-generatorq\x02U\x0czodb/py2 (g)U\tx-cookieWU\x05ZTWBQu."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 984,
Oid: 0,
Tid: 0x0285cbac46666680,
PrevRevPos: 610,
TxnPos: 891,
DataLen: 156,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04(U\x01dU\x08\x00\x00\x00\x00\x00\x00\x00\x02q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86QU\x01gU\x08\x00\x00\x00\x00\x00\x00\x00\x03q\x07h\x06\x86QU\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\x08h\x06\x86Qus."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 1182,
Oid: 3,
Tid: 0x0285cbac46666680,
PrevRevPos: 0,
TxnPos: 891,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04g0.2q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 1284,
LenPrev: 393,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac4b17e4cc,
Status: ' ',
User: []byte("user0.3"),
Description: []byte("step 0.3"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieHU\x053FLWYU\x0bx-generatorq\x02U\x0czodb/py2 (d)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 1377,
Oid: 2,
Tid: 0x0285cbac4b17e4cc,
PrevRevPos: 789,
TxnPos: 1284,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04d0.3q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 1479,
LenPrev: 195,
Len: 412,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac4fc96319,
Status: ' ',
User: []byte("user0.4"),
Description: []byte("step 0.4"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieTU\x05SJ0PEU\x0bx-generatorq\x02U\x0czodb/py2 (b)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 1572,
Oid: 0,
Tid: 0x0285cbac4fc96319,
PrevRevPos: 984,
TxnPos: 1479,
DataLen: 175,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04(U\x01bU\x08\x00\x00\x00\x00\x00\x00\x00\x04q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86QU\x01dU\x08\x00\x00\x00\x00\x00\x00\x00\x02q\x07h\x06\x86QU\x01gU\x08\x00\x00\x00\x00\x00\x00\x00\x03q\x08h\x06\x86QU\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\th\x06\x86Qus."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 1789,
Oid: 4,
Tid: 0x0285cbac4fc96319,
PrevRevPos: 0,
TxnPos: 1479,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04b0.4q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 1891,
LenPrev: 412,
Len: 431,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac547ae166,
Status: ' ',
User: []byte("user0.5"),
Description: []byte("step 0.5"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie6U\x05HV8BVU\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 1984,
Oid: 0,
Tid: 0x0285cbac547ae166,
PrevRevPos: 1572,
TxnPos: 1891,
DataLen: 194,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04(U\x01aU\x08\x00\x00\x00\x00\x00\x00\x00\x05q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86QU\x01bU\x08\x00\x00\x00\x00\x00\x00\x00\x04q\x07h\x06\x86QU\x01dU\x08\x00\x00\x00\x00\x00\x00\x00\x02q\x08h\x06\x86QU\x01gU\x08\x00\x00\x00\x00\x00\x00\x00\x03q\th\x06\x86QU\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\nh\x06\x86Qus."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 2220,
Oid: 5,
Tid: 0x0285cbac547ae166,
PrevRevPos: 0,
TxnPos: 1891,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04a0.5q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 2322,
LenPrev: 431,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac592c5fb3,
Status: ' ',
User: []byte("user0.6"),
Description: []byte("step 0.6"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieIU\x05YSG2BU\x0bx-generatorq\x02U\x0czodb/py2 (b)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 2415,
Oid: 4,
Tid: 0x0285cbac592c5fb3,
PrevRevPos: 1789,
TxnPos: 2322,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04b0.6q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 2517,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac5dddde00,
Status: ' ',
User: []byte("user0.7"),
Description: []byte("step 0.7"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieYU\x05JPJTJU\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 2610,
Oid: 5,
Tid: 0x0285cbac5dddde00,
PrevRevPos: 2220,
TxnPos: 2517,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04a0.7q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 2712,
LenPrev: 195,
Len: 450,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac628f5c4c,
Status: ' ',
User: []byte("user0.8"),
Description: []byte("step 0.8"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie6U\x05GLDKAU\x0bx-generatorq\x02U\x0czodb/py2 (e)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 2805,
Oid: 0,
Tid: 0x0285cbac628f5c4c,
PrevRevPos: 1984,
TxnPos: 2712,
DataLen: 213,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04(U\x01aU\x08\x00\x00\x00\x00\x00\x00\x00\x05q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86QU\x01bU\x08\x00\x00\x00\x00\x00\x00\x00\x04q\x07h\x06\x86QU\x01eU\x08\x00\x00\x00\x00\x00\x00\x00\x06q\x08h\x06\x86QU\x01dU\x08\x00\x00\x00\x00\x00\x00\x00\x02q\th\x06\x86QU\x01gU\x08\x00\x00\x00\x00\x00\x00\x00\x03q\nh\x06\x86QU\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\x0bh\x06\x86Qus."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 3060,
Oid: 6,
Tid: 0x0285cbac628f5c4c,
PrevRevPos: 0,
TxnPos: 2712,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04e0.8q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 3162,
LenPrev: 450,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac6740da99,
Status: ' ',
User: []byte("user0.9"),
Description: []byte("step 0.9"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieXU\x05NH3RVU\x0bx-generatorq\x02U\x0czodb/py2 (e)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 3255,
Oid: 6,
Tid: 0x0285cbac6740da99,
PrevRevPos: 3060,
TxnPos: 3162,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04e0.9q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 3357,
LenPrev: 195,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac6bf258e6,
Status: ' ',
User: []byte("user0.10"),
Description: []byte("step 0.10"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieTU\x05XJEP9U\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 3452,
Oid: 3,
Tid: 0x0285cbac6bf258e6,
PrevRevPos: 1182,
TxnPos: 3357,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g0.10q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 3555,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac70a3d733,
Status: ' ',
User: []byte("user0.11"),
Description: []byte("step 0.11"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieZU\x05LYKGNU\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 3650,
Oid: 3,
Tid: 0x0285cbac70a3d733,
PrevRevPos: 3452,
TxnPos: 3555,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g0.11q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 3753,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac75555580,
Status: ' ',
User: []byte("user0.12"),
Description: []byte("step 0.12"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie8U\x052MHMUU\x0bx-generatorq\x02U\x0czodb/py2 (f)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 3848,
Oid: 1,
Tid: 0x0285cbac75555580,
PrevRevPos: 415,
TxnPos: 3753,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05f0.12q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 3951,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac7a06d3cc,
Status: ' ',
User: []byte("user0.13"),
Description: []byte("step 0.13"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie5U\x057SBT3U\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 4046,
Oid: 5,
Tid: 0x0285cbac7a06d3cc,
PrevRevPos: 2610,
TxnPos: 3951,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a0.13q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 4149,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac7eb85219,
Status: ' ',
User: []byte("user0.14"),
Description: []byte("step 0.14"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieMU\x05KWJO0U\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 4244,
Oid: 5,
Tid: 0x0285cbac7eb85219,
PrevRevPos: 4046,
TxnPos: 4149,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a0.14q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 4347,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac8369d066,
Status: ' ',
User: []byte("user0.15"),
Description: []byte("step 0.15"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieYU\x05EDZ10U\x0bx-generatorq\x02U\x0czodb/py2 (e)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 4442,
Oid: 6,
Tid: 0x0285cbac8369d066,
PrevRevPos: 3255,
TxnPos: 4347,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05e0.15q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 4545,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac881b4eb3,
Status: ' ',
User: []byte("user0.16"),
Description: []byte("step 0.16"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie3U\x057SX0FU\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 4640,
Oid: 5,
Tid: 0x0285cbac881b4eb3,
PrevRevPos: 4244,
TxnPos: 4545,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a0.16q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 4743,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac8ccccd00,
Status: ' ',
User: []byte("user0.17"),
Description: []byte("step 0.17"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie3U\x05NC6I1U\x0bx-generatorq\x02U\x0czodb/py2 (b)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 4838,
Oid: 4,
Tid: 0x0285cbac8ccccd00,
PrevRevPos: 2415,
TxnPos: 4743,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05b0.17q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 4941,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac917e4b4c,
Status: ' ',
User: []byte("user0.18"),
Description: []byte("step 0.18"),
Extension: []byte("\x80\x02}q\x01(U\x0bx-generatorq\x02U\x0czodb/py2 (b)U\tx-cookieOU\x05CQUX6u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 5036,
Oid: 4,
Tid: 0x0285cbac917e4b4c,
PrevRevPos: 4838,
TxnPos: 4941,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05b0.18q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 5139,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac962fc999,
Status: ' ',
User: []byte("user0.19"),
Description: []byte("step 0.19"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieFU\x05OUC9LU\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 5234,
Oid: 5,
Tid: 0x0285cbac962fc999,
PrevRevPos: 4640,
TxnPos: 5139,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a0.19q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 5337,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac9ae147e6,
Status: ' ',
User: []byte("user0.20"),
Description: []byte("step 0.20"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieUU\x05EKIBCU\x0bx-generatorq\x02U\x0czodb/py2 (d)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 5432,
Oid: 2,
Tid: 0x0285cbac9ae147e6,
PrevRevPos: 1377,
TxnPos: 5337,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05d0.20q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 5535,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbac9f92c633,
Status: ' ',
User: []byte("user0.21"),
Description: []byte("step 0.21"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie8U\x050QC1AU\x0bx-generatorq\x02U\x0czodb/py2 (d)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 5630,
Oid: 2,
Tid: 0x0285cbac9f92c633,
PrevRevPos: 5432,
TxnPos: 5535,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05d0.21q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 5733,
LenPrev: 198,
Len: 472,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbaca4444480,
Status: ' ',
User: []byte("user0.22"),
Description: []byte("step 0.22"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookiePU\x05ACYMMU\x0bx-generatorq\x02U\x0czodb/py2 (c)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 5828,
Oid: 0,
Tid: 0x0285cbaca4444480,
PrevRevPos: 2805,
TxnPos: 5733,
DataLen: 232,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04(U\x01aU\x08\x00\x00\x00\x00\x00\x00\x00\x05q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86QU\x01cU\x08\x00\x00\x00\x00\x00\x00\x00\x07q\x07h\x06\x86QU\x01bU\x08\x00\x00\x00\x00\x00\x00\x00\x04q\x08h\x06\x86QU\x01eU\x08\x00\x00\x00\x00\x00\x00\x00\x06q\th\x06\x86QU\x01dU\x08\x00\x00\x00\x00\x00\x00\x00\x02q\nh\x06\x86QU\x01gU\x08\x00\x00\x00\x00\x00\x00\x00\x03q\x0bh\x06\x86QU\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\x0ch\x06\x86Qus."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 6102,
Oid: 7,
Tid: 0x0285cbaca4444480,
PrevRevPos: 0,
TxnPos: 5733,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05c0.22q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 6205,
LenPrev: 472,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbaca8f5c2cc,
Status: ' ',
User: []byte("user0.23"),
Description: []byte("step 0.23"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieMU\x05N06C8U\x0bx-generatorq\x02U\x0czodb/py2 (c)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 6300,
Oid: 7,
Tid: 0x0285cbaca8f5c2cc,
PrevRevPos: 6102,
TxnPos: 6205,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05c0.23q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 6403,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbacada74119,
Status: ' ',
User: []byte("user0.24"),
Description: []byte("step 0.24"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieNU\x05KEEPFU\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 6498,
Oid: 3,
Tid: 0x0285cbacada74119,
PrevRevPos: 3650,
TxnPos: 6403,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g0.24q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 6601,
LenPrev: 198,
Len: 216,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbacb258bf66,
Status: ' ',
User: []byte("root0.0\nYour\nMagesty "),
Description: []byte("undo 0.0\nmore detailed description\n\nzzz ..."),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieKU\x05G95IHU\x0bx-generatorq\x02U\x1czodb/py2 (undo AoXLrKj1wsw=)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 6759,
Oid: 7,
Tid: 0x0285cbacb258bf66,
PrevRevPos: 6300,
TxnPos: 6601,
DataLen: 0,
},
[]byte("\x00\x00\x00\x00\x00\x00\x17\xd6"),
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05c0.22q\x02."),
/* copy from */ 0x0285cbaca4444480,
},
},
},
{
TxnHeader{
Pos: 6817,
LenPrev: 216,
Len: 217,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbacb70a3db3,
Status: ' ',
User: []byte("root0.1\nYour\nMagesty "),
Description: []byte("undo 0.1\nmore detailed description\n\nzzz ...\t"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieVU\x05VHBGTU\x0bx-generatorq\x02U\x1czodb/py2 (undo AoXLrK2nQRk=)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 6976,
Oid: 3,
Tid: 0x0285cbacb70a3db3,
PrevRevPos: 6498,
TxnPos: 6817,
DataLen: 0,
},
[]byte("\x00\x00\x00\x00\x00\x00\x0eB"),
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g0.11q\x02."),
/* copy from */ 0x0285cbac70a3d733,
},
},
},
{
TxnHeader{
Pos: 7034,
LenPrev: 217,
Len: 409,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbacbbbbbc00,
Status: ' ',
User: []byte(""),
Description: []byte("predelete 7"),
Extension: []byte(""),
},
},
[]txnEntry{
{
DataHeader{
Pos: 7068,
Oid: 0,
Tid: 0x0285cbacbbbbbc00,
PrevRevPos: 5828,
TxnPos: 7034,
DataLen: 232,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04(U\x01aU\x08\x00\x00\x00\x00\x00\x00\x00\x05q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86QU\x01cU\x08\x00\x00\x00\x00\x00\x00\x00\x08q\x07h\x06\x86QU\x01bU\x08\x00\x00\x00\x00\x00\x00\x00\x04q\x08h\x06\x86QU\x01eU\x08\x00\x00\x00\x00\x00\x00\x00\x06q\th\x06\x86QU\x01dU\x08\x00\x00\x00\x00\x00\x00\x00\x02q\nh\x06\x86QU\x01gU\x08\x00\x00\x00\x00\x00\x00\x00\x03q\x0bh\x06\x86QU\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\x0ch\x06\x86Qus."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 7342,
Oid: 8,
Tid: 0x0285cbacbbbbbc00,
PrevRevPos: 0,
TxnPos: 7034,
DataLen: 51,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x03c0*q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 7443,
LenPrev: 409,
Len: 248,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbacc06d3a4c,
Status: ' ',
User: []byte("root0\nYour\nRoyal\nMagesty' \x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"),
Description: []byte("delete 0\nalpha beta gamma'delta\"lambda\n\nqqq ..."),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieEU\x05ZM3QZU\x0bx-generatorq\x02U\x13zodb/py2 (delete 7)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 7633,
Oid: 7,
Tid: 0x0285cbacc06d3a4c,
PrevRevPos: 6759,
TxnPos: 7443,
DataLen: 0,
},
[]byte("\x00\x00\x00\x00\x00\x00\x00\x00"),
/* deleted */ nil,
/* deleted */ 0,
},
},
},
{
TxnHeader{
Pos: 7691,
LenPrev: 248,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbacfd70a433,
Status: ' ',
User: []byte("user1.0"),
Description: []byte("step 1.0"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie3U\x057P0TJU\x0bx-generatorq\x02U\x0czodb/py2 (c)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 7784,
Oid: 8,
Tid: 0x0285cbacfd70a433,
PrevRevPos: 7342,
TxnPos: 7691,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04c1.0q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 7886,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad02222280,
Status: ' ',
User: []byte("user1.1"),
Description: []byte("step 1.1"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieEU\x05VAZ3UU\x0bx-generatorq\x02U\x0czodb/py2 (e)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 7979,
Oid: 6,
Tid: 0x0285cbad02222280,
PrevRevPos: 4442,
TxnPos: 7886,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04e1.1q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 8081,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad06d3a0cc,
Status: ' ',
User: []byte("user1.2"),
Description: []byte("step 1.2"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieNU\x05GSV4IU\x0bx-generatorq\x02U\x0czodb/py2 (b)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 8174,
Oid: 4,
Tid: 0x0285cbad06d3a0cc,
PrevRevPos: 5036,
TxnPos: 8081,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04b1.2q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 8276,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad0b851f19,
Status: ' ',
User: []byte("user1.3"),
Description: []byte("step 1.3"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieYU\x05A01OKU\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 8369,
Oid: 3,
Tid: 0x0285cbad0b851f19,
PrevRevPos: 6976,
TxnPos: 8276,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04g1.3q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 8471,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad10369d66,
Status: ' ',
User: []byte("user1.4"),
Description: []byte("step 1.4"),
Extension: []byte("\x80\x02}q\x01(U\x0bx-generatorq\x02U\x0czodb/py2 (g)U\tx-cookieWU\x051QPNPu."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 8564,
Oid: 3,
Tid: 0x0285cbad10369d66,
PrevRevPos: 8369,
TxnPos: 8471,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04g1.4q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 8666,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad14e81bb3,
Status: ' ',
User: []byte("user1.5"),
Description: []byte("step 1.5"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieCU\x05J7L05U\x0bx-generatorq\x02U\x0czodb/py2 (c)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 8759,
Oid: 8,
Tid: 0x0285cbad14e81bb3,
PrevRevPos: 7784,
TxnPos: 8666,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04c1.5q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 8861,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad19999a00,
Status: ' ',
User: []byte("user1.6"),
Description: []byte("step 1.6"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieAU\x05CM15ZU\x0bx-generatorq\x02U\x0czodb/py2 (f)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 8954,
Oid: 1,
Tid: 0x0285cbad19999a00,
PrevRevPos: 3848,
TxnPos: 8861,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04f1.6q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 9056,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad1e4b184c,
Status: ' ',
User: []byte("user1.7"),
Description: []byte("step 1.7"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieIU\x05AH816U\x0bx-generatorq\x02U\x0czodb/py2 (d)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 9149,
Oid: 2,
Tid: 0x0285cbad1e4b184c,
PrevRevPos: 5630,
TxnPos: 9056,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04d1.7q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 9251,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad22fc9699,
Status: ' ',
User: []byte("user1.8"),
Description: []byte("step 1.8"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieUU\x05BE3WHU\x0bx-generatorq\x02U\x0czodb/py2 (c)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 9344,
Oid: 8,
Tid: 0x0285cbad22fc9699,
PrevRevPos: 8759,
TxnPos: 9251,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04c1.8q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 9446,
LenPrev: 195,
Len: 195,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad27ae14e6,
Status: ' ',
User: []byte("user1.9"),
Description: []byte("step 1.9"),
Extension: []byte("\x80\x02}q\x01(U\x0bx-generatorq\x02U\x0czodb/py2 (c)U\tx-cookieWU\x05HPFAQu."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 9539,
Oid: 8,
Tid: 0x0285cbad27ae14e6,
PrevRevPos: 9344,
TxnPos: 9446,
DataLen: 52,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x04c1.9q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 9641,
LenPrev: 195,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad2c5f9333,
Status: ' ',
User: []byte("user1.10"),
Description: []byte("step 1.10"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieQU\x05DZM23U\x0bx-generatorq\x02U\x0czodb/py2 (e)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 9736,
Oid: 6,
Tid: 0x0285cbad2c5f9333,
PrevRevPos: 7979,
TxnPos: 9641,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05e1.10q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 9839,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad31111180,
Status: ' ',
User: []byte("user1.11"),
Description: []byte("step 1.11"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieOU\x05EIGHLU\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 9934,
Oid: 5,
Tid: 0x0285cbad31111180,
PrevRevPos: 5234,
TxnPos: 9839,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a1.11q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 10037,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad35c28fcc,
Status: ' ',
User: []byte("user1.12"),
Description: []byte("step 1.12"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie2U\x05Z9RFCU\x0bx-generatorq\x02U\x0czodb/py2 (c)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 10132,
Oid: 8,
Tid: 0x0285cbad35c28fcc,
PrevRevPos: 9539,
TxnPos: 10037,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05c1.12q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 10235,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad3a740e19,
Status: ' ',
User: []byte("user1.13"),
Description: []byte("step 1.13"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie7U\x05WGO4EU\x0bx-generatorq\x02U\x0czodb/py2 (e)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 10330,
Oid: 6,
Tid: 0x0285cbad3a740e19,
PrevRevPos: 9736,
TxnPos: 10235,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05e1.13q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 10433,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad3f258c66,
Status: ' ',
User: []byte("user1.14"),
Description: []byte("step 1.14"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie5U\x05757DJU\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 10528,
Oid: 3,
Tid: 0x0285cbad3f258c66,
PrevRevPos: 8564,
TxnPos: 10433,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g1.14q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 10631,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad43d70ab3,
Status: ' ',
User: []byte("user1.15"),
Description: []byte("step 1.15"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieXU\x055EOVHU\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 10726,
Oid: 3,
Tid: 0x0285cbad43d70ab3,
PrevRevPos: 10528,
TxnPos: 10631,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g1.15q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 10829,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad48888900,
Status: ' ',
User: []byte("user1.16"),
Description: []byte("step 1.16"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieCU\x05HO7L7U\x0bx-generatorq\x02U\x0czodb/py2 (d)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 10924,
Oid: 2,
Tid: 0x0285cbad48888900,
PrevRevPos: 9149,
TxnPos: 10829,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05d1.16q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 11027,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad4d3a074c,
Status: ' ',
User: []byte("user1.17"),
Description: []byte("step 1.17"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieUU\x05T159SU\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 11122,
Oid: 3,
Tid: 0x0285cbad4d3a074c,
PrevRevPos: 10726,
TxnPos: 11027,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g1.17q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 11225,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad51eb8599,
Status: ' ',
User: []byte("user1.18"),
Description: []byte("step 1.18"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie8U\x05T23V1U\x0bx-generatorq\x02U\x0czodb/py2 (f)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 11320,
Oid: 1,
Tid: 0x0285cbad51eb8599,
PrevRevPos: 8954,
TxnPos: 11225,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05f1.18q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 11423,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad569d03e6,
Status: ' ',
User: []byte("user1.19"),
Description: []byte("step 1.19"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieYU\x05UB55NU\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 11518,
Oid: 5,
Tid: 0x0285cbad569d03e6,
PrevRevPos: 9934,
TxnPos: 11423,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a1.19q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 11621,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad5b4e8233,
Status: ' ',
User: []byte("user1.20"),
Description: []byte("step 1.20"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieZU\x05IKOSRU\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 11716,
Oid: 3,
Tid: 0x0285cbad5b4e8233,
PrevRevPos: 11122,
TxnPos: 11621,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g1.20q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 11819,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad60000080,
Status: ' ',
User: []byte("user1.21"),
Description: []byte("step 1.21"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieSU\x057JLTHU\x0bx-generatorq\x02U\x0czodb/py2 (g)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 11914,
Oid: 3,
Tid: 0x0285cbad60000080,
PrevRevPos: 11716,
TxnPos: 11819,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05g1.21q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 12017,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad64b17ecc,
Status: ' ',
User: []byte("user1.22"),
Description: []byte("step 1.22"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieIU\x05USN06U\x0bx-generatorq\x02U\x0czodb/py2 (e)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 12112,
Oid: 6,
Tid: 0x0285cbad64b17ecc,
PrevRevPos: 10330,
TxnPos: 12017,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05e1.22q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 12215,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad6962fd19,
Status: ' ',
User: []byte("user1.23"),
Description: []byte("step 1.23"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookie2U\x05UXAETU\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 12310,
Oid: 5,
Tid: 0x0285cbad6962fd19,
PrevRevPos: 11518,
TxnPos: 12215,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a1.23q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 12413,
LenPrev: 198,
Len: 198,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad6e147b66,
Status: ' ',
User: []byte("user1.24"),
Description: []byte("step 1.24"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieHU\x05AT11FU\x0bx-generatorq\x02U\x0czodb/py2 (a)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 12508,
Oid: 5,
Tid: 0x0285cbad6e147b66,
PrevRevPos: 12310,
TxnPos: 12413,
DataLen: 53,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a1.24q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 12611,
LenPrev: 198,
Len: 217,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad77777800,
Status: ' ',
User: []byte("root1.0\nYour\nMagesty "),
Description: []byte("undo 1.0\nmore detailed description\n\nzzz ...\t"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieMU\x051G51MU\x0bx-generatorq\x02U\x1czodb/py2 (undo AoXLrWSxfsw=)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 12770,
Oid: 6,
Tid: 0x0285cbad77777800,
PrevRevPos: 12112,
TxnPos: 12611,
DataLen: 0,
},
[]byte("\x00\x00\x00\x00\x00\x00(Z"),
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05e1.13q\x02."),
/* copy from */ 0x0285cbad3a740e19,
},
},
},
{
TxnHeader{
Pos: 12828,
LenPrev: 217,
Len: 218,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad7c28f64c,
Status: ' ',
User: []byte("root1.1\nYour\nMagesty "),
Description: []byte("undo 1.1\nmore detailed description\n\nzzz ...\t\t"),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieVU\x05JCDRHU\x0bx-generatorq\x02U\x1czodb/py2 (undo AoXLrW4Ue2Y=)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 12988,
Oid: 5,
Tid: 0x0285cbad7c28f64c,
PrevRevPos: 12508,
TxnPos: 12828,
DataLen: 0,
},
[]byte("\x00\x00\x00\x00\x00\x000\x16"),
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x05a1.23q\x02."),
/* copy from */ 0x0285cbad6962fd19,
},
},
},
{
TxnHeader{
Pos: 13046,
LenPrev: 218,
Len: 409,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad80da7499,
Status: ' ',
User: []byte(""),
Description: []byte("predelete 6"),
Extension: []byte(""),
},
},
[]txnEntry{
{
DataHeader{
Pos: 13080,
Oid: 0,
Tid: 0x0285cbad80da7499,
PrevRevPos: 7068,
TxnPos: 13046,
DataLen: 232,
},
[]byte("\x80\x02cpersistent.mapping\nPersistentMapping\nq\x01.\x80\x02}q\x02U\x04dataq\x03}q\x04(U\x01aU\x08\x00\x00\x00\x00\x00\x00\x00\x05q\x05czodbtools.test.gen_testdata\nObject\nq\x06\x86QU\x01cU\x08\x00\x00\x00\x00\x00\x00\x00\x08q\x07h\x06\x86QU\x01bU\x08\x00\x00\x00\x00\x00\x00\x00\x04q\x08h\x06\x86QU\x01eU\x08\x00\x00\x00\x00\x00\x00\x00\tq\th\x06\x86QU\x01dU\x08\x00\x00\x00\x00\x00\x00\x00\x02q\nh\x06\x86QU\x01gU\x08\x00\x00\x00\x00\x00\x00\x00\x03q\x0bh\x06\x86QU\x01fU\x08\x00\x00\x00\x00\x00\x00\x00\x01q\x0ch\x06\x86Qus."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
{
DataHeader{
Pos: 13354,
Oid: 9,
Tid: 0x0285cbad80da7499,
PrevRevPos: 0,
TxnPos: 13046,
DataLen: 51,
},
[]byte("\x80\x02czodbtools.test.gen_testdata\nObject\nq\x01.\x80\x02U\x03e1*q\x02."),
/* same as ^^^ */ sameAsRaw,
/* no copy */ 0,
},
},
},
{
TxnHeader{
Pos: 13455,
LenPrev: 409,
Len: 248,
TxnInfo: zodb.TxnInfo{
Tid: 0x0285cbad858bf2e6,
Status: ' ',
User: []byte("root1\nYour\nRoyal\nMagesty' \x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"),
Description: []byte("delete 1\nalpha beta gamma'delta\"lambda\n\nqqq ..."),
Extension: []byte("\x80\x02}q\x01(U\tx-cookieSU\x05MC4OMU\x0bx-generatorq\x02U\x13zodb/py2 (delete 6)u."),
},
},
[]txnEntry{
{
DataHeader{
Pos: 13645,
Oid: 6,
Tid: 0x0285cbad858bf2e6,
PrevRevPos: 12770,
TxnPos: 13455,
DataLen: 0,
},
[]byte("\x00\x00\x00\x00\x00\x00\x00\x00"),
/* deleted */ nil,
/* deleted */ 0,
},
},
},
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment