Documentation ¶
Overview ¶
Package syncbase defines the Syncbase client library.
Index ¶
- func RunInBatch(ctx *context.T, d Database, opts wire.BatchOptions, ...) error
- func UUID() string
- type BatchDatabase
- type Blob
- type BlobReader
- type BlobStatus
- type BlobWriter
- type ChangeType
- type Collection
- type Conflict
- type ConflictResolver
- type ConflictRow
- type ConflictRowSet
- type ConflictScanSet
- type Database
- type DatabaseHandle
- type EntityType
- type PrefixRange
- type Resolution
- type ResolvedRow
- type ResultStream
- type Row
- type RowRange
- type ScanStream
- type Schema
- type Service
- type Stream
- type Syncgroup
- type Value
- type WatchChange
- type WatchStream
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RunInBatch ¶
func RunInBatch(ctx *context.T, d Database, opts wire.BatchOptions, fn func(b BatchDatabase) error) error
RunInBatch runs the given fn in a batch, managing retries and commit/abort. Writable batches are committed, retrying if commit fails. Readonly batches are aborted.
Types ¶
type BatchDatabase ¶
type BatchDatabase interface { DatabaseHandle // Commit persists the pending changes to the database. // If the batch is readonly, Commit() will fail with ErrReadOnlyBatch; Abort() // should be used instead. Commit(ctx *context.T) error // Abort notifies the server that any pending changes can be discarded. // It is not strictly required, but it may allow the server to release locks // or other resources sooner than if it was not called. Abort(ctx *context.T) error }
BatchDatabase is a handle to a set of reads and writes to the database that should be considered an atomic unit. See BeginBatch() for concurrency semantics. TODO(sadovsky): If/when needed, add a CommitWillFail() method so that clients can avoid doing extra work inside a doomed batch. TODO(ivanpi): Document Abort-after-failed-Commit semantics and update all client RunInBatch methods.
type Blob ¶
type Blob interface { // Ref returns Syncbase's BlobRef for this blob. Ref() wire.BlobRef // Put appends the byte stream to the blob. Put(ctx *context.T) (BlobWriter, error) // Commit marks the blob as immutable. Commit(ctx *context.T) error // Size returns the count of bytes written as part of the blob // (committed or uncommitted). Size(ctx *context.T) (int64, error) // Delete locally deletes the blob (committed or uncommitted). Delete(ctx *context.T) error // Get returns the byte stream from a committed blob starting at offset. Get(ctx *context.T, offset int64) (BlobReader, error) // Fetch initiates fetching a blob if not locally found. priority // controls the network priority of the blob. Higher priority blobs are // fetched before the lower priority ones. However an ongoing blob // transfer is not interrupted. Status updates are streamed back to the // client as fetch is in progress. Fetch(ctx *context.T, priority uint64) (BlobStatus, error) // Pin locally pins the blob so that it is not evicted. Pin(ctx *context.T) error // Unpin locally unpins the blob so that it can be evicted if needed. Unpin(ctx *context.T) error // Keep locally caches the blob with the specified rank. Lower // ranked blobs are more eagerly evicted. Keep(ctx *context.T, rank uint64) error }
Blob is the interface for a Blob in the store.
type BlobReader ¶
type BlobReader interface { // Advance() stages bytes so that they may be retrieved via // Value(). Returns true iff there are bytes to retrieve. Advance() must // be called before Value() is called. The caller is expected to read // until Advance() returns false, or to call Cancel(). Advance() bool // Value() returns the bytes that were staged by Advance(). May panic if // Advance() returned false or was not called. Never blocks. Value() []byte // Err() returns any error encountered by Advance. Never blocks. Err() error // Cancel notifies the stream provider that it can stop producing // elements. The client must call Cancel if it does not iterate through // all elements (i.e. until Advance returns false). Cancel is idempotent // and can be called concurrently with a goroutine that is iterating via // Advance. Cancel causes Advance to subsequently return false. Cancel // does not block. Cancel() }
BlobReader is an interface for getting a blob.
type BlobStatus ¶
type BlobStatus interface { // Advance() stages an item so that it may be retrieved via // Value(). Returns true iff there are items to retrieve. Advance() must // be called before Value() is called. The caller is expected to read // until Advance() returns false, or to call Cancel(). Advance() bool // Value() returns the item that was staged by Advance(). May panic if // Advance() returned false or was not called. Never blocks. Value() wire.BlobFetchStatus // Err() returns any error encountered by Advance. Never blocks. Err() error // Cancel notifies the stream provider that it can stop producing // elements. The client must call Cancel if it does not iterate through // all elements (i.e. until Advance returns false). Cancel is idempotent // and can be called concurrently with a goroutine that is iterating via // Advance. Cancel causes Advance to subsequently return false. Cancel // does not block. Cancel() }
BlobStatus is an interface for getting the status of a blob transfer.
type BlobWriter ¶
type BlobWriter interface { // Send places the bytes given by the client onto the output // stream. Returns errors encountered while sending. Blocks if there is // no buffer space. Send([]byte) error // Close indicates that no more bytes will be sent. Close() error }
BlobWriter is an interface for putting a blob.
type ChangeType ¶
type ChangeType uint32
ChangeType denotes the type of the change: Put or Delete.
const ( PutChange ChangeType = iota DeleteChange )
type Collection ¶
type Collection interface { // Id returns the id of this Collection. Id() wire.Id // FullName returns the object name (encoded) of this Collection. FullName() string // Exists returns true only if this Collection exists. // Requires: at least one tag on Collection, or Read or Write on Database. // Otherwise, ErrNoExistOrNoAccess is returned. Exists(ctx *context.T) (bool, error) // Create creates this Collection. // If perms is nil, the user blessing derived from the context is given all // permissions. Create(ctx *context.T, perms access.Permissions) error // Destroy destroys this Collection, permanently removing all of its data. // TODO(ivanpi): Prevent for synced Collections. Destroy(ctx *context.T) error // GetPermissions returns the current Permissions for the Collection. // The Read bit on the ACL does not affect who this Collection's rows are // synced to; all members of syncgroups that include this Collection will // receive the rows in this Collection. It only determines which clients // are allowed to retrieve the value using a Syncbase RPC. GetPermissions(ctx *context.T) (access.Permissions, error) // SetPermissions replaces the current Permissions for the Collection. SetPermissions(ctx *context.T, perms access.Permissions) error // Row returns the Row with the given key. Row(key string) Row // Get loads the value stored under the given key into the given value. // If the given value's type does not match the stored value's type, Get // will return an error. Expected usage: // var value MyType // if err := cx.Get(ctx, key, &value); err != nil { // return err // } Get(ctx *context.T, key string, value interface{}) error // Put writes the given value to this Collection under the given key. // TODO(kash): Can VOM handle everything that satisfies interface{}? // Need to talk to Todd. // TODO(sadovsky): Maybe distinguish insert from update (and also offer // upsert) so that last-one-wins can have deletes trump updates. Put(ctx *context.T, key string, value interface{}) error // Delete deletes the row for the given key. Delete(ctx *context.T, key string) error // DeleteRange deletes all rows in the given half-open range [start, limit). // If limit is "", all rows with keys >= start are included. // TODO(sadovsky): Document how this deletion is considered during conflict // detection: is it considered as a range deletion, or as a bunch of point // deletions? // See helpers Prefix(), Range(), SingleRow(). DeleteRange(ctx *context.T, r RowRange) error // Scan returns all rows in the given half-open range [start, limit). If limit // is "", all rows with keys >= start are included. // Concurrency semantics: It is legal to perform writes concurrently with // Scan. The returned stream reads from a consistent snapshot taken at the // time of the RPC (or at the time of BeginBatch, if in a batch), and will not // reflect subsequent writes to keys not yet reached by the stream. // See helpers Prefix(), Range(), SingleRow(). Scan(ctx *context.T, r RowRange) ScanStream }
Collection represents a set of Rows.
TODO(sadovsky): Currently we provide Get/Put/Delete methods on both Collection and Row, because we're not sure which will feel more natural. Eventually, we'll need to pick one.
type Conflict ¶
type Conflict struct { ReadSet *ConflictRowSet WriteSet *ConflictRowSet ScanSet *ConflictScanSet Batches map[uint64]syncbase.BatchInfo }
Conflict contains information to fully specify a conflict. Since syncbase supports batches there can be one or more rows within the batch that has a conflict. Each of these rows will be sent together as part of a single conflict. Each row contains an Id of the batch to which it belongs, enabling the client to group together rows that are part of a batch. Note that a single row can be part of more than one batch.
WriteSet contains rows that were written. ReadSet contains rows that were read within a batch corresponding to a row within the write set. ScanSet contains scans performed within a batch corresponding to a row within the write set. Batches is a map of unique ids to BatchInfo objects. The id is unique only in the context of a given conflict and is otherwise meaningless.
type ConflictResolver ¶
type ConflictResolver interface {
OnConflict(ctx *context.T, conflict *Conflict) Resolution
}
ConflictResolver interface allows the app to define resolution of conflicts that it requested to handle.
type ConflictRow ¶
type ConflictRow struct { Key string LocalValue Value RemoteValue Value AncestorValue Value BatchIds []uint64 }
ConflictRow represents a row under conflict. Key is the key for the row. LocalValue is the value present in the local db. RemoteValue is the value received via sync. AncestorValue is the value for the key which is the lowest common ancestor of the two values represented by LocalValue and RemoteValue. AncestorValue's state is NoExists if the ConflictRow is a part of the read set. BatchIds is a list of ids of all the batches that this row belongs to.
func (ConflictRow) VDLIsZero ¶
func (x ConflictRow) VDLIsZero() bool
type ConflictRowSet ¶
type ConflictRowSet struct { ByKey map[string]ConflictRow ByBatch map[uint64][]ConflictRow }
ConflictRowSet contains a set of rows under conflict. It provides two different ways to access the same set. ByKey is a map of ConflictRows keyed by the row key. ByBatch is a map of []ConflictRows keyed by batch id. This map lets the client access all ConflictRows within this set that contain a given hint.
func (ConflictRowSet) VDLIsZero ¶
func (x ConflictRowSet) VDLIsZero() bool
type ConflictScanSet ¶
ConflictScanSet contains a set of scans under conflict. ByBatch is a map of array of ScanOps keyed by batch id.
func (ConflictScanSet) VDLIsZero ¶
func (x ConflictScanSet) VDLIsZero() bool
type Database ¶
type Database interface { DatabaseHandle // Create creates this Database. // If perms is nil, the user blessing derived from the context is given all // permissions. Create(ctx *context.T, perms access.Permissions) error // Destroy destroys this Database, permanently removing all of its data. // TODO(sadovsky): Specify what happens to syncgroups. Destroy(ctx *context.T) error // Exists returns true only if this Database exists. // Requires: at least one tag on Database, or Read or Write on Service. // Otherwise, ErrNoExistOrNoAccess is returned. Exists(ctx *context.T) (bool, error) // BeginBatch creates a new batch. Instead of calling this function directly, // clients are encouraged to use the RunInBatch() helper function, which // detects "concurrent batch" errors and handles retries internally. // // Default concurrency semantics: // - Reads (e.g. gets, scans) inside a batch operate over a consistent // snapshot taken during BeginBatch(), and will see the effects of prior // writes performed inside the batch. // - Commit() may fail with ErrConcurrentBatch, indicating that after // BeginBatch() but before Commit(), some concurrent routine wrote to a key // that matches a key or row-range read inside this batch. // - Other methods will never fail with error ErrConcurrentBatch, even if it // is known that Commit() will fail with this error. // // Once a batch has been committed or aborted, subsequent method calls will // fail with no effect. // // Concurrency semantics can be configured using BatchOptions. // TODO(sadovsky): Use varargs for options. BeginBatch(ctx *context.T, opts wire.BatchOptions) (BatchDatabase, error) // SetPermissions and GetPermissions are included from the AccessController // interface. util.AccessController // Watch allows a client to watch for updates to the database. At least one // pattern must be specified. For each watch request, the client will receive // a reliable stream of watch events without reordering. Only rows matching at // least one of the patterns are returned. Rows in collections with no Read // access are also filtered out. // // If a nil ResumeMarker is provided, the WatchStream will begin with a Change // batch containing the initial state, always starting with an empty update // for the root entity. Otherwise, the WatchStream will contain only changes // since the provided ResumeMarker. // See watch.GlobWatcher for a detailed explanation of the behavior. Watch(ctx *context.T, resumeMarker watch.ResumeMarker, patterns []wire.CollectionRowPattern) WatchStream // Syncgroup returns the Syncgroup with the given relative name. // The user blessing is derived from the context. Syncgroup(ctx *context.T, name string) Syncgroup // SyncgroupForId returns the Syncgroup with the given user blessing and name. SyncgroupForId(id wire.Id) Syncgroup // ListSyncgroups returns a list of ids of all syncgroups attached to this // Database. The list is sorted by blessing, then by name. ListSyncgroups(ctx *context.T) ([]wire.Id, error) // CreateBlob creates a new blob and returns a handle to it. CreateBlob(ctx *context.T) (Blob, error) // Blob returns a handle to the blob with the given BlobRef. Blob(br wire.BlobRef) Blob // EnforceSchema compares the current schema version of the database with the // schema version provided when creating this database handle, and updates the // schema metadata if required. // // This method also registers a conflict resolver with Syncbase to receive // conflicts. Note: schema can be nil, in which case this method should not be // called and the caller is responsible for maintaining schema sanity. EnforceSchema(ctx *context.T) error // PauseSync pauses sync for this database. Incoming sync, as well as outgoing // sync of subsequent writes, will be disabled until ResumeSync is called. // PauseSync is idempotent. PauseSync(ctx *context.T) error // ResumeSync resumes sync for this database. ResumeSync is idempotent. ResumeSync(ctx *context.T) error // Close cleans up any state associated with this database handle, including // closing the conflict resolution stream (if open). Close() }
Database represents a set of Collections.
type DatabaseHandle ¶
type DatabaseHandle interface { // Id returns the id of this DatabaseHandle. Id() wire.Id // FullName returns the object name (encoded) of this DatabaseHandle. FullName() string // Collection returns the Collection with the given relative name. // The user blessing is derived from the context. Collection(ctx *context.T, name string) Collection // CollectionForId returns the Collection with the given user blessing and // name. CollectionForId(id wire.Id) Collection // ListCollections returns a list of ids of all Collections in this Database. // The list is sorted by blessing, then by name. ListCollections(ctx *context.T) ([]wire.Id, error) // Exec executes a syncQL query. // // A value must be provided for every positional parameter ('?' placeholder) // in the query. // // For select statements: // If no error is returned, Exec returns an array of headers (i.e. column // names) and a result stream with an array of values for each row that // matches the query. The number of values returned in each row of the // result stream will match the size of the headers array. // // For delete statements: // If no error is returned, Exec returns an array of headers with exactly one // column, "Count", and a result stream with an array containing a single // element of type vdl.Int64. The value represents the number of rows deleted. // // Concurrency semantics: It is legal to perform writes concurrently with // Exec. The returned stream reads from a consistent snapshot taken at the // time of the RPC (or at the time of BeginBatch, if in a batch), and will not // reflect subsequent writes to keys not yet reached by the stream. Exec(ctx *context.T, query string, params ...interface{}) ([]string, ResultStream, error) // GetResumeMarker returns a ResumeMarker that points to the current end of // the event log. GetResumeMarker(ctx *context.T) (watch.ResumeMarker, error) }
DatabaseHandle is the set of methods that work both with and without a batch. It allows clients to pass the handle to helper methods that are batch-agnostic.
type EntityType ¶
type EntityType uint32
EntityType denotes the type of the changed entity: Root, Collection, or Row. TODO(ivanpi): Consider adding syncgroup metadata and other types.
const ( EntityRoot EntityType = iota EntityCollection EntityRow )
type PrefixRange ¶
PrefixRange represents all rows with keys that have some prefix.
func Prefix ¶
func Prefix(prefix string) PrefixRange
type Resolution ¶
type Resolution struct {
ResultSet map[string]ResolvedRow
}
Resolution contains the application’s reply to a conflict. It must contain a resolved value for each conflict row within the WriteSet of the given conflict. ResultSet is a map of row key to ResolvedRow.
func (Resolution) VDLIsZero ¶
func (x Resolution) VDLIsZero() bool
type ResolvedRow ¶
ResolvedRow represents a result of resolution of a row under conflict. Key is the key for the row. Result is the result of the conflict resolution. Delete is represented by nil.
func (ResolvedRow) VDLIsZero ¶
func (x ResolvedRow) VDLIsZero() bool
type ResultStream ¶
type ResultStream interface { Stream // ResultCount returns the number of results for the stream element // prepared by the most recent call to Advance(). Requires that the // last call to Advance() was successful. ResultCount() int // ResultValue loads the result numbered i into the given value. // Requires 0 <= i < ResultCount(), and that the last call to Advance() // was successful. // Errors represent possible decoding errors for individual values, // rather than errors that would necessarily terminate the stream. Result(i int, value interface{}) error }
ResultStream is an interface for iterating through Exec query results.
type Row ¶
type Row interface { // Key returns the key for this Row. Key() string // FullName returns the object name (encoded) of this Row. FullName() string // Exists returns true only if this Row exists. // Requires: Read or Write on Collection. // Otherwise, ErrNoExistOrNoAccess is returned. Exists(ctx *context.T) (bool, error) // Get loads the value stored in this Row into the given value. If the given // value's type does not match the stored value's type, Get will return an // error. Expected usage: // var value MyType // if err := row.Get(ctx, &value); err != nil { // return err // } Get(ctx *context.T, value interface{}) error // Put writes the given value for this Row. Put(ctx *context.T, value interface{}) error // Delete deletes this Row. Delete(ctx *context.T) error }
Row represents a single row in a Collection.
type RowRange ¶
RowRange represents all rows with keys in [start, limit). If limit is "", all rows with keys >= start are included.
type ScanStream ¶
type ScanStream interface { Stream // Key returns the key of the element that was staged by Advance. // Key may panic if Advance returned false or was not called at all. // Key does not block. Key() string // Value returns the value of the element that was staged by Advance, or an // error if the value could not be decoded. // Value may panic if Advance returned false or was not called at all. // Value does not block. Value(value interface{}) error }
ScanStream is an interface for iterating through a collection of key-value pairs.
type Schema ¶
type Schema struct { Metadata wire.SchemaMetadata Resolver ConflictResolver }
Each database has a Schema associated with it which defines the current version of the database. When a new version of app wishes to change its data in a way that it is not compatible with the old app's data, the app must change the schema version and perform relevant upgrade logic. The conflict resolution rules are also associated with the schema version. Hence if the conflict resolution rules change then the schema version also must be bumped.
Schema provides metadata and a ConflictResolver for a given database. ConflictResolver is purely local and not persisted.
type Service ¶
type Service interface { // FullName returns the object name (encoded) of this Service. FullName() string // Database returns the Database with the given relative name. // The app blessing is derived from the context. // TODO(sadovsky): Revisit API for schema stuff. Database(ctx *context.T, name string, schema *Schema) Database // DatabaseForId returns the Database with the given app blessing and name. DatabaseForId(id wire.Id, schema *Schema) Database // ListDatabases returns a list of ids of all Databases at this Service. The // list is sorted by blessing, then by name. ListDatabases(ctx *context.T) ([]wire.Id, error) // SetPermissions and GetPermissions are included from the AccessController // interface. util.AccessController }
Service represents a Vanadium Syncbase service. Use NewService to get a Service.
func NewService ¶
type Stream ¶
type Stream interface { // Advance stages an element so the client can retrieve it. Advance returns // true iff there is an element to retrieve. The client must call Advance // before retrieving the element. The client must call Cancel if it does not // iterate through all elements (i.e. until Advance returns false). // Advance may block if an element is not immediately available. Advance() bool // Err returns a non-nil error iff the stream encountered any errors. Err does // not block. Err() error // Cancel notifies the stream provider that it can stop producing elements. // The client must call Cancel if it does not iterate through all elements // (i.e. until Advance returns false). Cancel is idempotent and can be called // concurrently with a goroutine that is iterating via Advance. // Cancel causes Advance to subsequently return false. Cancel does not block. Cancel() }
Stream is an interface for iterating through a collection of elements.
type Syncgroup ¶
type Syncgroup interface { // Create creates a new syncgroup with the given spec. // // Requires: Client must have at least Read access on the Database; all // Collections specified in prefixes must exist; Client must have at least // Read access on each of the Collection ACLs. Create(ctx *context.T, spec wire.SyncgroupSpec, myInfo wire.SyncgroupMemberInfo) error // Join joins a syncgroup. // // Requires: Client must have at least Read access on the Database and on the // syncgroup ACL. Join(ctx *context.T, syncbaseName string, expectedSyncbaseBlessings []string, myInfo wire.SyncgroupMemberInfo) (wire.SyncgroupSpec, error) // Leave leaves the syncgroup. Previously synced data will continue // to be available. // // Requires: Client must have at least Read access on the Database. Leave(ctx *context.T) error // Destroy destroys the syncgroup. Previously synced data will // continue to be available to all members. // // Requires: Client must have at least Read access on the Database, and must // have Admin access on the syncgroup ACL. Destroy(ctx *context.T) error // Eject ejects a member from the syncgroup. The ejected member // will not be able to sync further, but will retain any data it has already // synced. // // Requires: Client must have at least Read access on the Database, and must // have Admin access on the syncgroup ACL. Eject(ctx *context.T, member string) error // GetSpec gets the syncgroup spec. version allows for atomic // read-modify-write of the spec - see comment for SetSpec. // // Requires: Client must have at least Read access on the Database and on the // syncgroup ACL. GetSpec(ctx *context.T) (spec wire.SyncgroupSpec, version string, err error) // SetSpec sets the syncgroup spec. version may be either empty or // the value from a previous Get. If not empty, Set will only succeed if the // current version matches the specified one. // // Requires: Client must have at least Read access on the Database, and must // have Admin access on the syncgroup ACL. SetSpec(ctx *context.T, spec wire.SyncgroupSpec, version string) error // GetMembers gets the info objects for members of the syncgroup. // // Requires: Client must have at least Read access on the Database and on the // syncgroup ACL. GetMembers(ctx *context.T) (map[string]wire.SyncgroupMemberInfo, error) // Id returns the relative syncgroup name and blessing. Id() wire.Id }
Syncgroup is the interface for a syncgroup in the store.
type Value ¶
type Value struct { State syncbase.ValueState Val *vom.RawBytes WriteTs time.Time // TODO(jlodhia): Since field Selection cannot be package private in VDL, // review the ConflictResolution API to see if we should keep this field // or not. Selection syncbase.ValueSelection }
Value contains a specific version of data for the row under conflict along with the write timestamp and hints associated with the version. State defines whether the value is empty or not. It can be empty for reasons like Deleted or Unknown. WriteTs is the write timestamp for this value.
type WatchChange ¶
type WatchChange struct { // EntityType is the type of the entity - Root, Collection, or Row. EntityType EntityType // Collection is the id of the collection that was changed or contains the // changed row. Has zero value if EntityType is not Collection or Row. Collection wire.Id // Row is the key of the changed row. Empty if EntityType is not Row. Row string // ChangeType describes the type of the change, depending on the EntityType: // - for EntityRow: // * PutChange: the row exists in the collection, and Value can be called to // obtain the new value for this row. // * DeleteChange: the row was removed from the collection. // - for EntityCollection: // * PutChange: the collection exists, and CollectionInfo can be called to // obtain the collection info. // * DeleteChange: the collection was destroyed. // - for EntityRoot: // * PutChange: appears as the first (possibly only) change in the initial // state batch, only if watching from an empty ResumeMarker. This is the // only situation where an EntityRoot appears. ChangeType ChangeType // ResumeMarker provides a compact representation of all the messages that // have been received by the caller for the given Watch call. // This marker can be provided in the Request message to allow the caller // to resume the stream watching at a specific point without fetching the // initial state. ResumeMarker watch.ResumeMarker // FromSync indicates whether the change came from sync. If FromSync is false, // then the change originated from the local device. FromSync bool // If true, this WatchChange is followed by more WatchChanges that are in the // same batch as this WatchChange. Continued bool // contains filtered or unexported fields }
WatchChange represents a change to a watched entity.
func ToWatchChange ¶
func ToWatchChange(c watch.Change) *WatchChange
ToWatchChange converts a generic Change struct as defined in v.io/v23/services/watch to a Syncbase-specific WatchChange struct as defined in v.io/v23/syncbase.
func (*WatchChange) CollectionInfo ¶
func (c *WatchChange) CollectionInfo() *wire.StoreChangeCollectionInfo
CollectionInfo returns the collection info containing permissions that the watcher has on the collection. Panics if the change type is DeleteChange or the entity is not a Collection.
func (*WatchChange) Value ¶
func (c *WatchChange) Value(value interface{}) error
Value decodes the new value of the watched element. Panics if the change type is DeleteChange or the entity is not a Row.
type WatchStream ¶
type WatchStream interface { Stream // Change returns the element that was staged by Advance. // Change may panic if Advance returned false or was not called at all. // Change does not block. Change() WatchChange }
WatchStream is an interface for receiving database updates.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
Package crtestutil defines helpers for conflict resolution tests.
|
Package crtestutil defines helpers for conflict resolution tests. |