-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Features + datastore scoping
The motivation for this is to enable "dispatching" datastores that dynamically implement the type of the datastore they are dispatching to, so that type assertions behave equivalently on the dispatcher as on the dispatchee. We also want this to be backwards-compatible with existing code using type assertions. At a high level, this works by generating a concrete implementation of every possible combination of "features", and then picking the right implementation at runtime. This is necessary due to language constraints in Go--it is currently impossible to create a concrete type dynamically with reflection that implements an interface. "Features" are introduced here, which are supplemental, optional interfaces that datastores may implement. These are backwards-compatible with existing "features", which are: * Batching * CheckedDatastore * GCDatastore * PersistentDatastore * ScrubbedDatastore * TTLDatastore * TxnDatastore New features can also be added in a backwards-compatible way. E.g. if datastore A is scoped down to datastore B, a new feature F is added, and then implemented on B, then A will continue to implement the same set of features since it hasn't implemented F yet (and vice versa if F is implemented on A but not B). Examples of things this enables: * Allow us to deprecate ErrBatchUnsupported * Allow existing dispatching datastores to support all features (keytransform, retrystore, MutexDatastore, autobatch, etc.) * Features supported by a Mount datastore could be scoped down to the intersection of all children Some related issues: * #160 * #88
- Loading branch information
Showing
7 changed files
with
2,623 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package datastore | ||
|
||
import ( | ||
"context" | ||
"time" | ||
) | ||
|
||
type BatchingFeature interface { | ||
Batch(ctx context.Context) (Batch, error) | ||
} | ||
|
||
type CheckedFeature interface { | ||
Check(ctx context.Context) error | ||
} | ||
|
||
type ScrubbedFeature interface { | ||
Scrub(ctx context.Context) error | ||
} | ||
|
||
type GCFeature interface { | ||
CollectGarbage(ctx context.Context) error | ||
} | ||
|
||
type PersistentFeature interface { | ||
// DiskUsage returns the space used by a datastore, in bytes. | ||
DiskUsage(ctx context.Context) (uint64, error) | ||
} | ||
|
||
// TTL encapulates the methods that deal with entries with time-to-live. | ||
type TTL interface { | ||
PutWithTTL(ctx context.Context, key Key, value []byte, ttl time.Duration) error | ||
SetTTL(ctx context.Context, key Key, ttl time.Duration) error | ||
GetExpiration(ctx context.Context, key Key) (time.Time, error) | ||
} | ||
|
||
type TxnFeature interface { | ||
NewTransaction(ctx context.Context, readOnly bool) (Txn, error) | ||
} | ||
|
||
// Feature contains metadata about a datastore feature. | ||
type Feature struct { | ||
// Interface is the nil interface of the feature. | ||
Interface interface{} | ||
// DatastoreInterface is the nil interface of the feature's corresponding datastore interface. | ||
DatastoreInterface interface{} | ||
} | ||
|
||
// Features returns a list of all datastore features. | ||
// This serves both to provide an authoritative list of features, | ||
// and to define a canonical ordering of features. | ||
func Features() []Feature { | ||
return []Feature{ | ||
{ | ||
Interface: (*BatchingFeature)(nil), | ||
DatastoreInterface: (*Batching)(nil), | ||
}, | ||
{ | ||
Interface: (*CheckedFeature)(nil), | ||
DatastoreInterface: (*CheckedDatastore)(nil), | ||
}, | ||
{ | ||
Interface: (*GCFeature)(nil), | ||
DatastoreInterface: (*GCDatastore)(nil), | ||
}, | ||
{ | ||
Interface: (*PersistentFeature)(nil), | ||
DatastoreInterface: (*PersistentDatastore)(nil), | ||
}, | ||
{ | ||
Interface: (*ScrubbedFeature)(nil), | ||
DatastoreInterface: (*ScrubbedDatastore)(nil), | ||
}, | ||
{ | ||
Interface: (*TTL)(nil), | ||
DatastoreInterface: (*TTLDatastore)(nil), | ||
}, | ||
{ | ||
Interface: (*TxnFeature)(nil), | ||
DatastoreInterface: (*TxnDatastore)(nil), | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Package scoped introduces a Datastore Shim that scopes down a source datastore | ||
// to the features supported by a target datastore. This is useful e.g. for dispatching | ||
// datastores, where the dispatcher needs to dynamically implement the same features | ||
// as the dispatchee, without knowing them statically. | ||
// | ||
// Use the Wrap function to wrap a datastore so that its interface is scoped down to | ||
// only those features supported both by it and its target datastore. Note that this | ||
// is a set intersection--if the target implements a feature not supported by the | ||
// wrapped datastore, then the resulting shim will not implement them either. | ||
// | ||
// For example: | ||
// | ||
// import ( | ||
// "context" | ||
// scopedds "github.com/ipfs/go-datastore/scoped" | ||
// ds "github.com/ipfs/go-datastore" | ||
// ) | ||
// | ||
// type BatchingDS struct { ds.Datastore } | ||
// | ||
// func (b *BatchingDS) Batch(ctx context.Context) (ds.Batch, error) { | ||
// // custom batching | ||
// return nil, nil | ||
// } | ||
// | ||
// type BoringDS struct { ds.Datastore } | ||
// | ||
// func Dispatcher(dstore ds.Datastore) ds.Datastore { | ||
// dispatcher := &BatchingDS{Datastore: dstore} | ||
// dispatchee := &BoringDS{Datastore: dstore} | ||
// | ||
// // the dispatcher supports batching, but since the dispatchee | ||
// // doesn't, the returned dispatcher does NOT implement ds.Batching | ||
// | ||
// return scoped.Wrap(dispatcher, dispatchee) | ||
// } | ||
|
||
package scoped | ||
|
||
//go:generate go run generate/main.go |
Oops, something went wrong.