zodb.go 17.5 KB
Newer Older
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
1
// Copyright (C) 2016-2018  Nexedi SA and Contributors.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
2 3 4 5 6 7 8
//                          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
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
9 10 11 12
// 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.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
13 14 15 16 17
//
// 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.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
18
// See https://www.nexedi.com/licensing for rationale and options.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
19

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
20
// Package zodb provides API to work with ZODB databases.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
21 22
//
// ZODB (http://zodb.org) was originally created in Python world by Jim Fulton et al.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
23
// Data model and API this package provides are partly based on ZODB/py
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
24 25
// (https://github.com/zopefoundation/ZODB) to maintain compatibility in
// between Python and Go implementations.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
26 27 28
//
// Data model
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
29 30 31 32 33
// A ZODB database is conceptually modeled as transactional log of changes to objects.
// Oid identifies an object and Tid - a transaction. A transaction can change
// several objects and also has metadata, like user and description, associated
// with it. If an object is changed by transaction, it is said that there is
// revision of the object with particular object state committed by that transaction.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
34
// Object revision is the same as tid of the transaction that modified the object.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
35 36
// The combination of object identifier and particular revision (serial)
// uniquely addresses corresponding data record.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
37
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
38 39 40 41 42 43 44 45
// Tids of consecutive database transactions are monotonically increasing and
// are connected with time when transaction in question was committed.
// This way, besides identifying a transaction with changes, Tid can also be
// used to specify whole database state constructed by all cumulated
// transaction changes from database beginning up to, and including,
// transaction specified by it. Xid is "extended" oid that specifies particular
// object state: it is (oid, at) pair that is mapped to object's latest
// revision with serial ≤ at.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
46
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
47 48
// Object state data is generally opaque, but is traditionally based on Python
// pickles in ZODB/py world.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
49
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
50 51
// An object can reference other objects in the database by their oid.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
52
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
53 54 55
// Storage layer
//
// The storage layer provides access to a ZODB database in terms of database
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
56
// records with raw bytes payload.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
57
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
58
// At storage level a ZODB database can be opened with OpenStorage. Once opened
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
59
// IStorage interface is returned that represents access to the database.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
60
// Please see IStorage, and interfaces it embeds, for details.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
61 62 63
//
//
// Application layer
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
64
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
65
// The application layer provides access to a ZODB database in terms of in-RAM
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
66
// application-level objects whose in-RAM state is synchronized with data in the database. For
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
67
// the synchronization to work, objects must be explicitly activated before
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
68
// access (contrary to zodb/py where activation is implicit, hooked into
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
69 70
// __getattr__), for example:
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
71 72 73
//	var obj *MyObject // *MyObject must implement IPersistent (see below)
//	... // init obj pointer, usually by traversing from another persistent object.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
74 75 76 77 78 79 80 81 82
//	// make sure object's in-RAM data is present.
//	//
//	// ZODB will load corresponding data and decode it into obj.
//	// On success, obj will be live and application can use its state.
//	err := obj.PActivate(ctx)
//	if err != nil {
//		return ... // handle error
//	}
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
83
//	obj.xxx // use object.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
84 85 86 87
//	if ... {
//		obj.xxx++     // change object.
//		obj.PModify() // let persistency layer know we modified the object.
//	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
88 89
//
//	// tell persistency layer we no longer need obj's in-RAM data to be present.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
90
//	// if obj was not modified, its in-RAM state might go away after.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
91 92
//	obj.PDeactivate()
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
93 94
// IPersistent interface describes the details of the activation protocol.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
95 96 97
// For MyObject to implement IPersistent it must embed Persistent type.
// MyObject also has to register itself to persistency machinery with RegisterClass.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
98
// In-RAM application objects are handled in groups. During the scope of
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
99
// corresponding in-progress transaction(*), a group corresponds to particular
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
100 101 102 103 104 105 106
// view of the database (at) and has isolation guarantee from further database
// transactions, and from in-progress changes to in-RAM objects in other
// groups.
//
// If object₁ references object₂ in the database, the database reference will
// be represented with corresponding reference between in-RAM application
// objects. If there are multiple database references to one object, it will be
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
107
// represented by the same number of references to only one in-RAM application object.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
108
// An in-RAM application object can have reference to another in-RAM
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
109
// application object only from the same group(+).
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
110 111
// Reference cycles are also allowed. In general objects graph in the database
// is isomorphly mapped to application objects graph in RAM.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
112
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
113 114 115
// A particular view of the database together with corresponding group of
// application objects isolated for modifications is represented by Connection.
// Connection is also sometimes called a "jar" in ZODB terminology.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
116
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
117
// DB represents a handle to database at application level and contains pool
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
118 119 120 121
// of connections. DB.Open opens database connection. The connection will be
// automatically put back into DB pool for future reuse after corresponding
// transaction is complete. DB thus provides service to maintain live objects
// cache and reuse live objects from transaction to transaction.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
122
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
123
// Note that it is possible to have several DB handles to the same database.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
124
// This might be useful if application accesses distinctly different sets of
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
125
// objects in different transactions and knows beforehand which set it will be
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
126
// next time. Then, to avoid huge live cache misses, it makes sense to keep DB
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
127
// handles opened for every possible case of application access.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
128 129
//
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
130 131
// All DB, Connection and object activation protocol is safe to access from
// multiple goroutines simultaneously.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
132
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
133
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
134 135 136 137
// --------
//
// (*) see package lab.nexedi.com/kirr/neo/go/transaction.
// (+) if both objects are from the same database.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
138
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
139 140
// Python data
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
141 142 143 144
// To maintain database data compatibility with ZODB/py, ZODB/go provides
// first class support for Python data. At storage-level PyData provides way to
// treat raw data record content as serialized by ZODB/py, and at application
// level types that are registered with state type providing PyStateful (see
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
145
// RegisterClass) are automatically (de)serialized as Python pickles(*).
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
146 147 148 149 150 151
//
// An example of application-level type with ZODB/py compatibility can be seen in
// package lab.nexedi.com/kirr/neo/go/zodb/btree which provides BTree handling
// for ZODB/go.
//
// --------
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
152
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
153
// (*) for pickle support package github.com/kisielk/og-rek is used.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
154
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
155
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
156 157
// Storage drivers
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
158 159 160 161
// To implement a ZODB storage one need to provide IStorageDriver interface and
// register it to ZODB with RegisterDriver. Package
// lab.nexedi.com/kirr/neo/go/zodb/wks links-in and registers support for
// well-known ZODB storages, such as FileStorage, ZEO and NEO.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
162
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
163 164
//
// Miscellaneous
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
165
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
166 167
// See also package lab.nexedi.com/kirr/neo/go/zodb/zodbtools and associated
// zodb command that provide tools for managing ZODB databases.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
168 169
package zodb

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
170
import (
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
171
	"context"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
172
	"fmt"
173 174

	"lab.nexedi.com/kirr/go123/mem"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
175 176
)

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
177 178 179 180
// ---- data model ----

