| 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 { |
| 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)) |
| 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 = ">" |
| 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 = "<" |
| 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 { |
| 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, ", ")) |
| 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 anc := Property{} |
| 184 if len(q.eqFilts) > 0 { |
| 185 eqProps := make([]string, 0, len(q.eqFilts)) |
| 186 for k, v := range q.eqFilts { |
| 187 if k == "__ancestor__" { |
| 188 anc = v[0] |
| 189 continue |
| 190 } |
| 191 eqProps = append(eqProps, k) |
| 192 } |
| 193 sort.Strings(eqProps) |
| 194 for _, k := range eqProps { |
| 195 vals := q.eqFilts[k] |
| 196 k = gqlQuoteName(k) |
| 197 for _, v := range vals { |
| 198 if v.Type() == PTNull { |
| 199 filts = append(filts, fmt.Sprintf("%s IS
NULL", k)) |
| 200 } else { |
| 201 filts = append(filts, fmt.Sprintf("%s =
%s", k, v.GQL())) |
| 202 } |
| 203 } |
| 204 } |
| 205 } |
| 206 if q.ineqFiltProp != "" { |
| 207 for _, f := range [](func() (p, op string, v Property)){q.IneqFi
lterLow, q.IneqFilterHigh} { |
| 208 prop, op, v := f() |
| 209 if prop != "" { |
| 210 filts = append(filts, fmt.Sprintf("%s %s %s", gq
lQuoteName(prop), op, v.GQL())) |
| 211 } |
| 212 } |
| 213 } |
| 214 if anc.propType != PTNull { |
| 215 filts = append(filts, fmt.Sprintf("__key__ HAS ANCESTOR %s", anc
.GQL())) |
| 216 } |
| 217 if len(filts) > 0 { |
| 218 fmt.Fprintf(&ret, " WHERE %s", strings.Join(filts, " AND ")) |
| 219 } |
| 220 |
| 221 if len(q.orders) > 0 { |
| 222 orders := make([]string, len(q.orders)) |
| 223 for i, col := range q.orders { |
| 224 orders[i] = col.GQL() |
| 225 } |
| 226 fmt.Fprintf(&ret, " ORDER BY %s", strings.Join(orders, ", ")) |
| 227 } |
| 228 |
| 229 if q.limit != nil { |
| 230 fmt.Fprintf(&ret, " LIMIT %d", *q.limit) |
| 231 } |
| 232 if q.offset != nil { |
| 233 fmt.Fprintf(&ret, " OFFSET %d", *q.offset) |
| 234 } |
| 235 |
| 236 return ret.String() |
| 237 } |
| 238 |
| 239 func (q *FinalizedQuery) String() string { |
| 240 // TODO(riannucci): make a more compact go-like representation here. |
| 241 return q.GQL() |
| 242 } |
| 243 |
| 244 func (q *FinalizedQuery) Valid(aid, ns string) error { |
| 245 anc := q.Ancestor() |
| 246 if anc != nil && (!anc.Valid(false, aid, ns) || anc.Incomplete()) { |
| 247 return ErrInvalidKey |
| 248 } |
| 249 |
| 250 if q.ineqFiltProp == "__key__" { |
| 251 if q.ineqFiltLowSet && !q.ineqFiltLow.Value().(*Key).Valid(false
, aid, ns) { |
| 252 return ErrInvalidKey |
| 253 } |
| 254 if q.ineqFiltHighSet && !q.ineqFiltHigh.Value().(*Key).Valid(fal
se, aid, ns) { |
| 255 return ErrInvalidKey |
| 256 } |
| 257 } |
| 258 return nil |
| 259 } |
| OLD | NEW |