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