| Index: service/datastore/finalized_query.go
|
| diff --git a/service/datastore/finalized_query.go b/service/datastore/finalized_query.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..1a7db9ad23f736aba1a074d60ac60d6e0113b6b7
|
| --- /dev/null
|
| +++ b/service/datastore/finalized_query.go
|
| @@ -0,0 +1,259 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package datastore
|
| +
|
| +import (
|
| + "bytes"
|
| + "fmt"
|
| + "sort"
|
| + "strings"
|
| +)
|
| +
|
| +type FinalizedQuery struct {
|
| + original *Query
|
| +
|
| + kind string
|
| + eventuallyConsistent bool
|
| + distinct bool
|
| + keysOnly bool
|
| +
|
| + limit *int32
|
| + offset *int32
|
| +
|
| + start Cursor
|
| + end Cursor
|
| +
|
| + project []string
|
| + orders []IndexColumn
|
| +
|
| + eqFilts map[string]PropertySlice
|
| +
|
| + ineqFiltProp string
|
| + ineqFiltLow Property
|
| + ineqFiltLowIncl bool
|
| + ineqFiltLowSet bool
|
| + ineqFiltHigh Property
|
| + ineqFiltHighIncl bool
|
| + ineqFiltHighSet bool
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Original() *Query {
|
| + return q.original
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Kind() string {
|
| + return q.kind
|
| +}
|
| +
|
| +func (q *FinalizedQuery) EventuallyConsistent() bool {
|
| + return q.eventuallyConsistent
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Project() []string {
|
| + ret := make([]string, len(q.project))
|
| + copy(ret, q.project)
|
| + return ret
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Distinct() bool {
|
| + return q.distinct
|
| +}
|
| +
|
| +func (q *FinalizedQuery) KeysOnly() bool {
|
| + return q.keysOnly
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Limit() (int32, bool) {
|
| + if q.limit != nil {
|
| + return *q.limit, true
|
| + }
|
| + return 0, false
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Offset() (int32, bool) {
|
| + if q.offset != nil {
|
| + return *q.offset, true
|
| + }
|
| + return 0, false
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Orders() []IndexColumn {
|
| + ret := make([]IndexColumn, len(q.orders))
|
| + copy(ret, q.orders)
|
| + return ret
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Bounds() (start, end Cursor) {
|
| + return q.start, q.end
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Ancestor() *Key {
|
| + if anc, ok := q.eqFilts["__ancestor__"]; ok {
|
| + return anc[0].Value().(*Key)
|
| + }
|
| + return nil
|
| +}
|
| +
|
| +func (q *FinalizedQuery) EqFilters() map[string]PropertySlice {
|
| + ret := make(map[string]PropertySlice, len(q.eqFilts))
|
| + for k, v := range q.eqFilts {
|
| + newV := make(PropertySlice, len(v))
|
| + copy(newV, v)
|
| + ret[k] = newV
|
| + }
|
| + return ret
|
| +}
|
| +
|
| +func (q *FinalizedQuery) IneqFilterProp() string {
|
| + return q.ineqFiltProp
|
| +}
|
| +
|
| +func (q *FinalizedQuery) IneqFilterLow() (field, op string, val Property) {
|
| + if q.ineqFiltLowSet {
|
| + field = q.ineqFiltProp
|
| + val = q.ineqFiltLow
|
| + op = ">"
|
| + if q.ineqFiltLowIncl {
|
| + op = ">="
|
| + }
|
| + }
|
| + return
|
| +}
|
| +
|
| +func (q *FinalizedQuery) IneqFilterHigh() (field, op string, val Property) {
|
| + if q.ineqFiltHighSet {
|
| + field = q.ineqFiltProp
|
| + val = q.ineqFiltHigh
|
| + op = "<"
|
| + if q.ineqFiltHighIncl {
|
| + op = "<="
|
| + }
|
| + }
|
| + return
|
| +}
|
| +
|
| +var escaper = strings.NewReplacer(
|
| + "\\%", `\%`,
|
| + "\\_", `\_`,
|
| + "\\", `\\`,
|
| + "\x00", `\0`,
|
| + "\b", `\b`,
|
| + "\n", `\n`,
|
| + "\r", `\r`,
|
| + "\t", `\t`,
|
| + "\x1A", `\Z`,
|
| + "'", `\'`,
|
| + "\"", `\"`,
|
| + "`", "\\`",
|
| +)
|
| +
|
| +func gqlQuoteName(s string) string {
|
| + return fmt.Sprintf("`%s`", escaper.Replace(s))
|
| +}
|
| +
|
| +func gqlQuoteString(s string) string {
|
| + return fmt.Sprintf(`"%s"`, escaper.Replace(s))
|
| +}
|
| +
|
| +func (q *FinalizedQuery) GQL() string {
|
| + ret := bytes.Buffer{}
|
| +
|
| + ret.WriteString("SELECT")
|
| + if len(q.project) != 0 {
|
| + if q.distinct {
|
| + ret.WriteString(" DISTINCT")
|
| + }
|
| + proj := make([]string, len(q.project))
|
| + for i, p := range q.project {
|
| + proj[i] = gqlQuoteName(p)
|
| + }
|
| + ret.WriteString(" ")
|
| + ret.WriteString(strings.Join(proj, ", "))
|
| + } else {
|
| + ret.WriteString(" *")
|
| + }
|
| +
|
| + if q.kind != "" {
|
| + fmt.Fprintf(&ret, " FROM %s", gqlQuoteName(q.kind))
|
| + }
|
| +
|
| + filts := []string(nil)
|
| + anc := Property{}
|
| + if len(q.eqFilts) > 0 {
|
| + eqProps := make([]string, 0, len(q.eqFilts))
|
| + for k, v := range q.eqFilts {
|
| + if k == "__ancestor__" {
|
| + anc = v[0]
|
| + continue
|
| + }
|
| + eqProps = append(eqProps, k)
|
| + }
|
| + sort.Strings(eqProps)
|
| + for _, k := range eqProps {
|
| + vals := q.eqFilts[k]
|
| + k = gqlQuoteName(k)
|
| + for _, v := range vals {
|
| + if v.Type() == PTNull {
|
| + filts = append(filts, fmt.Sprintf("%s IS NULL", k))
|
| + } else {
|
| + filts = append(filts, fmt.Sprintf("%s = %s", k, v.GQL()))
|
| + }
|
| + }
|
| + }
|
| + }
|
| + if q.ineqFiltProp != "" {
|
| + for _, f := range [](func() (p, op string, v Property)){q.IneqFilterLow, q.IneqFilterHigh} {
|
| + prop, op, v := f()
|
| + if prop != "" {
|
| + filts = append(filts, fmt.Sprintf("%s %s %s", gqlQuoteName(prop), op, v.GQL()))
|
| + }
|
| + }
|
| + }
|
| + if anc.propType != PTNull {
|
| + filts = append(filts, fmt.Sprintf("__key__ HAS ANCESTOR %s", anc.GQL()))
|
| + }
|
| + if len(filts) > 0 {
|
| + fmt.Fprintf(&ret, " WHERE %s", strings.Join(filts, " AND "))
|
| + }
|
| +
|
| + if len(q.orders) > 0 {
|
| + orders := make([]string, len(q.orders))
|
| + for i, col := range q.orders {
|
| + orders[i] = col.GQL()
|
| + }
|
| + fmt.Fprintf(&ret, " ORDER BY %s", strings.Join(orders, ", "))
|
| + }
|
| +
|
| + if q.limit != nil {
|
| + fmt.Fprintf(&ret, " LIMIT %d", *q.limit)
|
| + }
|
| + if q.offset != nil {
|
| + fmt.Fprintf(&ret, " OFFSET %d", *q.offset)
|
| + }
|
| +
|
| + return ret.String()
|
| +}
|
| +
|
| +func (q *FinalizedQuery) String() string {
|
| + // TODO(riannucci): make a more compact go-like representation here.
|
| + return q.GQL()
|
| +}
|
| +
|
| +func (q *FinalizedQuery) Valid(aid, ns string) error {
|
| + anc := q.Ancestor()
|
| + if anc != nil && (!anc.Valid(false, aid, ns) || anc.Incomplete()) {
|
| + return ErrInvalidKey
|
| + }
|
| +
|
| + if q.ineqFiltProp == "__key__" {
|
| + if q.ineqFiltLowSet && !q.ineqFiltLow.Value().(*Key).Valid(false, aid, ns) {
|
| + return ErrInvalidKey
|
| + }
|
| + if q.ineqFiltHighSet && !q.ineqFiltHigh.Value().(*Key).Valid(false, aid, ns) {
|
| + return ErrInvalidKey
|
| + }
|
| + }
|
| + return nil
|
| +}
|
|
|