firestorebind

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 9, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package firestorebind provides typed, reflection-free entity binding for Firestore in Datastore mode, on top of github.com/shibukawa/tinygodriver/nosql/datastore.

A struct declares its properties once with firestore tags, tinybind-gen emits the codec, and the call site never builds a datastore.Value:

type Reading struct {
	ID      string    `firestore:"-,name"`
	Sensor  string    `firestore:"sensor"`
	At      time.Time `firestore:"at"`
	Celsius float64   `firestore:"celsius"`
}

ctx = firestorebind.WithClient(ctx, client)
got, err := firestorebind.Load[Reading](ctx, datastore.NameKey("Reading", "r-1"))

The client is not a parameter. It, the namespace and the database are facts of one process or one request, installed once with WithClient, so no call site and no generated signature carries them; see context.go.

Nothing here names a kind either. A kind belongs to the type, not to the deployment, so the generated Kind method supplies it and a key carries it. That is the one place this package's signatures are shorter than dynamobind's.

Dispatch is by type constraint rather than by a registry, so a type without generated code fails to compile instead of failing at run time on a missing registration. Nothing here reflects on application fields.

The driver has its own mapper, and it is a different one

nosql/datastore ships MarshalEntity behind the datastore struct tag. This package reads firestore instead, and the two disagree on every renamed property. Generation treats a field carrying datastore but not firestore as an error rather than as agreement; the driver's own documentation asks for exactly that.

What this package does not do

It adds no retry loop: the driver already retries with backoff, and restarts a transaction closure on contention. It hides no batch boundary: Query iterates, but QueryPage stays public and returns the cursor and the reason the batch ended. It declares no service limit of its own: MaxLookupKeys and the rest are the driver's constants, because a copied limit is what drifts. It swallows no error: every driver sentinel survives errors.Is and *datastore.Error survives errors.As through every helper here.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoClient = errors.New("firestorebind: no Datastore client in context")

ErrNoClient reports that a Context does not carry a Datastore client, or that a zero Handle was passed to an entry taking one. It is returned rather than panicking, so every entry stays an ordinary error-returning function.

Functions

func ClientFromContext

func ClientFromContext(ctx context.Context) (*datastore.Client, error)

ClientFromContext returns the client installed by WithClient.

It is the escape hatch for reaching the driver directly, for an operation this package does not wrap. Note that it applies no namespace: a key passed to the driver through it is sent as built. Pass keys through KeyFor or KeysFor first to place them where every wrapped entry of this package would.

func Count

func Count(ctx context.Context, q *datastore.Query, opts ...datastore.ReadOption) (int64, error)

Count runs an aggregation query and returns how many entities match.

It is not generic: a count decodes no entity, so there is no type to infer. It exists because counting by paging through keys costs a read per entity, and a wrapper that omitted it would push callers toward the expensive thing.

func CountOn added in v0.3.7

func CountOn(ctx context.Context, h Handle, q *datastore.Query, opts ...datastore.ReadOption) (int64, error)

CountOn is Count taking its Handle as an argument.

func CountTx

func CountTx(ctx context.Context, tx *Tx, q *datastore.Query) (int64, error)

CountTx counts matching entities inside a transaction.

func Insert

func Insert[T EntityEncoder](ctx context.Context, v T, opts ...datastore.WriteOption) (datastore.Key, error)

Insert writes v and fails if its key already exists.

This is put-if-absent, and it is a precondition the wire evaluates rather than a condition this package composes: the driver sends an insert mutation, and a collision is datastore.ErrAlreadyExists.

func InsertAll

func InsertAll[T EntityEncoder](ctx context.Context, vs []T, opts ...datastore.WriteOption) ([]datastore.Key, error)

InsertAll inserts many entities. Each fails independently with datastore.ErrAlreadyExists if its key exists; see StoreAll on chunking.

func InsertAllOn added in v0.3.7

func InsertAllOn[T EntityEncoder](ctx context.Context, h Handle, vs []T, opts ...datastore.WriteOption) ([]datastore.Key, error)

InsertAllOn is InsertAll taking its Handle as an argument.

func InsertOn added in v0.3.7

func InsertOn[T EntityEncoder](ctx context.Context, h Handle, v T, opts ...datastore.WriteOption) (datastore.Key, error)

