1// Copyright 2017 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package firestore 16 17import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "reflect" 22 "regexp" 23 "sort" 24 "strings" 25 "sync" 26 27 "cloud.google.com/go/internal/fields" 28 pb "google.golang.org/genproto/googleapis/firestore/v1" 29) 30 31// A FieldPath is a non-empty sequence of non-empty fields that reference a value. 32// 33// A FieldPath value should only be necessary if one of the field names contains 34// one of the runes ".˜*/[]". Most methods accept a simpler form of field path 35// as a string in which the individual fields are separated by dots. 36// For example, 37// []string{"a", "b"} 38// is equivalent to the string form 39// "a.b" 40// but 41// []string{"*"} 42// has no equivalent string form. 43type FieldPath []string 44 45// parseDotSeparatedString constructs a FieldPath from a string that separates 46// path components with dots. Other than splitting at dots and checking for invalid 47// characters, it ignores everything else about the string, 48// including attempts to quote field path compontents. So "a.`b.c`.d" is parsed into 49// four parts, "a", "`b", "c`" and "d". 50func parseDotSeparatedString(s string) (FieldPath, error) { 51 const invalidRunes = "~*/[]" 52 if strings.ContainsAny(s, invalidRunes) { 53 return nil, fmt.Errorf("firestore: %q contains an invalid rune (one of %s)", s, invalidRunes) 54 } 55 fp := FieldPath(strings.Split(s, ".")) 56 if err := fp.validate(); err != nil { 57 return nil, err 58 } 59 return fp, nil 60} 61 62func (fp1 FieldPath) equal(fp2 FieldPath) bool { 63 if len(fp1) != len(fp2) { 64 return false 65 } 66 for i, c1 := range fp1 { 67 if c1 != fp2[i] { 68 return false 69 } 70 } 71 return true 72} 73 74func (fp1 FieldPath) prefixOf(fp2 FieldPath) bool { 75 return len(fp1) <= len(fp2) && fp1.equal(fp2[:len(fp1)]) 76} 77 78// Lexicographic ordering. 79func (fp1 FieldPath) less(fp2 FieldPath) bool { 80 for i := range fp1 { 81 switch { 82 case i >= len(fp2): 83 return false 84 case fp1[i] < fp2[i]: 85 return true 86 case fp1[i] > fp2[i]: 87 return false 88 } 89 } 90 // fp1 and fp2 are equal up to len(fp1). 91 return len(fp1) < len(fp2) 92} 93 94// validate checks the validity of fp and returns an error if it is invalid. 95func (fp FieldPath) validate() error { 96 if len(fp) == 0 { 97 return errors.New("firestore: empty field path") 98 } 99 for _, c := range fp { 100 if len(c) == 0 { 101 return errors.New("firestore: empty component in field path") 102 } 103 } 104 return nil 105} 106 107// with creates a new FieldPath consisting of fp followed by k. 108func (fp FieldPath) with(k string) FieldPath { 109 r := make(FieldPath, len(fp), len(fp)+1) 110 copy(r, fp) 111 return append(r, k) 112} 113 114// checkNoDupOrPrefix checks whether any FieldPath is a prefix of (or equal to) 115// another. 116// It modifies the order of FieldPaths in its argument (via sorting). 117func checkNoDupOrPrefix(fps []FieldPath) error { 118 // Sort fps lexicographically. 119 sort.Sort(byPath(fps)) 120 // Check adjacent pairs for prefix. 121 for i := 1; i < len(fps); i++ { 122 if fps[i-1].prefixOf(fps[i]) { 123 return fmt.Errorf("field path %v cannot be used in the same update as %v", fps[i-1], fps[i]) 124 } 125 } 126 return nil 127} 128 129type byPath []FieldPath 130 131func (b byPath) Len() int { return len(b) } 132func (b byPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 133func (b byPath) Less(i, j int) bool { return b[i].less(b[j]) } 134 135// setAtPath sets val at the location in m specified by fp, creating sub-maps as 136// needed. m must not be nil. fp is assumed to be valid. 137func setAtPath(m map[string]*pb.Value, fp FieldPath, val *pb.Value) { 138 if val == nil { 139 return 140 } 141 if len(fp) == 1 { 142 m[fp[0]] = val 143 } else { 144 v, ok := m[fp[0]] 145 if !ok { 146 v = &pb.Value{ValueType: &pb.Value_MapValue{&pb.MapValue{Fields: map[string]*pb.Value{}}}} 147 m[fp[0]] = v 148 } 149 // The type assertion below cannot fail, because setAtPath is only called 150 // with either an empty map or one filled by setAtPath itself, and the 151 // set of FieldPaths it is called with has been checked to make sure that 152 // no path is the prefix of any other. 153 setAtPath(v.GetMapValue().Fields, fp[1:], val) 154 } 155} 156 157// getAtPath gets the value in data referred to by fp. The data argument can 158// be a map or a struct. 159// Compare with valueAtPath, which does the same thing for a document. 160func getAtPath(v reflect.Value, fp FieldPath) (interface{}, error) { 161 var err error 162 for _, k := range fp { 163 v, err = getAtField(v, k) 164 if err != nil { 165 return nil, err 166 } 167 } 168 return v.Interface(), nil 169} 170 171// getAtField returns the equivalent of v[k], if v is a map, or v.k if v is a struct. 172func getAtField(v reflect.Value, k string) (reflect.Value, error) { 173 switch v.Kind() { 174 case reflect.Map: 175 if r := v.MapIndex(reflect.ValueOf(k)); r.IsValid() { 176 return r, nil 177 } 178 179 case reflect.Struct: 180 fm, err := fieldMap(v.Type()) 181 if err != nil { 182 return reflect.Value{}, err 183 } 184 if f, ok := fm[k]; ok { 185 return v.FieldByIndex(f.Index), nil 186 } 187 188 case reflect.Interface: 189 return getAtField(v.Elem(), k) 190 191 case reflect.Ptr: 192 return getAtField(v.Elem(), k) 193 } 194 return reflect.Value{}, fmt.Errorf("firestore: no field %q for value %#v", k, v) 195} 196 197// fieldMapCache holds maps from from Firestore field name to struct field, 198// keyed by struct type. 199var fieldMapCache sync.Map 200 201func fieldMap(t reflect.Type) (map[string]fields.Field, error) { 202 x, ok := fieldMapCache.Load(t) 203 if !ok { 204 fieldList, err := fieldCache.Fields(t) 205 if err != nil { 206 x = err 207 } else { 208 m := map[string]fields.Field{} 209 for _, f := range fieldList { 210 m[f.Name] = f 211 } 212 x = m 213 } 214 fieldMapCache.Store(t, x) 215 } 216 if err, ok := x.(error); ok { 217 return nil, err 218 } 219 return x.(map[string]fields.Field), nil 220} 221 222// toServiceFieldPath converts fp the form required by the Firestore service. 223// It assumes fp has been validated. 224func (fp FieldPath) toServiceFieldPath() string { 225 cs := make([]string, len(fp)) 226 for i, c := range fp { 227 cs[i] = toServiceFieldPathComponent(c) 228 } 229 return strings.Join(cs, ".") 230} 231 232func toServiceFieldPaths(fps []FieldPath) []string { 233 var sfps []string 234 for _, fp := range fps { 235 sfps = append(sfps, fp.toServiceFieldPath()) 236 } 237 return sfps 238} 239 240// Google SQL syntax for an unquoted field. 241var unquotedFieldRegexp = regexp.MustCompile("^[A-Za-z_][A-Za-z_0-9]*$") 242 243// toServiceFieldPathComponent returns a string that represents key and is a valid 244// field path component. 245func toServiceFieldPathComponent(key string) string { 246 if unquotedFieldRegexp.MatchString(key) { 247 return key 248 } 249 var buf bytes.Buffer 250 buf.WriteRune('`') 251 for _, r := range key { 252 if r == '`' || r == '\\' { 253 buf.WriteRune('\\') 254 } 255 buf.WriteRune(r) 256 } 257 buf.WriteRune('`') 258 return buf.String() 259} 260