OLD | NEW |
---|---|
(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 } | |
OLD | NEW |