github.com/trustbloc/kms-go@v1.1.2/spi/storage/storage.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package storage
     8  
     9  import (
    10  	"errors"
    11  )
    12  
    13  // MultiError represents the errors that occurred during a bulk operation.
    14  type MultiError interface {
    15  	error
    16  	Errors() []error // Errors returns the error objects for all operations.
    17  }
    18  
    19  var (
    20  	// ErrStoreNotFound is returned when a store is not found.
    21  	ErrStoreNotFound = errors.New("store not found")
    22  	// ErrDataNotFound is returned when data is not found.
    23  	ErrDataNotFound = errors.New("data not found")
    24  	// ErrDuplicateKey is returned when a call is made to Store.Batch using the IsNewKey PutOption with a key that
    25  	// already exists in the database.
    26  	ErrDuplicateKey = errors.New("duplicate key")
    27  )
    28  
    29  // StoreConfiguration represents the configuration of a store.
    30  // Currently, it's only used for creating indexes in underlying storage databases.
    31  type StoreConfiguration struct {
    32  	// TagNames is a list of Tag names to create indexes on.
    33  	// Tag names cannot contain any ':' characters.
    34  	TagNames []string `json:"tagNames,omitempty"`
    35  }
    36  
    37  // SortOrder specifies the sort order of query results.
    38  type SortOrder int
    39  
    40  const (
    41  	// SortAscending indicates that the query results must be sorted in ascending order.
    42  	SortAscending SortOrder = iota
    43  	// SortDescending indicates that the query results must be sorted in descending order.
    44  	SortDescending
    45  )
    46  
    47  // SortOptions sets the order that results from an Iterator will be returned in. Sorting is based on the tag values
    48  // associated with the TagName chosen below. The TagName you use below can be the same as the one you're querying on,
    49  // or it can be a different one. Depending on the storage implementation, you may need to ensure that the TagName set
    50  // below is in the Store's StoreConfiguration before trying to use it for sorting (or it may be optional,
    51  // but recommended). If tag value strings are decimal numbers, then the sorting must be based on their numerical value
    52  // instead of the string representations of those numbers (i.e. numerical sorting, not lexicographic).
    53  // TagName cannot be blank.
    54  type SortOptions struct {
    55  	Order   SortOrder
    56  	TagName string
    57  }
    58  
    59  // QueryOptions represents various options for Query calls in a store.
    60  type QueryOptions struct {
    61  	// PageSize sets the page size used by the Store.Query method.
    62  	PageSize int
    63  	// InitialPageNum sets the page for the iterator returned from Store.Query to start from.
    64  	// InitialPageNum=0 means start from the first page.
    65  	InitialPageNum int
    66  	// SortOptions defines the sort order.
    67  	SortOptions *SortOptions
    68  }
    69  
    70  // QueryOption represents an option for a Store.Query call.
    71  type QueryOption func(opts *QueryOptions)
    72  
    73  // WithPageSize sets the maximum page size for data retrievals done within the Iterator returned by the Query call.
    74  // Paging is handled internally by the Iterator. Higher values may reduce CPU time and the number of database calls at
    75  // the expense of higher memory usage.
    76  func WithPageSize(size int) QueryOption {
    77  	return func(opts *QueryOptions) {
    78  		opts.PageSize = size
    79  	}
    80  }
    81  
    82  // WithInitialPageNum sets the page number for an Iterator to start from. If this option isn't used,
    83  // then the Iterator will start from the first page.
    84  // Page number counting starts from 0 (i.e. initialPageNum=0 means that the iterator will start from the first page).
    85  func WithInitialPageNum(initialPageNum int) QueryOption {
    86  	return func(opts *QueryOptions) {
    87  		opts.InitialPageNum = initialPageNum
    88  	}
    89  }
    90  
    91  // WithSortOrder sets the sort order used by a Store.Query call. See SortOptions for more information.
    92  // If this option isn't used, then the result order from the Iterator will be determined (perhaps arbitrarily) by the
    93  // underlying database implementation.
    94  // As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
    95  func WithSortOrder(sortOptions *SortOptions) QueryOption {
    96  	return func(opts *QueryOptions) {
    97  		opts.SortOptions = sortOptions
    98  	}
    99  }
   100  
   101  // Tag represents a Name + Value pair that can be associated with a key + value pair for querying later.
   102  type Tag struct {
   103  	// Name can be used to tag a given key + value pair as belonging to some sort of common
   104  	// group. Example: Identifying a key+value pair as being a Verifiable Credential by storing it
   105  	// with a tag Name called "VC". When used with the optional Value (see below), tag Name + Value can be used to
   106  	// specify metadata for a given key + value pair. Example: Identifying a Verifiable Credential (stored as a
   107  	// key+value pair) as belonging to a user account by using a tag Name called "UserAccount" and a tag Value called
   108  	// "bob@example.com". Tag Names are intended to be static values that the store is configured with in order to build
   109  	// indexes for queries (see TagNames in StoreConfiguration).
   110  	// Tag Names cannot contain any ':' characters.
   111  	Name string `json:"name,omitempty"`
   112  	// Value can optionally be used to indicate some metadata associated with a tag name for a given key + value pair.
   113  	// See Name above for an example of how this can be used.
   114  	// Tag Values are dynamic and are not specified in a StoreConfiguration.
   115  	// Tag Values cannot contain any ':' characters.
   116  	Value string `json:"value,omitempty"`
   117  }
   118  
   119  // PutOptions represents options for a Put Operation.
   120  type PutOptions struct {
   121  	// This is an optimization for a Put Operation. Some storage providers may be able to store data faster if they
   122  	// know beforehand that this key does not currently exist in the database. Unexpected behaviour may occur if
   123  	// this is set to true and the key already exists. See the documentation for the specific storage provider to
   124  	// see if and how this option is used.
   125  	IsNewKey bool `json:"isNewKey,omitempty"`
   126  }
   127  
   128  // Operation represents an operation to be performed in the Batch method.
   129  type Operation struct {
   130  	Key        string      `json:"key,omitempty"`
   131  	Value      []byte      `json:"value,omitempty"`      // A nil value will result in a delete operation.
   132  	Tags       []Tag       `json:"tags,omitempty"`       // Optional.
   133  	PutOptions *PutOptions `json:"putOptions,omitempty"` // Optional. Only used for Put Operations.
   134  }
   135  
   136  // Provider represents a storage provider.
   137  type Provider interface {
   138  	// OpenStore opens a Store with the given name and returns it.
   139  	// Depending on the store implementation, this may or may not create an underlying database.
   140  	// The store implementation may defer creating the underlying database until SetStoreConfig is called or
   141  	// data is inserted using Store.Put or Store.Batch.
   142  	// Store names are not case-sensitive. If name is blank, then an error will be returned.
   143  	OpenStore(name string) (Store, error)
   144  
   145  	// SetStoreConfig sets the configuration on a Store. It's recommended calling this method at some point before
   146  	// calling Store.Query if your store contains a large amount of data. The underlying database will use this to
   147  	// create indexes to make querying via the Store.Query method faster. If you don't need to use Store.Query, then
   148  	// you don't need to call this method. OpenStore must be called first before calling this method. If not, then an
   149  	// error wrapping ErrStoreNotFound will be returned. This method will not open the store automatically.
   150  	// If name is blank, then an error will be returned.
   151  	SetStoreConfig(name string, config StoreConfiguration) error
   152  
   153  	// GetStoreConfig gets the current Store configuration.
   154  	// This method operates a bit differently in that it directly checks the underlying storage implementation to see
   155  	// if the underlying database exists for the given name, rather than checking the currently known list of
   156  	// open stores in memory. If no underlying database can be found, then an error wrapping ErrStoreNotFound will be
   157  	// returned. This means that this method can be used to determine whether an underlying database for a Store
   158  	// already exists or not. This method will not create the database automatically.
   159  	// If name is blank, then an error will be returned.
   160  	// As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
   161  	GetStoreConfig(name string) (StoreConfiguration, error)
   162  
   163  	// GetOpenStores returns all Stores that are currently open in memory from calling OpenStore.
   164  	// It does not check for all databases that have been created before. They have to have been opened in this Provider
   165  	// object's lifetime from a call to OpenStore.
   166  	// As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
   167  	GetOpenStores() []Store
   168  
   169  	// Close closes all open Stores in this Provider
   170  	// For persistent Store implementations, this does not delete any data in the underlying databases.
   171  	Close() error
   172  }
   173  
   174  // Store represents a storage database.
   175  type Store interface {
   176  	// Put stores the key + value pair along with the (optional) tags. If the key already exists in the database,
   177  	// then the value and tags will be overwritten silently.
   178  	// If value is a JSON-formatted object, then an underlying storage implementation may store it in a way that
   179  	// does not preserve the order of the fields. Therefore, you should avoid doing direct byte-for-byte comparisons
   180  	// with data put in and data retrieved, as the marshalled representation may be different - always unmarshal data
   181  	// first before comparing.
   182  	// If key is empty or value is nil, then an error will be returned.
   183  	// A single key-value pair cannot have multiple tags that share the same tag name.
   184  	Put(key string, value []byte, tags ...Tag) error
   185  
   186  	// Get fetches the value associated with the given key.
   187  	// If key cannot be found, then an error wrapping ErrDataNotFound will be returned.
   188  	// If key is empty, then an error will be returned.
   189  	Get(key string) ([]byte, error)
   190  
   191  	// GetTags fetches all tags associated with the given key.
   192  	// If key cannot be found, then an error wrapping ErrDataNotFound will be returned.
   193  	// If key is empty, then an error will be returned.
   194  	// As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
   195  	GetTags(key string) ([]Tag, error)
   196  
   197  	// GetBulk fetches the values associated with the given keys.
   198  	// If no data exists under a given key, then a nil []byte is returned for that value. It is not considered an error.
   199  	// Depending on the implementation, this method may be faster than calling Get for each key individually.
   200  	// If any of the given keys are empty, then an error will be returned.
   201  	// As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
   202  	GetBulk(keys ...string) ([][]byte, error)
   203  
   204  	// Query returns all data that satisfies the expression. Basic expression format: TagName:TagValue.
   205  	// If TagValue is not provided, then all data associated with the TagName will be returned, regardless of their
   206  	// tag values.
   207  	// At a minimum, a store implementation must be able to support querying with a single basic expression, but a
   208  	// store implementation may also support a more advanced expression format.
   209  	// Advanced expression format: [Criterion1][Operator][Criterion2][Operator]...[CriterionN]. Square brackets are
   210  	// used here for visual clarity. Omit them from the actual expression string.
   211  	// Each Criterion follows the rules for the basic expression format described above.
   212  	// Each operator must be either "&&" or "||" (without quotes). "&&" indicates an AND operator while "||"
   213  	// indicates an OR operator. The order of operations are ANDs followed by ORs.
   214  	// This method also supports a number of QueryOptions. If none are provided, then defaults will be used.
   215  	// If your store contains a large amount of data, then it's recommended calling Provider.SetStoreConfig at some
   216  	// point before calling this method in order to create indexes which will speed up queries.
   217  	Query(expression string, options ...QueryOption) (Iterator, error)
   218  
   219  	// Delete deletes the key + value pair (and all tags) associated with key.
   220  	// If key is empty, then an error will be returned.
   221  	Delete(key string) error
   222  
   223  	// Batch performs multiple Put and/or Delete operations in order. The Puts and Deletes here follow the same rules
   224  	// as described in the Put and Delete method documentation. The only exception is if the operation makes use of
   225  	// the PutOptions.IsNewKey optimization, in which case an error wrapping an ErrDuplicateKey may be returned if it's
   226  	// enabled and a key is used that already exists in the database.
   227  	// Depending on the implementation, this method may be faster than repeated Put and/or Delete calls.
   228  	// If any of the given keys are empty, or the operations slice is empty or nil, then an error will be returned.
   229  	// As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
   230  	Batch(operations []Operation) error
   231  
   232  	// Flush forces any queued up Put and/or Delete operations to execute.
   233  	// If the Store implementation doesn't queue up operations, then this method is a no-op.
   234  	// As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
   235  	Flush() error
   236  
   237  	// Close closes this store object, freeing resources. For persistent store implementations, this does not delete
   238  	// any data in the underlying databases.
   239  	// Close can be called repeatedly on the same store multiple times without causing an error.
   240  	Close() error
   241  }
   242  
   243  // Iterator allows for iteration over a collection of entries in a store.
   244  type Iterator interface {
   245  	// Next moves the pointer to the next entry in the iterator.
   246  	// Note that it must be called before accessing the first entry.
   247  	// It returns false if the iterator is exhausted - this is not considered an error.
   248  	Next() (bool, error)
   249  
   250  	// Key returns the key of the current entry.
   251  	Key() (string, error)
   252  
   253  	// Value returns the value of the current entry.
   254  	Value() ([]byte, error)
   255  
   256  	// Tags returns the tags associated with the key of the current entry.
   257  	// As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
   258  	Tags() ([]Tag, error)
   259  
   260  	// TotalItems returns a count of the number of entries (key + value + tags triplets) matched by the query
   261  	// that generated this Iterator. This count is not affected by the page settings used (i.e. the count is of all
   262  	// results as if you queried starting from the first page and with an unlimited page size).
   263  	// Depending on the storage implementation, you may need to ensure that the TagName used in the query is in the
   264  	// Store's StoreConfiguration before trying to call this method (or it may be optional, but recommended).
   265  	// As of writing, aries-framework-go code does not use this, but it may be useful for custom solutions.
   266  	TotalItems() (int, error)
   267  
   268  	// Close closes this iterator object, freeing resources.
   269  	Close() error
   270  }