InsertOn is Insert taking its Handle as an argument.

func KeyError

func KeyError(message string) error

KeyError reports a key that cannot be used for the operation, such as an incomplete key passed to a read.

func KeyFor

func KeyFor(ctx context.Context, key datastore.Key) datastore.Key

KeyFor stamps the Context's namespace onto a key, the way every wrapped entry of this package does before it sends one.

It exists for the ClientFromContext escape hatch. A key reaching the driver any other way is sent as built, which places it in the default namespace: for a multi-tenant caller that is a data-placement bug no test running in the default namespace can see, and for a test isolating itself in a namespace of its own it is a teardown that deletes nothing and reports success.

The key is returned unchanged when the Context carries no client, when no WithNamespace resolver was installed, when the resolver answers the empty string, and when the key already names a namespace. That last one is the point: an explicitly placed key is not silently moved.

There is no error to return, so there is none in the signature. A Context with no client meets ErrNoClient at the operation, not here.

func KeyForOn added in v0.3.7

func KeyForOn(ctx context.Context, h Handle, key datastore.Key) datastore.Key

KeyForOn is KeyFor taking its Handle as an argument. A zero Handle returns the key unchanged, as a Context carrying no client does.

func KeysFor

func KeysFor(ctx context.Context, keys []datastore.Key) []datastore.Key

KeysFor is KeyFor over a slice. It allocates only when the resolver would change something, which a caller looping over KeyFor cannot avoid.

func KeysForOn added in v0.3.7

func KeysForOn(ctx context.Context, h Handle, keys []datastore.Key) []datastore.Key

KeysForOn is KeysFor taking its Handle as an argument.

func Load

func Load[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, key datastore.Key, opts ...datastore.ReadOption) (T, error)

Load reads one entity by key and decodes it into T.

A key that matches nothing keeps the driver's datastore.ErrNoSuchEntity rather than returning a zero value, so a miss cannot be mistaken for an empty entity.

The decoded value carries its own key: the key fields declared with the name, id and parent tags are filled from the entity's key, not from its properties.

func LoadAll

func LoadAll[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, keys []datastore.Key, opts ...datastore.ReadOption) (values []T, missing, deferred []datastore.Key, err error)

LoadAll reads many entities by key.

The three results are three different facts and are not collapsed: values are what came back, missing are keys with no entity, and deferred are keys the server chose not to read this time. A deferred key is not a missing one, and retrying it is the caller's decision, per the driver's own contract.

Values come back in the server's reply order, which is not the order of keys. A caller that needs its own order matches on the decoded key.

Keys are chunked at datastore.MaxLookupKeys, so a caller passing more than one lookup accepts does not meet datastore.ErrTooManyKeys.

func LoadAllOn added in v0.3.7

func LoadAllOn[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, h Handle, keys []datastore.Key, opts ...datastore.ReadOption) (values []T, missing, deferred []datastore.Key, err error)

LoadAllOn is LoadAll taking its Handle as an argument.

func LoadAllTx

func LoadAllTx[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, tx *Tx, keys []datastore.Key) (values []T, missing, deferred []datastore.Key, err error)

LoadAllTx reads many entities by key inside a transaction. The three results mean what they do in LoadAll.

func LoadOn added in v0.3.7

func LoadOn[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, h Handle, key datastore.Key, opts ...datastore.ReadOption) (T, error)

LoadOn is Load taking its Handle as an argument.

func LoadTx

func LoadTx[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, tx *Tx, key datastore.Key, opts ...datastore.ReadOption) (T, error)

LoadTx reads one entity by key inside a transaction.

It is a separate function from Load rather than a method on Tx because Go methods cannot take type parameters, and separate from Load itself because a transactional read has to travel through the transaction handle. A Context carrying the handle instead would make one call site mean two different things depending on which Context reached it.

func Query

func Query[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, q *datastore.Query, opts ...datastore.ReadOption) iter.Seq2[T, error]

Query iterates every entity a query matches, requesting batches as the range advances.

One range can issue many requests, and a query with only a kind walks every entity of that kind. The iterator reports no batch boundary, no cursor and no SkippedResults, so a query that steps over far more than it returns looks the same as one that does not, and an interrupted run cannot be resumed. Use QueryPage when any of that matters.