// Tid is transaction identifier.
//
181 182
// In ZODB transaction identifiers are unique 64-bit integers corresponding to
// time when transaction in question was committed.
Kirill Smelkov's avatar
Kirill Smelkov committed
183
//
184 185 186
// This way tid can also be used to specify whole database state constructed
// by all cumulated transaction changes from database beginning up to, and
// including, transaction specified by tid.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
187
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
188
// 0 is invalid Tid, but canonical invalid Tid value is InvalidTid.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
189 190
type Tid uint64

Kirill Smelkov's avatar
Kirill Smelkov committed
191 192
// ZODB/py defines maxtid to be max signed int64 since Jun 7 2016:
// https://github.com/zopefoundation/ZODB/commit/baee84a6
Kirill Smelkov's avatar
Kirill Smelkov committed
193
// (same in neo/py with "SQLite does not accept numbers above 2^63-1" comment)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
194

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
195 196 197 198
const (
	TidMax     Tid = 1<<63 - 1 // 0x7fffffffffffffff
	InvalidTid Tid = 1<<64 - 1 // 0xffffffffffffffff
)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
199

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
200 201 202
// Oid is object identifier.
//
// In ZODB objects are uniquely identified by 64-bit integer.
203
// An object can have several revisions - each committed in different transaction.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
204 205 206
// The combination of object identifier and particular transaction (serial)
// uniquely addresses corresponding data record.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
207
// 0 is valid Oid and represents root database object.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
208
// InvalidOid represents an invalid Oid.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
209
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
210 211 212
// See also: Xid.
type Oid uint64

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
213 214
const InvalidOid Oid = 1<<64 - 1 // 0xffffffffffffffff

