// Copyright 2017 Google Inc. All Rights Reserved. // // 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() }