Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(284)

Side by Side Diff: service/datastore/finalized_query.go

Issue 1355783002: Refactor keys and queries in datastore service and implementation. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: appease errcheck Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « service/datastore/dskey/key_test.go ('k') | service/datastore/index.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package datastore
6
7 import (
8 "bytes"
9 "fmt"
10 "sort"
11 "strings"
12 )
13
14 // FinalizedQuery is the representation of a Query which has been normalized.
15 //
16 // It contains only fully-specified, non-redundant, non-conflicting information
17 // pertaining to the Query to run. It can only represent a valid query.
18 type FinalizedQuery struct {
19 original *Query
20
21 kind string
22 eventuallyConsistent bool
23 distinct bool
24 keysOnly bool
25
26 limit *int32
27 offset *int32
28
29 start Cursor
30 end Cursor
31
32 project []string
33 orders []IndexColumn
34
35 eqFilts map[string]PropertySlice
36
37 ineqFiltProp string
38 ineqFiltLow Property
39 ineqFiltLowIncl bool
40 ineqFiltLowSet bool
41 ineqFiltHigh Property
42 ineqFiltHighIncl bool
43 ineqFiltHighSet bool
44 }
45
46 // Original returns the original Query object from which this FinalizedQuery was
47 // derived.
48 func (q *FinalizedQuery) Original() *Query {
49 return q.original
50 }
51
52 // Kind returns the datastore 'Kind' over which this query operates. It may be
53 // empty for a kindless query.
54 func (q *FinalizedQuery) Kind() string {
55 return q.kind
56 }
57
58 // EventuallyConsistent returns true iff this query will be eventually
59 // consistent. This is true when the query is a non-ancestor query, or when it's
60 // an ancestory query with the 'EventualConsistency(true)' option set.
61 func (q *FinalizedQuery) EventuallyConsistent() bool {
62 return q.eventuallyConsistent
63 }
64
65 // Project is the list of fields that this query projects on, or empty if this
66 // is not a projection query.
67 func (q *FinalizedQuery) Project() []string {
68 if len(q.project) == 0 {
69 return nil
70 }
71 ret := make([]string, len(q.project))
72 copy(ret, q.project)
73 return ret
74 }
75
76 // Distinct returnst true iff this is a distinct projection query. It will never
77 // be true for non-projection queries.
78 func (q *FinalizedQuery) Distinct() bool {
79 return q.distinct
80 }
81
82 // KeysOnly returns true iff this query will only return keys (as opposed to a
83 // normal or projection query).
84 func (q *FinalizedQuery) KeysOnly() bool {
85 return q.keysOnly
86 }
87
88 // Limit returns the maximum number of responses this query will retrieve, and a
89 // boolean indicating if the limit is set.
90 func (q *FinalizedQuery) Limit() (int32, bool) {
91 if q.limit != nil {
92 return *q.limit, true
93 }
94 return 0, false
95 }
96
97 // Offset returns the number of responses this query will skip before returning
98 // data, and a boolean indicating if the offset is set.
99 func (q *FinalizedQuery) Offset() (int32, bool) {
100 if q.offset != nil {
101 return *q.offset, true
102 }
103 return 0, false
104 }
105
106 // Orders returns the sort orders that this query will use, including all orders
107 // implied by the projections, and the implicit __key__ order at the end.
108 func (q *FinalizedQuery) Orders() []IndexColumn {
109 ret := make([]IndexColumn, len(q.orders))
110 copy(ret, q.orders)
111 return ret
112 }
113
114 // Bounds returns the start and end Cursors. One or both may be nil. The Cursors
115 // returned are implementation-specific depending on the actual RawInterface
116 // implementation and the filters installed (if the filters interfere with
117 // Cursor production).
118 func (q *FinalizedQuery) Bounds() (start, end Cursor) {
119 return q.start, q.end
120 }
121
122 // Ancestor returns the ancestor filter key, if any. This is a convenience
123 // function for getting the value from EqFilters()["__ancestor__"].
124 func (q *FinalizedQuery) Ancestor() *Key {
125 if anc, ok := q.eqFilts["__ancestor__"]; ok {
126 return anc[0].Value().(*Key)
127 }
128 return nil
129 }
130
131 // EqFilters returns all the equality filters. The map key is the field name
132 // and the PropertySlice is the values that field should equal.
133 //
134 // This includes a special equality filter on "__ancestor__". If "__ancestor__"
135 // is present in the result, it's guaranteed to have 1 value in the
136 // PropertySlice which is of type *Key.
137 func (q *FinalizedQuery) EqFilters() map[string]PropertySlice {
138 ret := make(map[string]PropertySlice, len(q.eqFilts))
139 for k, v := range q.eqFilts {
140 newV := make(PropertySlice, len(v))
141 copy(newV, v)
142 ret[k] = newV
143 }
144 return ret
145 }
146
147 // IneqFilterProp returns the inequality filter property name, if one is used
148 // for this filter. An empty return value means that this query does not
149 // contain any inequality filters.
150 func (q *FinalizedQuery) IneqFilterProp() string {
151 return q.ineqFiltProp
152 }
153
154 // IneqFilterLow returns the field name, operator and value for the low-side
155 // inequality filter. If the returned field name is "", it means that there's
156 // now lower inequality bound on this query.
157 //
158 // If field is non-empty, op may have the values ">" or ">=".
159 func (q *FinalizedQuery) IneqFilterLow() (field, op string, val Property) {
160 if q.ineqFiltLowSet {
161 field = q.ineqFiltProp
162 val = q.ineqFiltLow
163 op = ">"
164 if q.ineqFiltLowIncl {
165 op = ">="
166 }
167 }
168 return
169 }
170
171 // IneqFilterHigh returns the field name, operator and value for the high-side
172 // inequality filter. If the returned field name is "", it means that there's
173 // now upper inequality bound on this query.
174 //
175 // If field is non-empty, op may have the values "<" or "<=".
176 func (q *FinalizedQuery) IneqFilterHigh() (field, op string, val Property) {
177 if q.ineqFiltHighSet {
178 field = q.ineqFiltProp
179 val = q.ineqFiltHigh
180 op = "<"
181 if q.ineqFiltHighIncl {
182 op = "<="
183 }
184 }
185 return
186 }
187
188 var escaper = strings.NewReplacer(
189 "\\%", `\%`,
190 "\\_", `\_`,
191 "\\", `\\`,
192 "\x00", `\0`,
193 "\b", `\b`,
194 "\n", `\n`,
195 "\r", `\r`,
196 "\t", `\t`,
197 "\x1A", `\Z`,
198 "'", `\'`,
199 "\"", `\"`,
200 "`", "\\`",
201 )
202
203 func gqlQuoteName(s string) string {
204 return fmt.Sprintf("`%s`", escaper.Replace(s))
205 }
206
207 func gqlQuoteString(s string) string {
208 return fmt.Sprintf(`"%s"`, escaper.Replace(s))
209 }
210
211 // GQL returns a correctly formatted Cloud Datastore GQL expression which
212 // is equivalent to this query.
213 //
214 // The flavor of GQL that this emits is defined here:
215 // https://cloud.google.com/datastore/docs/apis/gql/gql_reference
216 //
217 // NOTE: Cursors are omitted because currently there's currently no syntax for
218 // literal cursors.
219 //
220 // NOTE: GeoPoint values are emitted with speculated future syntax. There is
221 // currently no syntax for literal GeoPoint values.
222 func (q *FinalizedQuery) GQL() string {
223 ret := bytes.Buffer{}
224
225 ws := func(s string) {
226 _, err := ret.WriteString(s)
227 if err != nil {
228 panic(err)
229 }
230 }
231
232 ws("SELECT")
233 if len(q.project) != 0 {
234 if q.distinct {
235 ws(" DISTINCT")
236 }
237 proj := make([]string, len(q.project))
238 for i, p := range q.project {
239 proj[i] = gqlQuoteName(p)
240 }
241 ws(" ")
242 ws(strings.Join(proj, ", "))
243 } else {
244 ws(" *")
245 }
246
247 if q.kind != "" {
248 fmt.Fprintf(&ret, " FROM %s", gqlQuoteName(q.kind))
249 }
250
251 filts := []string(nil)
252 anc := Property{}
253 if len(q.eqFilts) > 0 {
254 eqProps := make([]string, 0, len(q.eqFilts))
255 for k, v := range q.eqFilts {
256 if k == "__ancestor__" {
257 anc = v[0]
258 continue
259 }
260 eqProps = append(eqProps, k)
261 }
262 sort.Strings(eqProps)
263 for _, k := range eqProps {
264 vals := q.eqFilts[k]
265 k = gqlQuoteName(k)
266 for _, v := range vals {
267 if v.Type() == PTNull {
268 filts = append(filts, fmt.Sprintf("%s IS NULL", k))
269 } else {
270 filts = append(filts, fmt.Sprintf("%s = %s", k, v.GQL()))
271 }
272 }
273 }
274 }
275 if q.ineqFiltProp != "" {
276 for _, f := range [](func() (p, op string, v Property)){q.IneqFi lterLow, q.IneqFilterHigh} {
277 prop, op, v := f()
278 if prop != "" {
279 filts = append(filts, fmt.Sprintf("%s %s %s", gq lQuoteName(prop), op, v.GQL()))
280 }
281 }
282 }
283 if anc.propType != PTNull {
284 filts = append(filts, fmt.Sprintf("__key__ HAS ANCESTOR %s", anc .GQL()))
285 }
286 if len(filts) > 0 {
287 fmt.Fprintf(&ret, " WHERE %s", strings.Join(filts, " AND "))
288 }
289
290 if len(q.orders) > 0 {
291 orders := make([]string, len(q.orders))
292 for i, col := range q.orders {
293 orders[i] = col.GQL()
294 }
295 fmt.Fprintf(&ret, " ORDER BY %s", strings.Join(orders, ", "))
296 }
297
298 if q.limit != nil {
299 fmt.Fprintf(&ret, " LIMIT %d", *q.limit)
300 }
301 if q.offset != nil {
302 fmt.Fprintf(&ret, " OFFSET %d", *q.offset)
303 }
304
305 return ret.String()
306 }
307
308 func (q *FinalizedQuery) String() string {
309 // TODO(riannucci): make a more compact go-like representation here.
310 return q.GQL()
311 }
312
313 // Valid returns true iff this FinalizedQuery is valid in the provided appID and
314 // namespace.
315 //
316 // This checks the ancestor filter (if any), as well as the inequality filters
317 // if they filter on '__key__'.
318 //
319 // In particular, it does NOT validate equality filters which happen to have
320 // values of type PTKey, nor does it validate inequality filters that happen to
321 // have values of type PTKey (but don't filter on the magic '__key__' field).
322 func (q *FinalizedQuery) Valid(aid, ns string) error {
323 anc := q.Ancestor()
324 if anc != nil && (!anc.Valid(false, aid, ns) || anc.Incomplete()) {
325 return ErrInvalidKey
326 }
327
328 if q.ineqFiltProp == "__key__" {
329 if q.ineqFiltLowSet && !q.ineqFiltLow.Value().(*Key).Valid(false , aid, ns) {
330 return ErrInvalidKey
331 }
332 if q.ineqFiltHighSet && !q.ineqFiltHigh.Value().(*Key).Valid(fal se, aid, ns) {
333 return ErrInvalidKey
334 }
335 }
336 return nil
337 }
OLDNEW
« no previous file with comments | « service/datastore/dskey/key_test.go ('k') | service/datastore/index.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698