215 216 217 218
// Xid is "extended" oid - that fully specifies object and query for its revision.
//
// At specifies whole database state at which object identified with Oid should
// be looked up. The object revision is taken from latest transaction modifying
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
219
// the object with tid ≤ At.
220 221 222 223 224 225 226 227 228 229 230
//
// Note that Xids are not unique - the same object revision can be addressed
// with several xids.
//
// See also: Tid, Oid.
type Xid struct {
	At  Tid
	Oid Oid
}


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
231 232
// TxnInfo is metadata information about one transaction.
type TxnInfo struct {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
233 234 235 236
	Tid         Tid
	Status      TxnStatus
	User        []byte
	Description []byte
Kirill Smelkov's avatar
Kirill Smelkov committed
237 238 239

	// additional information about transaction. ZODB/py usually puts py
	// dict here but it can be arbitrary raw bytes.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
240 241 242
	Extension   []byte
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
243
// DataInfo is information about one object change.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
244 245
type DataInfo struct {
	Oid	Oid
246 247
	Tid	Tid    // changed by this transaction
	Data	[]byte // new object data; nil if object becomes deleted
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
248

249 250 251
	// DataTidHint is optional hint from a storage that the same data was
	// already originally committed in earlier transaction, for example in
	// case of undo. It is 0 if there is no such hint.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
252
	//
253 254 255 256 257 258
	// Storages are not obliged to provide this hint, and in particular it
	// is valid for a storage to always return this as zero.
	//
	// In ZODB/py world this originates from
	// https://github.com/zopefoundation/ZODB/commit/2b0c9aa4.
	DataTidHint Tid
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
259 260
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
261
// TxnStatus represents status of a transaction.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
262 263 264
type TxnStatus byte

const (
265 266 267
	TxnComplete   TxnStatus = ' ' // completed transaction that hasn't been packed
	TxnPacked     TxnStatus = 'p' // completed transaction that has been packed
	TxnInprogress TxnStatus = 'c' // checkpoint -- a transaction in progress; it's been thru vote() but not finish()
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
268 269 270
)


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
271
// ---- storage interfaces ----
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
272

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
273
// NoObjectError is the error which tells that there is no such object in the database at all.
274 275
type NoObjectError struct {
	Oid Oid
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
276 277
}

278 279 280 281 282 283 284 285 286 287 288 289 290
func (e NoObjectError) Error() string {
	return fmt.Sprintf("%s: no such object", e.Oid)
}

// NoDataError is the error which tells that object exists in the database,
// but there is no its non-empty revision satisfying search criteria.
type NoDataError struct {
	Oid Oid

	// DeletedAt explains object state wrt used search criteria:
	// - 0:  object was not created at time of searched xid.At
	// - !0: object was deleted by transaction with tid=DeletedAt
	DeletedAt Tid
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
291 292
}

293 294 295 296 297 298
func (e *NoDataError) Error() string {
	if e.DeletedAt == 0 {
		return fmt.Sprintf("%s: object was not yet created", e.Oid)
	} else {
		return fmt.Sprintf("%s: object was deleted @%s", e.Oid, e.DeletedAt)
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
299
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
300

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
301
// OpError is the error returned by IStorageDriver operations.	XXX -> by ZODB operations?
302 303 304 305
type OpError struct {
	URL  string	 // URL of the storage
	Op   string	 // operation that failed
	Args interface{} // operation arguments, if any
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
306
	Err  error	 // actual error that occurred during the operation
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
307 308
}

309 310 311 312 313
func (e *OpError) Error() string {
	s := e.URL + ": " + e.Op
	if e.Args != nil {
		s += fmt.Sprintf(" %s", e.Args)
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
314
	// XXX if e.Err = OpError with the same URL - don't print URL twice.
315 316
	s += ": " + e.Err.Error()
	return s
317 318
}

319
func (e *OpError) Cause() error {
320 321 322 323
	return e.Err
}


Kirill Smelkov's avatar
.  
Kirill Smelkov committed
324
// IStorage is the interface provided by opened ZODB storage.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
325
type IStorage interface {
326 327
	IStorageDriver

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
328 329 330
	Prefetcher
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
331
// Prefetcher provides functionality to prefetch objects.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
332
type Prefetcher interface {
333 334 335 336 337
	// Prefetch prefetches object addressed by xid.
	//
	// If data is not yet in cache loading for it is started in the background.
	// Prefetch is not blocking operation and does not wait for loading, if any was
	// started, to complete.
338 339
	//
	// Prefetch does not return any error.
340
	// Prefetch is noop if storage was opened with NoCache option.
341 342 343
	Prefetch(ctx context.Context, xid Xid)
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
344
// IStorageDriver is the raw interface provided by ZODB storage drivers.
345
type IStorageDriver interface {
346 347 348
	// URL returns URL of how the storage was opened
	URL() string

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
349 350 351
	// Close closes storage
	Close() error

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
352
	// LastTid returns the id of the last committed transaction.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
353
	//
354
	// If no transactions have been committed yet, LastTid returns 0.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
355
	LastTid(ctx context.Context) (Tid, error)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
356

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
357 358
	Loader
	Iterator
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
359 360 361 362 363 364 365 366 367

	// Notifier returns storage driver notifier.
	//
	// The notifier represents invalidation channel (notify about changes
	// made to DB not by us from outside).	XXX
	//
	// To simplify drivets, there must be only 1 logical user of
	// storage-driver level notifier interface. Ccontrary IStorage allows
	// for several users of notification channel.	XXX ok?
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
368 369 370 371
	//
	// XXX -> nil, if driver does not support notifications?
	// XXX or always support them, even with FileStorage (inotify)?
	//Notifier() Notifier
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
372 373
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
374
// Loader provides functionality to load objects.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
375
type Loader interface {
376 377
	// Load loads object data addressed by xid from database.
	//
378 379 380 381 382
	// Returned are:
	//
	//	- if there is data to load: buf is non-empty, serial indicates
	//	  transaction which matched xid criteria and err=nil.
	//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
383 384
	//	  caller must not modify buf memory.
	//
385
	// otherwise buf=nil, serial=0 and err is *OpError with err.Err
386
	// describing the error cause:
387
	//
388 389 390 391 392 393 394 395
	//	- *NoObjectError if there is no such object in database at all,
	//	- *NoDataError   if object exists in database but there is no
	//	                 its data matching xid,
	//	- some other error indicating e.g. IO problem.
	//
	//
	// NOTE 1: ZODB/py provides 2 entrypoints in IStorage for loading:
	// loadSerial and loadBefore but in ZODB/go we have only Load which is
396
	// a bit different from both:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
397
	//
398
	//	- Load loads object data for object at database state specified by xid.At
399
	//	- loadBefore loads object data for object at database state previous to xid.At
400
	//	  it is thus equivalent to Load(..., xid.At-1)
401
	//	- loadSerial loads object data from revision exactly modified
402 403 404
	//	  by transaction with tid = xid.At.
	//	  it is thus equivalent to Load(..., xid.At) with followup
	//	  check that returned serial is exactly xid.At(*)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
405
	//
406
	// (*) loadSerial is used only in a few places in ZODB/py - mostly in
407 408 409
	//     conflict resolution code where plain Load semantic - without
	//     checking object was particularly modified at that revision - would
	//     suffice.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
410
	//
411 412 413 414 415 416 417
	// NOTE 2: in ZODB/py loadBefore, in addition to serial, also returns
	// serial_next, which constraints storage implementations unnecessarily
	// and is used only in client cache.
	//
	// In ZODB/go Cache shows that it is possible to build efficient client
	// cache without serial_next returned from Load. For this reason in ZODB/go
	// Load specification comes without specifying serial_next return.
418
	Load(ctx context.Context, xid Xid) (buf *mem.Buf, serial Tid, err error)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
419
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
420

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
421
// Committer provides functionality to commit transactions.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
422
type Committer interface {
423
	// TODO: write mode
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
424

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
425 426
	// Store(ctx, oid Oid, serial Tid, data []byte, txn ITransaction) error
	// StoreKeepCurrent(ctx, oid Oid, serial Tid, txn ITransaction)
427 428 429 430 431

	// TpcBegin(txn)
	// TpcVote(txn)
	// TpcFinish(txn, callback)
	// TpcAbort(txn)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
432 433
}

434

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
435
// Notifier allows to be notified of changes made to database by other clients.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
436
type Notifier interface {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
437 438 439 440 441 442

	// Read returns next notification event.
	//
	// XXX ...
	// XXX overflow -> special error
	Read(ctx context.Context) (Tid, []Oid, error)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
443
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
444 445


Kirill Smelkov's avatar
Kirill Smelkov committed
446 447
	// TODO: History(ctx, oid, size=1)

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
448 449
// Iterator provides functionality to iterate through storage transactions sequentially.
type Iterator interface {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
450 451
	// Iterate creates iterator to iterate storage in [tidMin, tidMax] range.
	//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
452 453 454
	// Iterate does not return any error. If there was error when setting
	// iteration up - it will be returned on first NextTxn call.
	//
455
	// TODO allow iteration both ways (forward & backward)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
456
	Iterate(ctx context.Context, tidMin, tidMax Tid) ITxnIterator
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
457 458
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
459 460
// ITxnIterator is the interface to iterate transactions.
type ITxnIterator interface {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
461 462
	// NextTxn yields information about next database transaction:
	// 1. transaction metadata, and
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
463
	// 2. iterator over transaction's data records.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
464
	// transaction metadata stays valid until next call to NextTxn().
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
465
	// end of iteration is indicated with io.EOF
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
466
	NextTxn(ctx context.Context) (*TxnInfo, IDataIterator, error)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
467 468
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
469 470
// IDataIterator is the interface to iterate data records.
type IDataIterator interface {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
471 472
	// NextData yields information about next storage data record.
	// returned data stays valid until next call to NextData().
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
473
	// end of iteration is indicated with io.EOF
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
474
	NextData(ctx context.Context) (*DataInfo, error)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
475 476 477 478
}

// ---- misc ----

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
479
// Valid returns whether tid is in valid transaction identifiers range.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
480
func (tid Tid) Valid() bool {
481 482
	// NOTE 0 is invalid tid
	if 0 < tid && tid <= TidMax {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
483 484 485 486 487 488
		return true
	} else {
		return false
	}
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
489
// Valid returns true if transaction status value is well-known and valid.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
490 491 492 493 494 495 496 497
func (ts TxnStatus) Valid() bool {
	switch ts {
	case TxnComplete, TxnPacked, TxnInprogress:
		return true

	default:
		return false
	}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
498
}