feat: Inital commit
This commit is contained in:
+476
@@ -0,0 +1,476 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
pb "cloud.google.com/go/firestore/apiv1/firestorepb"
|
||||
"cloud.google.com/go/internal/fields"
|
||||
)
|
||||
|
||||
func setFromProtoValue(x interface{}, vproto *pb.Value, c *Client) error {
|
||||
v := reflect.ValueOf(x)
|
||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
return errors.New("firestore: nil or not a pointer")
|
||||
}
|
||||
return setReflectFromProtoValue(v.Elem(), vproto, c)
|
||||
}
|
||||
|
||||
// setReflectFromProtoValue sets vDest from a Firestore Value.
|
||||
// vDest must be a settable value.
|
||||
func setReflectFromProtoValue(vDest reflect.Value, vprotoSrc *pb.Value, c *Client) error {
|
||||
typeErr := func() error {
|
||||
return fmt.Errorf("firestore: cannot set type %s to %s", vDest.Type(), typeString(vprotoSrc))
|
||||
}
|
||||
|
||||
valTypeSrc := vprotoSrc.ValueType
|
||||
// A Null value sets anything nullable to nil, and has no effect
|
||||
// on anything else.
|
||||
if _, ok := valTypeSrc.(*pb.Value_NullValue); ok {
|
||||
switch vDest.Kind() {
|
||||
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
||||
vDest.Set(reflect.Zero(vDest.Type()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle special types first.
|
||||
switch vDest.Type() {
|
||||
case typeOfByteSlice:
|
||||
x, ok := valTypeSrc.(*pb.Value_BytesValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vDest.SetBytes(x.BytesValue)
|
||||
return nil
|
||||
|
||||
case typeOfGoTime:
|
||||
x, ok := valTypeSrc.(*pb.Value_TimestampValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
if err := x.TimestampValue.CheckValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
vDest.Set(reflect.ValueOf(x.TimestampValue.AsTime()))
|
||||
return nil
|
||||
|
||||
case typeOfProtoTimestamp:
|
||||
x, ok := valTypeSrc.(*pb.Value_TimestampValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vDest.Set(reflect.ValueOf(x.TimestampValue))
|
||||
return nil
|
||||
|
||||
case typeOfLatLng:
|
||||
x, ok := valTypeSrc.(*pb.Value_GeoPointValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vDest.Set(reflect.ValueOf(x.GeoPointValue))
|
||||
return nil
|
||||
|
||||
case typeOfDocumentRef:
|
||||
x, ok := valTypeSrc.(*pb.Value_ReferenceValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
dr, err := pathToDoc(x.ReferenceValue, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vDest.Set(reflect.ValueOf(dr))
|
||||
return nil
|
||||
|
||||
case typeOfVector32:
|
||||
val, err := vector32FromProtoValue(vprotoSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vDest.Set(reflect.ValueOf(val))
|
||||
return nil
|
||||
case typeOfVector64:
|
||||
val, err := vector64FromProtoValue(vprotoSrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vDest.Set(reflect.ValueOf(val))
|
||||
return nil
|
||||
}
|
||||
|
||||
switch vDest.Kind() {
|
||||
case reflect.Bool:
|
||||
x, ok := valTypeSrc.(*pb.Value_BooleanValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vDest.SetBool(x.BooleanValue)
|
||||
|
||||
case reflect.String:
|
||||
x, ok := valTypeSrc.(*pb.Value_StringValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vDest.SetString(x.StringValue)
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var i int64
|
||||
switch x := valTypeSrc.(type) {
|
||||
case *pb.Value_IntegerValue:
|
||||
i = x.IntegerValue
|
||||
case *pb.Value_DoubleValue:
|
||||
f := x.DoubleValue
|
||||
i = int64(f)
|
||||
if float64(i) != f {
|
||||
return fmt.Errorf("firestore: float %f does not fit into %s", f, vDest.Type())
|
||||
}
|
||||
default:
|
||||
return typeErr()
|
||||
}
|
||||
if vDest.OverflowInt(i) {
|
||||
return overflowErr(vDest, i)
|
||||
}
|
||||
vDest.SetInt(i)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
var u uint64
|
||||
switch x := valTypeSrc.(type) {
|
||||
case *pb.Value_IntegerValue:
|
||||
u = uint64(x.IntegerValue)
|
||||
case *pb.Value_DoubleValue:
|
||||
f := x.DoubleValue
|
||||
u = uint64(f)
|
||||
if float64(u) != f {
|
||||
return fmt.Errorf("firestore: float %f does not fit into %s", f, vDest.Type())
|
||||
}
|
||||
default:
|
||||
return typeErr()
|
||||
}
|
||||
if vDest.OverflowUint(u) {
|
||||
return overflowErr(vDest, u)
|
||||
}
|
||||
vDest.SetUint(u)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
var f float64
|
||||
switch x := valTypeSrc.(type) {
|
||||
case *pb.Value_DoubleValue:
|
||||
f = x.DoubleValue
|
||||
case *pb.Value_IntegerValue:
|
||||
f = float64(x.IntegerValue)
|
||||
if int64(f) != x.IntegerValue {
|
||||
return overflowErr(vDest, x.IntegerValue)
|
||||
}
|
||||
default:
|
||||
return typeErr()
|
||||
}
|
||||
if vDest.OverflowFloat(f) {
|
||||
return overflowErr(vDest, f)
|
||||
}
|
||||
vDest.SetFloat(f)
|
||||
|
||||
case reflect.Slice:
|
||||
x, ok := valTypeSrc.(*pb.Value_ArrayValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vals := x.ArrayValue.Values
|
||||
vlen := vDest.Len()
|
||||
xlen := len(vals)
|
||||
// Make a slice of the right size, avoiding allocation if possible.
|
||||
switch {
|
||||
case vlen < xlen:
|
||||
vDest.Set(reflect.MakeSlice(vDest.Type(), xlen, xlen))
|
||||
case vlen > xlen:
|
||||
vDest.SetLen(xlen)
|
||||
}
|
||||
return populateRepeated(vDest, vals, xlen, c)
|
||||
|
||||
case reflect.Array:
|
||||
x, ok := valTypeSrc.(*pb.Value_ArrayValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
vals := x.ArrayValue.Values
|
||||
xlen := len(vals)
|
||||
vlen := vDest.Len()
|
||||
minlen := vlen
|
||||
// Set extra elements to their zero value.
|
||||
if vlen > xlen {
|
||||
z := reflect.Zero(vDest.Type().Elem())
|
||||
for i := xlen; i < vlen; i++ {
|
||||
vDest.Index(i).Set(z)
|
||||
}
|
||||
minlen = xlen
|
||||
}
|
||||
return populateRepeated(vDest, vals, minlen, c)
|
||||
|
||||
case reflect.Map:
|
||||
x, ok := valTypeSrc.(*pb.Value_MapValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
return populateMap(vDest, x.MapValue.Fields, c)
|
||||
|
||||
case reflect.Ptr:
|
||||
// If the pointer is nil, set it to a zero value.
|
||||
if vDest.IsNil() {
|
||||
vDest.Set(reflect.New(vDest.Type().Elem()))
|
||||
}
|
||||
return setReflectFromProtoValue(vDest.Elem(), vprotoSrc, c)
|
||||
|
||||
case reflect.Struct:
|
||||
x, ok := valTypeSrc.(*pb.Value_MapValue)
|
||||
if !ok {
|
||||
return typeErr()
|
||||
}
|
||||
return populateStruct(vDest, x.MapValue.Fields, c)
|
||||
|
||||
case reflect.Interface:
|
||||
if vDest.NumMethod() == 0 { // empty interface
|
||||
// If v holds a pointer, set the pointer.
|
||||
if !vDest.IsNil() && vDest.Elem().Kind() == reflect.Ptr {
|
||||
return setReflectFromProtoValue(vDest.Elem(), vprotoSrc, c)
|
||||
}
|
||||
// Otherwise, create a fresh value.
|
||||
x, err := createFromProtoValue(vprotoSrc, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vDest.Set(reflect.ValueOf(x))
|
||||
return nil
|
||||
}
|
||||
// Any other kind of interface is an error.
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
return fmt.Errorf("firestore: cannot set type %s", vDest.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateRepeated sets the first n elements of vr, which must be a slice or
|
||||
// array, to the corresponding elements of vals.
|
||||
func populateRepeated(vr reflect.Value, vals []*pb.Value, n int, c *Client) error {
|
||||
for i := 0; i < n; i++ {
|
||||
if err := setReflectFromProtoValue(vr.Index(i), vals[i], c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// populateMap sets the elements of vm, which must be a map, from the
|
||||
// corresponding elements of pm.
|
||||
//
|
||||
// Since a map value is not settable, this function always creates a new
|
||||
// element for each corresponding map key. Existing values of vm are
|
||||
// overwritten. This happens even if the map value is something like a pointer
|
||||
// to a struct, where we could in theory populate the existing struct value
|
||||
// instead of discarding it. This behavior matches encoding/json.
|
||||
func populateMap(vm reflect.Value, pm map[string]*pb.Value, c *Client) error {
|
||||
t := vm.Type()
|
||||
if t.Key().Kind() != reflect.String {
|
||||
return errors.New("firestore: map key type is not string")
|
||||
}
|
||||
if vm.IsNil() {
|
||||
vm.Set(reflect.MakeMap(t))
|
||||
}
|
||||
et := t.Elem()
|
||||
for k, vproto := range pm {
|
||||
el := reflect.New(et).Elem()
|
||||
if err := setReflectFromProtoValue(el, vproto, c); err != nil {
|
||||
return err
|
||||
}
|
||||
vm.SetMapIndex(reflect.ValueOf(k), el)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createMapFromValueMap creates a fresh map and populates it with pm.
|
||||
func createMapFromValueMap(pm map[string]*pb.Value, c *Client) (map[string]interface{}, error) {
|
||||
m := map[string]interface{}{}
|
||||
for k, pv := range pm {
|
||||
v, err := createFromProtoValue(pv, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m[k] = v
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// populateStruct sets the fields of vs, which must be a struct, from
|
||||
// the matching elements of pm.
|
||||
func populateStruct(vs reflect.Value, pm map[string]*pb.Value, c *Client) error {
|
||||
fs, err := fieldCache.Fields(vs.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type match struct {
|
||||
vproto *pb.Value
|
||||
f *fields.Field
|
||||
}
|
||||
// Find best field matches
|
||||
matched := make(map[string]match)
|
||||
for k, vproto := range pm {
|
||||
f := fs.Match(k)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := matched[f.Name]; ok {
|
||||
// If multiple case insensitive fields match, the exact match
|
||||
// should win.
|
||||
if f.Name == k {
|
||||
matched[k] = match{vproto: vproto, f: f}
|
||||
}
|
||||
} else {
|
||||
matched[f.Name] = match{vproto: vproto, f: f}
|
||||
}
|
||||
}
|
||||
|
||||
// Reflect values
|
||||
for _, v := range matched {
|
||||
f := v.f
|
||||
vproto := v.vproto
|
||||
|
||||
if err := setReflectFromProtoValue(vs.FieldByIndex(f.Index), vproto, c); err != nil {
|
||||
return fmt.Errorf("%s.%s: %w", vs.Type(), f.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createFromProtoValue(vproto *pb.Value, c *Client) (interface{}, error) {
|
||||
switch v := vproto.ValueType.(type) {
|
||||
case *pb.Value_NullValue:
|
||||
return nil, nil
|
||||
case *pb.Value_BooleanValue:
|
||||
return v.BooleanValue, nil
|
||||
case *pb.Value_IntegerValue:
|
||||
return v.IntegerValue, nil
|
||||
case *pb.Value_DoubleValue:
|
||||
return v.DoubleValue, nil
|
||||
case *pb.Value_TimestampValue:
|
||||
if err := v.TimestampValue.CheckValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.TimestampValue.AsTime(), nil
|
||||
case *pb.Value_StringValue:
|
||||
return v.StringValue, nil
|
||||
case *pb.Value_BytesValue:
|
||||
return v.BytesValue, nil
|
||||
case *pb.Value_ReferenceValue:
|
||||
return pathToDoc(v.ReferenceValue, c)
|
||||
case *pb.Value_GeoPointValue:
|
||||
return v.GeoPointValue, nil
|
||||
case *pb.Value_ArrayValue:
|
||||
vals := v.ArrayValue.Values
|
||||
ret := make([]interface{}, len(vals))
|
||||
for i, v := range vals {
|
||||
r, err := createFromProtoValue(v, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[i] = r
|
||||
}
|
||||
return ret, nil
|
||||
|
||||
case *pb.Value_MapValue:
|
||||
fields := v.MapValue.Fields
|
||||
ret := make(map[string]interface{}, len(fields))
|
||||
for k, v := range fields {
|
||||
r, err := createFromProtoValue(v, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[k] = r
|
||||
}
|
||||
|
||||
typeVal, ok := ret[typeKey]
|
||||
if !ok || typeVal != typeValVector {
|
||||
// Map is not a vector. Return the map
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Special handling for vector
|
||||
return vector64FromProtoValue(vproto)
|
||||
default:
|
||||
return nil, fmt.Errorf("firestore: unknown value type %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a document path to a DocumentRef.
|
||||
func pathToDoc(docPath string, c *Client) (*DocumentRef, error) {
|
||||
projID, dbID, docIDs, err := parseDocumentPath(docPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentResourceName := fmt.Sprintf("projects/%s/databases/%s", projID, dbID)
|
||||
_, doc := c.idsToRef(docIDs, parentResourceName)
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
// A document path should be of the form "projects/P/databases/D/documents/coll1/doc1/coll2/doc2/...".
|
||||
func parseDocumentPath(path string) (projectID, databaseID string, docPath []string, err error) {
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) < 6 || parts[0] != "projects" || parts[2] != "databases" || parts[4] != "documents" {
|
||||
return "", "", nil, fmt.Errorf("firestore: malformed document path %q", path)
|
||||
}
|
||||
docp := parts[5:]
|
||||
if len(docp)%2 != 0 {
|
||||
return "", "", nil, fmt.Errorf("firestore: path %q refers to collection, not document", path)
|
||||
}
|
||||
return parts[1], parts[3], docp, nil
|
||||
}
|
||||
|
||||
func typeString(vproto *pb.Value) string {
|
||||
switch vproto.ValueType.(type) {
|
||||
case *pb.Value_NullValue:
|
||||
return "null"
|
||||
case *pb.Value_BooleanValue:
|
||||
return "bool"
|
||||
case *pb.Value_IntegerValue:
|
||||
return "int"
|
||||
case *pb.Value_DoubleValue:
|
||||
return "float"
|
||||
case *pb.Value_TimestampValue:
|
||||
return "timestamp"
|
||||
case *pb.Value_StringValue:
|
||||
return "string"
|
||||
case *pb.Value_BytesValue:
|
||||
return "bytes"
|
||||
case *pb.Value_ReferenceValue:
|
||||
return "reference"
|
||||
case *pb.Value_GeoPointValue:
|
||||
return "GeoPoint"
|
||||
case *pb.Value_MapValue:
|
||||
return "map"
|
||||
case *pb.Value_ArrayValue:
|
||||
return "array"
|
||||
default:
|
||||
return "<unknown Value type>"
|
||||
}
|
||||
}
|
||||
|
||||
func overflowErr(v reflect.Value, x interface{}) error {
|
||||
return fmt.Errorf("firestore: value %v overflows type %s", x, v.Type())
|
||||
}
|
||||
Reference in New Issue
Block a user