Iteration stops at the first error, which is yielded once with the zero value of T. A break stops it without issuing a further request.

func QueryOn added in v0.3.7

func QueryOn[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, h Handle, q *datastore.Query, opts ...datastore.ReadOption) iter.Seq2[T, error]

QueryOn is Query taking its Handle as an argument. The Handle is resolved once for the whole range rather than once per batch.

func Remove

func Remove[T Keyer](ctx context.Context, v T, opts ...datastore.WriteOption) error

Remove deletes the entity identified by v's key. Only the key of v is read.

func RemoveAll

func RemoveAll[T Keyer](ctx context.Context, vs []T, opts ...datastore.WriteOption) error

RemoveAll deletes the entities identified by the keys of vs.

Deleting a key that holds nothing succeeds, as it does on the wire, so a caller cannot tell from the result which of them existed. It is RemoveKeys over the keys the values carry, and shares its chunking and its refusal of an incomplete key.

func RemoveAllOn added in v0.3.7

func RemoveAllOn[T Keyer](ctx context.Context, h Handle, vs []T, opts ...datastore.WriteOption) error

RemoveAllOn is RemoveAll taking its Handle as an argument.

func RemoveKeys

func RemoveKeys(ctx context.Context, keys []datastore.Key, opts ...datastore.WriteOption) error

RemoveKeys deletes the entities named by keys.

It is the counterpart of QueryKeysPage, which hands back keys: find these keys, then delete them is the shape of every cleanup, teardown and administrative sweep, and RemoveAll cannot express it because it needs a bound value to take the key from.

Deleting a key that holds nothing succeeds, as it does on the wire, so the result cannot say which of them existed. An incomplete key is refused before anything is sent, since it names no entity to delete.

A commit is not a transaction. Chunking means a large sweep commits in pieces, and a failure leaves the earlier pieces deleted; use Run when the deletion has to be all-or-nothing, subject to datastore.MaxTransactionBytes.

func RemoveKeysOn added in v0.3.7

func RemoveKeysOn(ctx context.Context, h Handle, keys []datastore.Key, opts ...datastore.WriteOption) error

RemoveKeysOn is RemoveKeys taking its Handle as an argument.

func RemoveOn added in v0.3.7

func RemoveOn[T Keyer](ctx context.Context, h Handle, v T, opts ...datastore.WriteOption) error

RemoveOn is Remove taking its Handle as an argument.

func Run

func Run(ctx context.Context, fn func(*Tx) error, opts ...datastore.TxOption) error

Run executes fn inside a read-write transaction and commits what it queued.

fn can run more than once. Contention makes the server answer ABORTED, and the driver re-runs the whole closure rather than resending the commit, because the reads it was built on are stale. So fn must be free of side effects outside the transaction: a message sent or a file written inside it can happen several times. That is stated here rather than enforced, because it cannot be.

A closure that returns an error writes nothing and needs no rollback, since the mutations travel with the commit that never happens.

No retry loop is added here. The driver's own restart budget applies, and datastore.WithTxRetries configures it.

func RunOn added in v0.3.7

func RunOn(ctx context.Context, h Handle, fn func(*Tx) error, opts ...datastore.TxOption) error

RunOn is Run taking its Handle as an argument. The entries inside the closure take the *Tx, which already carries the tenancy, so they have no second form.

func RunReadOnly

func RunReadOnly(ctx context.Context, fn func(*Tx) error, opts ...datastore.TxOption) error

RunReadOnly executes fn against a consistent snapshot.

It queues no writes, so a read-only transaction never contends and never re-runs. Use it when several reads have to agree with each other and nothing is being changed.

func RunReadOnlyOn added in v0.3.7

func RunReadOnlyOn(ctx context.Context, h Handle, fn func(*Tx) error, opts ...datastore.TxOption) error

RunReadOnlyOn is RunReadOnly taking its Handle as an argument.

func Store

func Store[T EntityEncoder](ctx context.Context, v T, opts ...datastore.WriteOption) (datastore.Key, error)

Store writes v as a whole entity, replacing any entity with the same key.

It is an upsert. The returned key is the stored one, which differs from v's own only when v carried an incomplete key and the server allocated an id; a caller storing a new entity assigns the result back rather than expecting v to have been mutated, since v was passed by value.

