|
- // Copyright 2017 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package firestore
-
- import (
- "bytes"
- "errors"
- "fmt"
- "reflect"
- "regexp"
- "sort"
- "strings"
-
- "cloud.google.com/go/internal/atomiccache"
- "cloud.google.com/go/internal/fields"
- pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
- )
-
- // A FieldPath is a non-empty sequence of non-empty fields that reference a value.
- //
- // A FieldPath value should only be necessary if one of the field names contains
- // one of the runes ".˜*/[]". Most methods accept a simpler form of field path
- // as a string in which the individual fields are separated by dots.
- // For example,
- // []string{"a", "b"}
- // is equivalent to the string form
- // "a.b"
- // but
- // []string{"*"}
- // has no equivalent string form.
- type FieldPath []string
-
- // parseDotSeparatedString constructs a FieldPath from a string that separates
- // path components with dots. Other than splitting at dots and checking for invalid
- // characters, it ignores everything else about the string,
- // including attempts to quote field path compontents. So "a.`b.c`.d" is parsed into
- // four parts, "a", "`b", "c`" and "d".
- func parseDotSeparatedString(s string) (FieldPath, error) {
- const invalidRunes = "~*/[]"
- if strings.ContainsAny(s, invalidRunes) {
- return nil, fmt.Errorf("firestore: %q contains an invalid rune (one of %s)", s, invalidRunes)
- }
- fp := FieldPath(strings.Split(s, "."))
- if err := fp.validate(); err != nil {
- return nil, err
- }
- return fp, nil
- }
-
- func (fp1 FieldPath) equal(fp2 FieldPath) bool {
- if len(fp1) != len(fp2) {
- return false
- }
- for i, c1 := range fp1 {
- if c1 != fp2[i] {
- return false
- }
- }
- return true
- }
-
- func (fp1 FieldPath) prefixOf(fp2 FieldPath) bool {
- return len(fp1) <= len(fp2) && fp1.equal(fp2[:len(fp1)])
- }
-
- // Lexicographic ordering.
- func (fp1 FieldPath) less(fp2 FieldPath) bool {
- for i := range fp1 {
- switch {
- case i >= len(fp2):
- return false
- case fp1[i] < fp2[i]:
- return true
- case fp1[i] > fp2[i]:
- return false
- }
- }
- // fp1 and fp2 are equal up to len(fp1).
- return len(fp1) < len(fp2)
- }
-
- // validate checks the validity of fp and returns an error if it is invalid.
- func (fp FieldPath) validate() error {
- if len(fp) == 0 {
- return errors.New("firestore: empty field path")
- }
- for _, c := range fp {
- if len(c) == 0 {
- return errors.New("firestore: empty component in field path")
- }
- }
- return nil
- }
-
- // with creates a new FieldPath consisting of fp followed by k.
- func (fp FieldPath) with(k string) FieldPath {
- r := make(FieldPath, len(fp), len(fp)+1)
- copy(r, fp)
- return append(r, k)
- }
-
- // concat creates a new FieldPath consisting of fp1 followed by fp2.
- func (fp1 FieldPath) concat(fp2 FieldPath) FieldPath {
- r := make(FieldPath, len(fp1)+len(fp2))
- copy(r, fp1)
- copy(r[len(fp1):], fp2)
- return r
- }
-
- // in reports whether fp is equal to one of the fps.
- func (fp FieldPath) in(fps []FieldPath) bool {
- for _, e := range fps {
- if fp.equal(e) {
- return true
- }
- }
- return false
- }
-
- // checkNoDupOrPrefix checks whether any FieldPath is a prefix of (or equal to)
- // another.
- // It modifies the order of FieldPaths in its argument (via sorting).
- func checkNoDupOrPrefix(fps []FieldPath) error {
- // Sort fps lexicographically.
- sort.Sort(byPath(fps))
- // Check adjacent pairs for prefix.
- for i := 1; i < len(fps); i++ {
- if fps[i-1].prefixOf(fps[i]) {
- return fmt.Errorf("field path %v cannot be used in the same update as %v", fps[i-1], fps[i])
- }
- }
- return nil
- }
-
- type byPath []FieldPath
-
- func (b byPath) Len() int { return len(b) }
- func (b byPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
- func (b byPath) Less(i, j int) bool { return b[i].less(b[j]) }
-
- // setAtPath sets val at the location in m specified by fp, creating sub-maps as
- // needed. m must not be nil. fp is assumed to be valid.
- func setAtPath(m map[string]*pb.Value, fp FieldPath, val *pb.Value) {
- if val == nil {
- return
- }
- if len(fp) == 1 {
- m[fp[0]] = val
- } else {
- v, ok := m[fp[0]]
- if !ok {
- v = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: map[string]*pb.Value{}}}}
- m[fp[0]] = v
- }
- // The type assertion below cannot fail, because setAtPath is only called
- // with either an empty map or one filled by setAtPath itself, and the
- // set of FieldPaths it is called with has been checked to make sure that
- // no path is the prefix of any other.
- setAtPath(v.GetMapValue().Fields, fp[1:], val)
- }
- }
-
- // getAtPath gets the value in data referred to by fp. The data argument can
- // be a map or a struct.
- // Compare with valueAtPath, which does the same thing for a document.
- func getAtPath(v reflect.Value, fp FieldPath) (interface{}, error) {
- var err error
- for _, k := range fp {
- v, err = getAtField(v, k)
- if err != nil {
- return nil, err
- }
- }
- return v.Interface(), nil
- }
-
- // getAtField returns the equivalent of v[k], if v is a map, or v.k if v is a struct.
- func getAtField(v reflect.Value, k string) (reflect.Value, error) {
- switch v.Kind() {
- case reflect.Map:
- if r := v.MapIndex(reflect.ValueOf(k)); r.IsValid() {
- return r, nil
- }
-
- case reflect.Struct:
- fm, err := fieldMap(v.Type())
- if err != nil {
- return reflect.Value{}, err
- }
- if f, ok := fm[k]; ok {
- return v.FieldByIndex(f.Index), nil
- }
-
- case reflect.Interface:
- return getAtField(v.Elem(), k)
-
- case reflect.Ptr:
- return getAtField(v.Elem(), k)
- }
- return reflect.Value{}, fmt.Errorf("firestore: no field %q for value %#v", k, v)
- }
-
- // fieldMapCache holds maps from from Firestore field name to struct field,
- // keyed by struct type.
- // TODO(jba): replace with sync.Map for Go 1.9.
- var fieldMapCache atomiccache.Cache
-
- func fieldMap(t reflect.Type) (map[string]fields.Field, error) {
- x := fieldMapCache.Get(t, func() interface{} {
- fieldList, err := fieldCache.Fields(t)
- if err != nil {
- return err
- }
- m := map[string]fields.Field{}
- for _, f := range fieldList {
- m[f.Name] = f
- }
- return m
- })
- if err, ok := x.(error); ok {
- return nil, err
- }
- return x.(map[string]fields.Field), nil
- }
-
- // toServiceFieldPath converts fp the form required by the Firestore service.
- // It assumes fp has been validated.
- func (fp FieldPath) toServiceFieldPath() string {
- cs := make([]string, len(fp))
- for i, c := range fp {
- cs[i] = toServiceFieldPathComponent(c)
- }
- return strings.Join(cs, ".")
- }
-
- func toServiceFieldPaths(fps []FieldPath) []string {
- var sfps []string
- for _, fp := range fps {
- sfps = append(sfps, fp.toServiceFieldPath())
- }
- return sfps
- }
-
- // Google SQL syntax for an unquoted field.
- var unquotedFieldRegexp = regexp.MustCompile("^[A-Za-z_][A-Za-z_0-9]*$")
-
- // toServiceFieldPathComponent returns a string that represents key and is a valid
- // field path component.
- func toServiceFieldPathComponent(key string) string {
- if unquotedFieldRegexp.MatchString(key) {
- return key
- }
- var buf bytes.Buffer
- buf.WriteRune('`')
- for _, r := range key {
- if r == '`' || r == '\\' {
- buf.WriteRune('\\')
- }
- buf.WriteRune(r)
- }
- buf.WriteRune('`')
- return buf.String()
- }
|