github.com/thiagoyeds/go-cloud@v0.26.0/docstore/query.go (about) 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 15 package docstore 16 17 import ( 18 "context" 19 "io" 20 "reflect" 21 "time" 22 23 "gocloud.dev/docstore/driver" 24 "gocloud.dev/internal/gcerr" 25 ) 26 27 // Query represents a query over a collection. 28 type Query struct { 29 coll *Collection 30 dq *driver.Query 31 err error 32 } 33 34 // Query creates a new Query over the collection. 35 func (c *Collection) Query() *Query { 36 return &Query{coll: c, dq: &driver.Query{}} 37 } 38 39 // Where expresses a condition on the query. 40 // Valid ops are: "=", ">", "<", ">=", "<=". 41 // Valid values are strings, integers, floating-point numbers, and time.Time values. 42 func (q *Query) Where(fp FieldPath, op string, value interface{}) *Query { 43 if q.err != nil { 44 return q 45 } 46 pfp, err := parseFieldPath(fp) 47 if err != nil { 48 q.err = err 49 return q 50 } 51 if !validOp[op] { 52 return q.invalidf("invalid filter operator: %q. Use one of: =, >, <, >=, <=", op) 53 } 54 if !validFilterValue(value) { 55 return q.invalidf("invalid filter value: %v", value) 56 } 57 q.dq.Filters = append(q.dq.Filters, driver.Filter{ 58 FieldPath: pfp, 59 Op: op, 60 Value: value, 61 }) 62 return q 63 } 64 65 var validOp = map[string]bool{ 66 "=": true, 67 ">": true, 68 "<": true, 69 ">=": true, 70 "<=": true, 71 } 72 73 func validFilterValue(v interface{}) bool { 74 if v == nil { 75 return false 76 } 77 if _, ok := v.(time.Time); ok { 78 return true 79 } 80 switch reflect.TypeOf(v).Kind() { 81 case reflect.String: 82 return true 83 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 84 return true 85 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 86 return true 87 case reflect.Float32, reflect.Float64: 88 return true 89 default: 90 return false 91 } 92 } 93 94 // Limit will limit the results to at most n documents. 95 // n must be positive. 96 // It is an error to specify Limit more than once in a Get query, or 97 // at all in a Delete or Update query. 98 func (q *Query) Limit(n int) *Query { 99 if q.err != nil { 100 return q 101 } 102 if n <= 0 { 103 return q.invalidf("limit value of %d must be greater than zero", n) 104 } 105 if q.dq.Limit > 0 { 106 return q.invalidf("query can have at most one limit clause") 107 } 108 q.dq.Limit = n 109 return q 110 } 111 112 // Ascending and Descending are constants for use in the OrderBy method. 113 const ( 114 Ascending = "asc" 115 Descending = "desc" 116 ) 117 118 // OrderBy specifies that the returned documents appear sorted by the given field in 119 // the given direction. 120 // A query can have at most one OrderBy clause. If it has none, the order of returned 121 // documents is unspecified. 122 // If a query has a Where clause and an OrderBy clause, the OrderBy clause's field 123 // must appear in a Where clause. 124 // It is an error to specify OrderBy in a Delete or Update query. 125 func (q *Query) OrderBy(field, direction string) *Query { 126 if q.err != nil { 127 return q 128 } 129 if field == "" { 130 return q.invalidf("OrderBy: empty field") 131 } 132 if direction != Ascending && direction != Descending { 133 return q.invalidf("OrderBy: direction must be one of %q or %q", Ascending, Descending) 134 } 135 if q.dq.OrderByField != "" { 136 return q.invalidf("a query can have at most one OrderBy") 137 } 138 q.dq.OrderByField = field 139 q.dq.OrderAscending = (direction == Ascending) 140 return q 141 } 142 143 // BeforeQuery takes a callback function that will be called before the Query is 144 // executed to the underlying service's query functionality. The callback takes 145 // a parameter, asFunc, that converts its argument to driver-specific types. 146 // See https://gocloud.dev/concepts/as/ for background information. 147 func (q *Query) BeforeQuery(f func(asFunc func(interface{}) bool) error) *Query { 148 q.dq.BeforeQuery = f 149 return q 150 } 151 152 // Get returns an iterator for retrieving the documents specified by the query. If 153 // field paths are provided, only those paths are set in the resulting documents. 154 // 155 // Call Stop on the iterator when finished. 156 func (q *Query) Get(ctx context.Context, fps ...FieldPath) *DocumentIterator { 157 return q.get(ctx, true, fps...) 158 } 159 160 // get implements Get, with optional OpenCensus tracing so it can be used internally. 161 func (q *Query) get(ctx context.Context, oc bool, fps ...FieldPath) *DocumentIterator { 162 dcoll := q.coll.driver 163 if err := q.initGet(fps); err != nil { 164 return &DocumentIterator{err: wrapError(dcoll, err)} 165 } 166 167 var err error 168 if oc { 169 ctx = q.coll.tracer.Start(ctx, "Query.Get") 170 defer func() { q.coll.tracer.End(ctx, err) }() 171 } 172 it, err := dcoll.RunGetQuery(ctx, q.dq) 173 return &DocumentIterator{iter: it, coll: q.coll, err: wrapError(dcoll, err)} 174 } 175 176 func (q *Query) initGet(fps []FieldPath) error { 177 if q.err != nil { 178 return q.err 179 } 180 if err := q.coll.checkClosed(); err != nil { 181 return errClosed 182 } 183 pfps, err := parseFieldPaths(fps) 184 if err != nil { 185 return err 186 } 187 q.dq.FieldPaths = pfps 188 if q.dq.OrderByField != "" && len(q.dq.Filters) > 0 { 189 found := false 190 for _, f := range q.dq.Filters { 191 if len(f.FieldPath) == 1 && f.FieldPath[0] == q.dq.OrderByField { 192 found = true 193 break 194 } 195 } 196 if !found { 197 return gcerr.Newf(gcerr.InvalidArgument, nil, "OrderBy field %s must appear in a Where clause", 198 q.dq.OrderByField) 199 } 200 } 201 return nil 202 } 203 204 func (q *Query) invalidf(format string, args ...interface{}) *Query { 205 q.err = gcerr.Newf(gcerr.InvalidArgument, nil, format, args...) 206 return q 207 } 208 209 // DocumentIterator iterates over documents. 210 // 211 // Always call Stop on the iterator. 212 type DocumentIterator struct { 213 iter driver.DocumentIterator 214 coll *Collection 215 err error // already wrapped 216 } 217 218 // Next stores the next document in dst. It returns io.EOF if there are no more 219 // documents. 220 // Once Next returns an error, it will always return the same error. 221 func (it *DocumentIterator) Next(ctx context.Context, dst Document) error { 222 if it.err != nil { 223 return it.err 224 } 225 if err := it.coll.checkClosed(); err != nil { 226 it.err = err 227 return it.err 228 } 229 ddoc, err := driver.NewDocument(dst) 230 if err != nil { 231 it.err = wrapError(it.coll.driver, err) 232 return it.err 233 } 234 it.err = wrapError(it.coll.driver, it.iter.Next(ctx, ddoc)) 235 return it.err 236 } 237 238 // Stop stops the iterator. Calling Next on a stopped iterator will return io.EOF, or 239 // the error that Next previously returned. 240 func (it *DocumentIterator) Stop() { 241 if it.err != nil { 242 return 243 } 244 it.err = io.EOF 245 it.iter.Stop() 246 } 247 248 // As converts i to driver-specific types. 249 // See https://gocloud.dev/concepts/as/ for background information, the "As" 250 // examples in this package for examples, and the driver package 251 // documentation for the specific types supported for that driver. 252 func (it *DocumentIterator) As(i interface{}) bool { 253 if i == nil || it.iter == nil { 254 return false 255 } 256 return it.iter.As(i) 257 } 258 259 // Plan describes how the query would be executed if its Get method were called with 260 // the given field paths. Plan uses only information available to the client, so it 261 // cannot know whether a service uses indexes or scans internally. 262 func (q *Query) Plan(fps ...FieldPath) (string, error) { 263 if err := q.initGet(fps); err != nil { 264 return "", err 265 } 266 return q.coll.driver.QueryPlan(q.dq) 267 }