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