1// Copyright 2019 The Go Cloud Development Kit Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package mongodocstore
16
17import (
18	"context"
19	"fmt"
20	"io"
21	"strings"
22
23	"go.mongodb.org/mongo-driver/bson"
24	"go.mongodb.org/mongo-driver/mongo"
25	"go.mongodb.org/mongo-driver/mongo/options"
26	"gocloud.dev/docstore/driver"
27)
28
29func (c *collection) RunGetQuery(ctx context.Context, q *driver.Query) (driver.DocumentIterator, error) {
30	opts := options.Find()
31	if len(q.FieldPaths) > 0 {
32		opts.Projection = c.projectionDoc(q.FieldPaths)
33	}
34	if q.Limit > 0 {
35		lim := int64(q.Limit)
36		opts.Limit = &lim
37	}
38	if q.OrderByField != "" {
39		f := q.OrderByField
40		if c.opts.LowercaseFields {
41			f = strings.ToLower(f)
42		}
43		var dir int
44		if q.OrderAscending {
45			dir = 1
46		} else {
47			dir = -1
48		}
49		opts.Sort = bson.D{{Key: f, Value: dir}}
50	}
51
52	filter := bson.D{} // must be a zero-length slice, not nil
53	for _, f := range q.Filters {
54		bf, err := c.filterToBSON(f)
55		if err != nil {
56			return nil, err
57		}
58		filter = append(filter, bf)
59	}
60	if q.BeforeQuery != nil {
61		if err := q.BeforeQuery(driver.AsFunc(opts)); err != nil {
62			return nil, err
63		}
64	}
65	cursor, err := c.coll.Find(ctx, filter, opts)
66	if err != nil {
67		return nil, err
68	}
69	return &docIterator{cursor: cursor, idField: c.idField, ctx: ctx, lowercaseFields: c.opts.LowercaseFields}, nil
70}
71
72var mongoQueryOps = map[string]string{
73	driver.EqualOp: "$eq",
74	">":            "$gt",
75	">=":           "$gte",
76	"<":            "$lt",
77	"<=":           "$lte",
78}
79
80// filtersToBSON converts a []driver.Filter to the MongoDB equivalent, expressed
81// as a bson.D (list of key-value pairs).
82func (c *collection) filtersToBSON(fs []driver.Filter) (bson.D, error) {
83	filter := bson.D{} // must be a zero-length slice, not nil
84	for _, f := range fs {
85		bf, err := c.filterToBSON(f)
86		if err != nil {
87			return nil, err
88		}
89		filter = append(filter, bf)
90	}
91	return filter, nil
92}
93
94// filterToBSON converts a driver.Filter to the MongoDB equivalent, expressed
95// as a bson.E (key-value pair).
96// The MongoDB document corresponding to "field op value" is
97//   {field: {mop: value}}
98// where mop is the mongo version of op (see the mongoQueryOps map above).
99func (c *collection) filterToBSON(f driver.Filter) (bson.E, error) {
100	key := c.toMongoFieldPath(f.FieldPath)
101	if c.idField != "" && key == c.idField {
102		key = mongoIDField
103	}
104	val, err := encodeValue(f.Value)
105	if err != nil {
106		return bson.E{}, err
107	}
108	op := mongoQueryOps[f.Op]
109	if op == "" {
110		return bson.E{}, fmt.Errorf("no mongo operator for %q", f.Op)
111	}
112	return bson.E{Key: key, Value: bson.D{{Key: op, Value: val}}}, nil
113}
114
115type docIterator struct {
116	cursor          *mongo.Cursor
117	idField         string
118	ctx             context.Context // remember for Stop
119	lowercaseFields bool
120}
121
122func (it *docIterator) Next(ctx context.Context, doc driver.Document) error {
123	m, err := it.nextMap(ctx)
124	if err != nil {
125		return err
126	}
127	return decodeDoc(m, doc, it.idField, it.lowercaseFields)
128}
129
130func (it *docIterator) nextMap(ctx context.Context) (map[string]interface{}, error) {
131	if !it.cursor.Next(ctx) {
132		if it.cursor.Err() != nil {
133			return nil, it.cursor.Err()
134		}
135		return nil, io.EOF
136	}
137	var m map[string]interface{}
138	if err := it.cursor.Decode(&m); err != nil {
139		return nil, fmt.Errorf("cursor.Decode: %v", err)
140	}
141	return m, nil
142}
143
144func (it *docIterator) Stop() {
145	// Ignore error on Close.
146	_ = it.cursor.Close(it.ctx)
147}
148
149func (it *docIterator) As(i interface{}) bool {
150	p, ok := i.(**mongo.Cursor)
151	if !ok {
152		return false
153	}
154	*p = it.cursor
155	return true
156}
157
158func (c *collection) QueryPlan(q *driver.Query) (string, error) {
159	return "unknown", nil
160}
161
162func (c *collection) RunDeleteQuery(ctx context.Context, q *driver.Query) error {
163	filter, err := c.filtersToBSON(q.Filters)
164	if err != nil {
165		return err
166	}
167	if q.BeforeQuery != nil {
168		if err := q.BeforeQuery(driver.AsFunc(filter)); err != nil {
169			return err
170		}
171	}
172	_, err = c.coll.DeleteMany(ctx, filter)
173	return err
174}
175
176func (c *collection) RunUpdateQuery(ctx context.Context, q *driver.Query, mods []driver.Mod) error {
177	filter, err := c.filtersToBSON(q.Filters)
178	if err != nil {
179		return err
180	}
181	// TODO(#2458): provide an option for the user to choose whether or not to
182	// update the revision.
183	updateDoc, _, err := c.newUpdateDoc(mods, !c.opts.NoWriteQueryUpdateRevisions)
184	if err != nil {
185		return err
186	}
187	if q.BeforeQuery != nil {
188		if err := q.BeforeQuery(driver.AsFunc(filter)); err != nil {
189			return err
190		}
191	}
192	_, err = c.coll.UpdateMany(ctx, filter, updateDoc)
193	return err
194}
195