get.porter.sh/porter@v1.3.0/pkg/storage/query.go (about)

     1  package storage
     2  
     3  import (
     4  	"encoding/json"
     5  	"strings"
     6  
     7  	"get.porter.sh/porter/pkg/storage/plugins"
     8  	"go.mongodb.org/mongo-driver/bson"
     9  )
    10  
    11  // AggregateOptions is the set of options available to the Aggregate operation on any
    12  // storage provider.
    13  type AggregateOptions struct {
    14  	// Pipeline document to aggregate, filter, and shape the results.
    15  	// See https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
    16  	Pipeline []bson.D
    17  }
    18  
    19  func (o AggregateOptions) ToPluginOptions(collection string) plugins.AggregateOptions {
    20  	return plugins.AggregateOptions{
    21  		Collection: collection,
    22  		Pipeline:   o.Pipeline,
    23  	}
    24  }
    25  
    26  // EnsureIndexOptions is the set of options available to the EnsureIndex operation.
    27  type EnsureIndexOptions struct {
    28  	// Indices to create if not found.
    29  	Indices []Index
    30  }
    31  
    32  // Index on a collection.
    33  type Index struct {
    34  	// Collection name to which the index applies.
    35  	Collection string
    36  
    37  	// Keys describes the fields and their sort order.
    38  	// Example: ["namespace", "name", "-timestamp"]
    39  	Keys []string
    40  
    41  	// Unique specifies if the index should enforce that the indexed fields for each document are unique.
    42  	Unique bool
    43  }
    44  
    45  // Convert from a simplified sort specifier like []{"-key"}
    46  // to a mongodb sort document like []{{Key: "key", Value: -1}}
    47  func convertSortKeys(values []string) bson.D {
    48  	if len(values) == 0 {
    49  		return nil
    50  	}
    51  
    52  	keys := make(bson.D, len(values))
    53  	for i, key := range values {
    54  		sortKey := key
    55  		sortOrder := 1
    56  		if strings.HasPrefix(key, "-") {
    57  			sortKey = strings.Trim(key, "-")
    58  			sortOrder = -1
    59  		}
    60  		keys[i] = bson.E{Key: sortKey, Value: sortOrder}
    61  	}
    62  	return keys
    63  }
    64  
    65  func (o EnsureIndexOptions) ToPluginOptions() plugins.EnsureIndexOptions {
    66  	opts := plugins.EnsureIndexOptions{
    67  		Indices: make([]plugins.Index, len(o.Indices)),
    68  	}
    69  	for i, index := range o.Indices {
    70  		opts.Indices[i] = plugins.Index{
    71  			Collection: index.Collection,
    72  			Keys:       convertSortKeys(index.Keys),
    73  			Unique:     index.Unique,
    74  		}
    75  	}
    76  	return opts
    77  }
    78  
    79  // CountOptions is the set of options available to the Count operation on any
    80  // storage provider.
    81  type CountOptions struct {
    82  	// Query is a query filter document
    83  	// See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter
    84  	Filter bson.M
    85  }
    86  
    87  func (o CountOptions) ToPluginOptions(collection string) plugins.CountOptions {
    88  	if o.Filter == nil {
    89  		o.Filter = map[string]interface{}{}
    90  	}
    91  	return plugins.CountOptions{
    92  		Collection: collection,
    93  		Filter:     o.Filter,
    94  	}
    95  }
    96  
    97  // FindOptions is the set of options available to the StorageProtocol.Find
    98  // operation.
    99  type FindOptions struct {
   100  	// Sort is a list of field names by which the results should be sorted.
   101  	// Prefix a field with "-" to sort in reverse order.
   102  	Sort []string
   103  
   104  	// Skip is the number of results to skip past and exclude from the results.
   105  	Skip int64
   106  
   107  	// Limit is the number of results to return.
   108  	Limit int64
   109  
   110  	// Filter specifies a filter the results.
   111  	// See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter
   112  	Filter bson.M
   113  
   114  	// Select is a projection document. The entire document is returned by default.
   115  	// See https://docs.mongodb.com/manual/tutorial/project-fields-from-query-results/
   116  	Select bson.D
   117  }
   118  
   119  func (o FindOptions) ToPluginOptions(collection string) plugins.FindOptions {
   120  	if o.Filter == nil {
   121  		o.Filter = bson.M{}
   122  	}
   123  	return plugins.FindOptions{
   124  		Collection: collection,
   125  		Sort:       convertSortKeys(o.Sort),
   126  		Skip:       o.Skip,
   127  		Limit:      o.Limit,
   128  		Select:     o.Select,
   129  		Filter:     o.Filter,
   130  	}
   131  }
   132  
   133  // GetOptions is the set of options available for the Get operation.
   134  // Documents can be retrieved by either ID or Namespace + Name.
   135  type GetOptions struct {
   136  	// ID of the document to retrieve.
   137  	ID string
   138  
   139  	// Name of the document to retrieve.
   140  	Name string
   141  
   142  	// Namespace of the document to retrieve.
   143  	Namespace string
   144  }
   145  
   146  // ToFindOptions converts from the convenience method Get to FindOne.
   147  func (o GetOptions) ToFindOptions() FindOptions {
   148  	var filter map[string]interface{}
   149  	if o.ID != "" {
   150  		filter = map[string]interface{}{"_id": o.ID}
   151  	} else if o.Name != "" {
   152  		filter = map[string]interface{}{"namespace": o.Namespace, "name": o.Name}
   153  	}
   154  
   155  	return FindOptions{
   156  		Filter: filter,
   157  	}
   158  }
   159  
   160  // InsertOptions is the set of options for the StorageProtocol.Insert operation.
   161  type InsertOptions struct {
   162  	// Documents is a set of documents to insert.
   163  	Documents []interface{}
   164  }
   165  
   166  func (o InsertOptions) ToPluginOptions(collection string) (plugins.InsertOptions, error) {
   167  	var docs []bson.M
   168  	err := convertToRawJsonDocument(o.Documents, &docs)
   169  	if err != nil {
   170  		return plugins.InsertOptions{}, nil
   171  	}
   172  
   173  	return plugins.InsertOptions{
   174  		Collection: collection,
   175  		Documents:  docs,
   176  	}, nil
   177  }
   178  
   179  // PatchOptions is the set of options for the StorageProtocol.Patch operation.
   180  type PatchOptions struct {
   181  	// Query is a query filter document
   182  	// See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter
   183  	QueryDocument bson.M
   184  
   185  	// Transformation is set of instructions to modify matching
   186  	// documents.
   187  	Transformation bson.D
   188  }
   189  
   190  func (o PatchOptions) ToPluginOptions(collection string) plugins.PatchOptions {
   191  	return plugins.PatchOptions{
   192  		Collection:     collection,
   193  		QueryDocument:  o.QueryDocument,
   194  		Transformation: o.Transformation,
   195  	}
   196  }
   197  
   198  // RemoveOptions is the set of options for the StorageProtocol.Remove operation.
   199  type RemoveOptions struct {
   200  	// Filter is a query filter document
   201  	// See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter
   202  	Filter bson.M
   203  
   204  	// All matching documents should be removed. Defaults to false, which only
   205  	// removes the first matching document.
   206  	All bool
   207  
   208  	// ID of the document to remove. This sets the Filter to an _id match using the specified value.
   209  	ID string
   210  
   211  	// Name of the document to remove.
   212  	Name string
   213  
   214  	// Namespace of the document to remove.
   215  	Namespace string
   216  }
   217  
   218  func (o RemoveOptions) ToPluginOptions(collection string) plugins.RemoveOptions {
   219  	// If a custom filter wasn't specified, update the specified document
   220  	if o.Filter == nil {
   221  		if o.ID != "" {
   222  			o.Filter = map[string]interface{}{"_id": o.ID}
   223  		} else if o.Name != "" {
   224  			o.Filter = map[string]interface{}{"namespace": o.Namespace, "name": o.Name}
   225  		}
   226  	}
   227  
   228  	return plugins.RemoveOptions{
   229  		Collection: collection,
   230  		Filter:     o.Filter,
   231  		All:        o.All,
   232  	}
   233  }
   234  
   235  // UpdateOptions is the set of options for the StorageProtocol.Update operation.
   236  type UpdateOptions struct {
   237  	// Filter is a query filter document. Defaults to filtering by the document id.
   238  	// See https://docs.mongodb.com/manual/core/document/#std-label-document-query-filter
   239  	Filter bson.M
   240  
   241  	// Upsert indicates that the document should be inserted if not found
   242  	Upsert bool
   243  
   244  	// Document is the replacement document.
   245  	Document interface{}
   246  }
   247  
   248  func (o UpdateOptions) ToPluginOptions(collection string) (plugins.UpdateOptions, error) {
   249  	// If a custom filter wasn't specified, update the specified document
   250  	if o.Filter == nil {
   251  		if doc, ok := o.Document.(Document); ok {
   252  			o.Filter = doc.DefaultDocumentFilter()
   253  		}
   254  	}
   255  
   256  	var doc map[string]interface{}
   257  	err := convertToRawJsonDocument(o.Document, &doc)
   258  	if err != nil {
   259  		return plugins.UpdateOptions{}, nil
   260  	}
   261  
   262  	return plugins.UpdateOptions{
   263  		Collection: collection,
   264  		Filter:     o.Filter,
   265  		Upsert:     o.Upsert,
   266  		Document:   doc,
   267  	}, nil
   268  }
   269  
   270  // Document represents a stored Porter document with
   271  // accessor methods to make persistence more straightforward.
   272  type Document interface {
   273  	// DefaultDocumentFilter is the default filter to match the current document.
   274  	DefaultDocumentFilter() map[string]interface{}
   275  }
   276  
   277  // converts a set of typed documents to a raw representation using maps
   278  // by way of the type's json representation. This ensures that any
   279  // json marshal logic is used when serializing documents to the database.
   280  // e.g. if a document has a calculated field such as _id (which is required
   281  // when persisting the document), that it is included in the doc sent to the database.
   282  func convertToRawJsonDocument(in interface{}, raw interface{}) error {
   283  	data, err := json.Marshal(in)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	return json.Unmarshal(data, raw)
   289  }
   290  
   291  // ListOptions is the set of options available to the list operation
   292  // on any storage provider.
   293  type ListOptions struct {
   294  	// Namespace in which the particular result list is defined.
   295  	Namespace string
   296  
   297  	// Name specifies whether the result list name contain the specified substring.
   298  	Name string
   299  
   300  	// Labels is used to filter result list based on a key-value pair.
   301  	Labels map[string]string
   302  
   303  	// Skip is the number of results to skip past and exclude from the results.
   304  	Skip int64
   305  
   306  	// Limit is the number of results to return.
   307  	Limit int64
   308  }
   309  
   310  // ToFindOptions builds a query for a list of documents with these conditions:
   311  // * sorted in ascending order by namespace first and then name
   312  // * filtered by matching namespace, name contains substring, and labels contain all matches
   313  // * skipped and limited to a certain number of result
   314  func (o ListOptions) ToFindOptions() FindOptions {
   315  	filter := make(map[string]interface{}, 3)
   316  	if o.Namespace != "*" {
   317  		filter["namespace"] = o.Namespace
   318  	}
   319  	if o.Name != "" {
   320  		filter["name"] = map[string]interface{}{"$regex": o.Name}
   321  	}
   322  	for k, v := range o.Labels {
   323  		filter["labels."+k] = v
   324  	}
   325  
   326  	return FindOptions{
   327  		Sort:   []string{"namespace", "name"},
   328  		Filter: filter,
   329  		Skip:   o.Skip,
   330  		Limit:  o.Limit,
   331  	}
   332  }