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

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: 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
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 type FinalizedQuery struct {
15 original *Query
16
17 kind string
18 eventuallyConsistent bool
19 distinct bool
20 keysOnly bool
21
22 limit *int32
23 offset *int32
24
25 start Cursor
26 end Cursor
27
28 project []string
29 orders []IndexColumn
30
31 eqFilts map[string]PropertySlice
32
33 ineqFiltProp string
34 ineqFiltLow Property
35 ineqFiltLowIncl bool
36 ineqFiltLowSet bool
37 ineqFiltHigh Property
38 ineqFiltHighIncl bool
39 ineqFiltHighSet bool
40 }
41
42 func (q *FinalizedQuery) Original() *Query {
iannucci 2015/09/18 04:31:53 yeah, I know I need docstrings on all of these.
iannucci 2015/09/18 04:31:53 yeah, I know I need docstrings on all of these.
dnj 2015/09/18 16:47:57 yeah, I know you know you need docstrings on all o
43 return q.original
44 }
45
46 func (q *FinalizedQuery) Kind() string {
47 return q.kind
48 }
49
50 func (q *FinalizedQuery) EventuallyConsistent() bool {
51 return q.eventuallyConsistent
52 }
53
54 func (q *FinalizedQuery) Project() []string {
55 ret := make([]string, len(q.project))
dnj 2015/09/18 16:47:57 Maybe an allocation-avoiding shortcut here: if len
iannucci 2015/09/18 22:25:48 done
dnj 2015/09/18 22:45:12 I think you missed this.
56 copy(ret, q.project)
57 return ret
58 }
59
60 func (q *FinalizedQuery) Distinct() bool {
61 return q.distinct
62 }
63
64 func (q *FinalizedQuery) KeysOnly() bool {
65 return q.keysOnly
66 }
67
68 func (q *FinalizedQuery) Limit() (int32, bool) {
69 if q.limit != nil {
70 return *q.limit, true
71 }
72 return 0, false
73 }
74
75 func (q *FinalizedQuery) Offset() (int32, bool) {
76 if q.offset != nil {
77 return *q.offset, true
78 }
79 return 0, false
80 }
81
82 func (q *FinalizedQuery) Orders() []IndexColumn {
83 ret := make([]IndexColumn, len(q.orders))
84 copy(ret, q.orders)
85 return ret
86 }
87
88 func (q *FinalizedQuery) Bounds() (start, end Cursor) {
89 return q.start, q.end
90 }
91
92 func (q *FinalizedQuery) Ancestor() *Key {
93 if anc, ok := q.eqFilts["__ancestor__"]; ok {
94 return anc[0].Value().(*Key)
95 }
96 return nil
97 }
98
99 func (q *FinalizedQuery) EqFilters() map[string]PropertySlice {
100 ret := make(map[string]PropertySlice, len(q.eqFilts))
101 for k, v := range q.eqFilts {
102 newV := make(PropertySlice, len(v))
103 copy(newV, v)
104 ret[k] = newV
105 }
106 return ret
107 }
108
109 func (q *FinalizedQuery) IneqFilterProp() string {
110 return q.ineqFiltProp
111 }
112
113 func (q *FinalizedQuery) IneqFilterLow() (field, op string, val Property) {
114 if q.ineqFiltLowSet {
115 field = q.ineqFiltProp
116 val = q.ineqFiltLow
117 op = ">"
dnj 2015/09/18 16:47:57 Since "op" is a return value, you can avoid the do
iannucci 2015/09/18 22:25:48 ¯\_(ツ)_/¯
118 if q.ineqFiltLowIncl {
119 op = ">="
120 }
121 }
122 return
123 }
124
125 func (q *FinalizedQuery) IneqFilterHigh() (field, op string, val Property) {
126 if q.ineqFiltHighSet {
127 field = q.ineqFiltProp
128 val = q.ineqFiltHigh
129 op = "<"
dnj 2015/09/18 16:47:57 Since "op" is a return value, you can avoid the do
130 if q.ineqFiltHighIncl {
131 op = "<="
132 }
133 }
134 return
135 }
136
137 var escaper = strings.NewReplacer(
138 "\\%", `\%`,
139 "\\_", `\_`,
140 "\\", `\\`,
141 "\x00", `\0`,
142 "\b", `\b`,
143 "\n", `\n`,
144 "\r", `\r`,
145 "\t", `\t`,
146 "\x1A", `\Z`,
147 "'", `\'`,
148 "\"", `\"`,
149 "`", "\\`",
150 )
151
152 func gqlQuoteName(s string) string {
153 return fmt.Sprintf("`%s`", escaper.Replace(s))
154 }
155
156 func gqlQuoteString(s string) string {
157 return fmt.Sprintf(`"%s"`, escaper.Replace(s))
158 }
159
160 func (q *FinalizedQuery) GQL() string {
dnj 2015/09/18 16:47:57 Make sure you mention which dialect of GQL you're
161 ret := bytes.Buffer{}
162
163 ret.WriteString("SELECT")
164 if len(q.project) != 0 {
165 if q.distinct {
166 ret.WriteString(" DISTINCT")
167 }
168 proj := make([]string, len(q.project))
169 for i, p := range q.project {
170 proj[i] = gqlQuoteName(p)
171 }
172 ret.WriteString(" ")
173 ret.WriteString(strings.Join(proj, ", "))
dnj 2015/09/18 16:47:57 nit: Since we're already leaning heavily on the Bu
iannucci 2015/09/18 22:25:48 if there are a bunch of projections, that's a lot
174 } else {
175 ret.WriteString(" *")
176 }
177
178 if q.kind != "" {
179 fmt.Fprintf(&ret, " FROM %s", gqlQuoteName(q.kind))
180 }
181
182 filts := []string(nil)
183 if len(q.eqFilts) > 0 {
184 eqProps := make([]string, 0, len(q.eqFilts))
185 for k := range q.eqFilts {
186 if k == "__ancestor__" {
dnj 2015/09/18 16:47:57 Retain this for later so you don't have to double-
iannucci 2015/09/18 22:25:48 done
187 continue
188 }
189 eqProps = append(eqProps, k)
190 }
191 sort.Strings(eqProps)
192 for _, k := range eqProps {
193 vals := q.eqFilts[k]
194 k = gqlQuoteName(k)
195 for _, v := range vals {
196 if v.Type() == PTNull {
197 filts = append(filts, fmt.Sprintf("%s IS NULL", k))
198 } else {
199 filts = append(filts, fmt.Sprintf("%s = %s", k, v.GQL()))
200 }
201 }
202 }
203 }
204 if q.ineqFiltProp != "" {
205 for _, f := range [](func() (p, op string, v Property)){q.IneqFi lterLow, q.IneqFilterHigh} {
206 prop, op, v := f()
207 if prop != "" {
208 filts = append(filts, fmt.Sprintf("%s %s %s", gq lQuoteName(prop), op, v.GQL()))
209 }
210 }
211 }
212 if anc, ok := q.eqFilts["__ancestor__"]; ok {
dnj 2015/09/18 16:47:57 Use retained value here.
213 filts = append(filts, fmt.Sprintf("__key__ HAS ANCESTOR %s", anc [0].GQL()))
214 }
215 if len(filts) > 0 {
216 fmt.Fprintf(&ret, " WHERE %s", strings.Join(filts, " AND "))
217 }
218
219 if len(q.orders) > 0 {
220 orders := make([]string, len(q.orders))
221 for i, col := range q.orders {
222 orders[i] = col.GQL()
223 }
224 fmt.Fprintf(&ret, " ORDER BY %s", strings.Join(orders, ", "))
225 }
226
227 if q.limit != nil {
228 fmt.Fprintf(&ret, " LIMIT %d", *q.limit)
229 }
230 if q.offset != nil {
231 fmt.Fprintf(&ret, " OFFSET %d", *q.offset)
232 }
233
234 return ret.String()
235 }
236
237 func (q *FinalizedQuery) String() string {
238 // TODO(riannucci): make a more compact go-like representation here.
239 return q.GQL()
240 }
241
242 func (q *FinalizedQuery) Valid(aid, ns string) error {
243 anc := q.Ancestor()
244 if anc != nil && (!anc.Valid(false, aid, ns) || anc.Incomplete()) {
245 return ErrInvalidKey
246 }
247
248 if q.ineqFiltProp == "__key__" {
249 if q.ineqFiltLowSet && !q.ineqFiltLow.Value().(*Key).Valid(false , aid, ns) {
250 return ErrInvalidKey
251 }
252 if q.ineqFiltHighSet && !q.ineqFiltHigh.Value().(*Key).Valid(fal se, aid, ns) {
253 return ErrInvalidKey
254 }
255 }
256 return nil
257 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698