func StoreAll

func StoreAll[T EntityEncoder](ctx context.Context, vs []T, opts ...datastore.WriteOption) ([]datastore.Key, error)

StoreAll upserts many entities, chunked into as few commits as the request limit allows.

It chunks by encoded size rather than by count because Datastore publishes no per-commit mutation limit: a commit is bounded by datastore.MaxRequestBytes, and a count-based chunker would be a number this package made up. Sizing is the driver's own datastore.Client.MutationSize, which measures the mutation as it will be sent, including the key's project, database and namespace.

The returned keys are the stored ones, in the order the values were given, so an insert whose key was incomplete comes back completed at the same index.

A commit is not a transaction. Chunking means a large batch commits in pieces, and a failure leaves the earlier pieces written; the error says which commit failed but not which entities within it. Use Run when the batch has to be all-or-nothing, subject to datastore.MaxTransactionBytes.

func StoreAllOn added in v0.3.7

func StoreAllOn[T EntityEncoder](ctx context.Context, h Handle, vs []T, opts ...datastore.WriteOption) ([]datastore.Key, error)

StoreAllOn is StoreAll taking its Handle as an argument.

func StoreOn added in v0.3.7

func StoreOn[T EntityEncoder](ctx context.Context, h Handle, v T, opts ...datastore.WriteOption) (datastore.Key, error)

StoreOn is Store taking its Handle as an argument.

func TypeError

func TypeError(property, expected string, got datastore.Value) error

TypeError reports a property whose stored kind is not the one the field needs. Generated decoders call it.

An integer stored where a float is expected is a type error rather than a conversion: Datastore orders and compares integerValue and doubleValue separately, so coercing one to the other would produce a value the query that found it can no longer find.

func Update

func Update[T EntityEncoder](ctx context.Context, v T, opts ...datastore.WriteOption) error

Update writes v and fails if its key does not exist.

This is put-if-present. It replaces the whole entity: Datastore has no partial update, so every property of the stored entity comes from v.

func UpdateOn added in v0.3.7

func UpdateOn[T EntityEncoder](ctx context.Context, h Handle, v T, opts ...datastore.WriteOption) error

UpdateOn is Update taking its Handle as an argument.

func ValueError

func ValueError(property, message string, cause error) error

ValueError reports a property whose kind is right but whose value cannot be represented by the field, such as an integer too large for the Go type. Generated decoders call it.

func WithClient

func WithClient(ctx context.Context, c *datastore.Client, options ...ClientOption) context.Context

WithClient returns a child Context carrying a Datastore client and, with WithNamespace, how its tenancy is resolved. Framework middleware installs it once, and every entry of this package resolves it.

A second client is a second Context, not a second signature. That is what a test, a second database or a second project uses.

func WithHandle added in v0.3.7

func WithHandle(ctx context.Context, h Handle) context.Context

WithHandle returns a child Context carrying an already-built Handle.

It is WithClient for a caller that holds a Handle: a framework resolving one out of its own Context value, or a setup path that builds the Handle once and installs it in several Contexts.

Types

type ClientOption

type ClientOption func(*Handle)

ClientOption configures what a stored client is used with.

func WithNamespace

func WithNamespace(resolve NamespaceResolver) ClientOption

WithNamespace records how a request picks its namespace. Without it, keys are sent as built and the client's own namespace applies. A nil resolver is ignored, so a mistaken nil behaves as no resolver rather than panicking on the first call.

type EntityDecoder

type EntityDecoder interface {
	DecodeEntity(e datastore.Entity) error
}

EntityDecoder fills a value from a Datastore entity. Generated code implements it on the pointer receiver.

It fills the key fields from the entity's key as well as the properties, so a decoded value carries its own identity without a second read.

type EntityEncoder

type EntityEncoder interface {
	EncodeEntity() datastore.Entity
}

EntityEncoder converts a value into a Datastore entity. Generated code implements it on the value receiver.

The returned entity carries the key when the type declares one, and its properties never include the key fields: Datastore stores a key beside the properties, so writing them as properties too would store identity twice.

type Error

type Error struct {
	// Property is the property that failed, or "" for a whole-entity failure.
	Property string
	// Expected and Got name value kinds, such as "string" or "integer".
	Expected string
	Got      string
	Message  string
	// contains filtered or unexported fields
}

Error describes an entity mapping failure. Property names the property that failed, which is the Datastore name rather than the Go field name, because that is the name the stored data uses.

func AsError

func AsError(err error) (*Error, bool)

AsError finds a firestorebind Error in a chain without errors.As, which needs reflection. Whether reflect is linked at all is the driver's business, not this package's.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type Expirer

type Expirer interface {
	ExpiryProperty() (string, bool)
}

Expirer reports which property a TTL policy for this kind should expire entities by. Generated code implements it when the type carries a ttl tag, and only then, so the assertion succeeding is itself the declaration.

Nothing in this package or in the driver applies a TTL. Datastore mode has no expiry on the wire: a policy is applied out of band, with

gcloud firestore fields ttls update <property> --collection-group=<kind>

over an ordinary timestamp property. The tag changes nothing about how that property is written. It exists so the deployment step can be told which property to name, rather than that list being kept by hand beside the types and drifting the next time one is renamed — a drift with no compile error and no run-time error, just a policy pointed at a property that no longer exists and records that never expire.

The boolean is always true for a generated type, and is there so a caller reaching this through the interface does not have to know that.

type Handle added in v0.3.7

type Handle struct {
	// contains filtered or unexported fields
}

Handle is a Datastore client together with the tenancy of one deployment.

It is what WithClient stores in a Context, and it is what the entries suffixed On take directly. The two forms are the same value reached two ways: a Context keeps it off every call site, and a parameter keeps it out of every lookup. Which one a program uses is a call-site preference, not a behaviour difference.

The zero Handle carries no client, so an entry given one returns ErrNoClient exactly as a Context carrying none does.

Fields are unexported so a field added later is not a breaking change; build one with NewHandle.

func HandleFromContext added in v0.3.7

func HandleFromContext(ctx context.Context) (Handle, error)

HandleFromContext returns the Handle installed by WithClient or WithHandle.

It is the one lookup a caller needs: a framework reading it once in middleware has the client and the tenancy in hand, and can then call the entries suffixed On with no further Context lookup on the operation path.

func NewHandle added in v0.3.7

func NewHandle(c *datastore.Client, options ...ClientOption) Handle

NewHandle binds a client to the tenancy of one deployment, for the entries suffixed On.

It takes the same ClientOption list as WithClient, so a program moving between the two forms rewrites the call and not the configuration:

h := firestorebind.NewHandle(client, firestorebind.WithNamespace(tenantOf))
r, err := firestorebind.LoadOn[Reading](ctx, h, key)

func (Handle) Client added in v0.3.7

func (h Handle) Client() *datastore.Client

Client returns the driver client this Handle carries, or nil for the zero Handle. It is the escape hatch for reaching the driver directly, and it applies no namespace; KeyForOn is what places a key.

type KeyPage

type KeyPage struct {
	Keys      []datastore.Key
	EndCursor datastore.Cursor
	More      datastore.MoreResults

	SkippedResults int32
}

KeyPage is one batch of a keys-only query.

A keys-only query reads no properties, which is the cheap way to test existence in bulk or to collect keys for a later LoadAll. It pages like any other query, and the reason a batch ended is kept for the same reason.

func QueryKeysPage

func QueryKeysPage(ctx context.Context, q *datastore.Query, opts ...datastore.ReadOption) (KeyPage, error)

QueryKeysPage runs one keys-only query and collects the keys of its batch.

It is not generic: nothing is decoded, so there is no type to infer. The query must already be keys-only; this does not set that for the caller, because a query that silently returned keys where the caller expected entities would be the surprise this package avoids.

func QueryKeysPageOn added in v0.3.7

func QueryKeysPageOn(ctx context.Context, h Handle, q *datastore.Query, opts ...datastore.ReadOption) (KeyPage, error)

QueryKeysPageOn is QueryKeysPage taking its Handle as an argument.

func QueryKeysPageTx

func QueryKeysPageTx(ctx context.Context, tx *Tx, q *datastore.Query) (KeyPage, error)

QueryKeysPageTx runs one keys-only query inside a transaction.

As outside a transaction, the query must already be keys-only.

func (KeyPage) HasMore

func (p KeyPage) HasMore() bool

HasMore reports whether running the query again from EndCursor could return anything.

type Keyer

type Keyer interface {
	EntityKey() datastore.Key
}

Keyer reports the key of a value. Generated code implements it when the type carries a name or id tag.

type Kinder

type Kinder interface {
	Kind() string
}

Kinder reports the Datastore kind of a value. Generated code implements it for every bound type, defaulting to the Go type name.

type NamespaceResolver

type NamespaceResolver func(ctx context.Context) string

NamespaceResolver picks the namespace for one request.

A namespace is a tenancy dimension, not a property of a type, so it lives here rather than in a struct tag: putting it on the type would make one struct unusable for a second tenant. The driver's own datastore.WithNamespace already covers a namespace fixed for the process; this covers one that varies per request, which is the case that option cannot serve:

firestorebind.WithClient(ctx, client, firestorebind.WithNamespace(
	func(ctx context.Context) string { return tenantOf(ctx) }))

A generated key carries no namespace. This resolver supplies it at the runtime entry, so a key value stays portable across tenants.

It keeps its Context parameter in every form, including the entries suffixed On: a per-request tenant is read from a Context even when the client is not.

type Page

type Page[T any] struct {
	Values    []T
	EndCursor datastore.Cursor
	More      datastore.MoreResults

	// SkippedResults counts entities an offset stepped over. They were read
	// and billed.
	SkippedResults int32
}

Page is one batch of decoded entities.

More says why the batch ended and is the authority on whether another follows. It is kept rather than flattened to a bool because the reason matters: a batch that ended at a limit and one that ended because the results ran out are different facts, and only the first has a successor worth asking for.

func QueryPage

func QueryPage[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, q *datastore.Query, opts ...datastore.ReadOption) (Page[T], error)

QueryPage runs one query and decodes its batch.

This is the form that keeps the request count visible: one call is one request. Query iterates instead, at the cost of hiding how many requests that takes.

func QueryPageOn added in v0.3.7

func QueryPageOn[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, h Handle, q *datastore.Query, opts ...datastore.ReadOption) (Page[T], error)

QueryPageOn is QueryPage taking its Handle as an argument.

func QueryPageTx

func QueryPageTx[T any, PT interface {
	*T
	EntityDecoder
}](ctx context.Context, tx *Tx, q *datastore.Query) (Page[T], error)

QueryPageTx runs one query inside a transaction and decodes its batch.

func (Page[T]) HasMore

func (p Page[T]) HasMore() bool

HasMore reports whether running the query again from EndCursor could return anything.

type Tx

type Tx struct {
	// contains filtered or unexported fields
}

Tx is a typed view of one Datastore transaction.

Datastore has no condition expression over property values, so a transaction is the only way to express a read-modify-write: read inside, decide in Go, commit. That is why this package binds transactions where dynamobind does not — there, they were a convenience the driver did not offer; here they are the only conditional path beyond the insert and update verbs.

Writes queue and return nothing, matching the driver. Nothing has happened yet, and a signature that returned an error would be inventing one.

func (*Tx) Driver

func (t *Tx) Driver() *datastore.Tx

Driver returns the underlying transaction, for an operation this package does not wrap. A key passed through it has no namespace applied.

func (*Tx) Insert

func (t *Tx) Insert(v EntityEncoder, opts ...datastore.WriteOption)

Insert queues an insert of v, which fails the commit if the key exists.

func (*Tx) Remove

func (t *Tx) Remove(v Keyer, opts ...datastore.WriteOption)

Remove queues a delete of the entity identified by v's key.

func (*Tx) Store

func (t *Tx) Store(v EntityEncoder, opts ...datastore.WriteOption)

Store queues an upsert of v.

func (*Tx) Update

func (t *Tx) Update(v EntityEncoder, opts ...datastore.WriteOption)

Update queues a whole-entity update of v, which fails the commit if the key does not exist.

type Versioner

type Versioner interface {
	EntityVersion() int64
}

Versioner reports the entity version a value was read at. Generated code implements it when the type carries a version tag.

A non-zero version makes Store and Update conditional on the stored entity still being at that version, which is optimistic concurrency under its own name. A zero version sends no precondition, so a value that was never read writes unconditionally.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL