go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/lsp.go (about)

     1  // Copyright 2023 The LUCI Authors.
     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  
    15  package datastore
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"google.golang.org/protobuf/proto"
    21  
    22  	pb "go.chromium.org/luci/gae/service/datastore/internal/protos/datastore"
    23  )
    24  
    25  // loadLegacyLSP deserializes a "local structure property" into a property map.
    26  //
    27  // It supports only basic set of property types (e.g. no geo-points or keys).
    28  func loadLegacyLSP(blob []byte) (PropertyMap, error) {
    29  	// Key and EntityGroup are marked `required` in the proto, but they aren't
    30  	// actually populate by Python code.
    31  	ent := pb.EntityProto{}
    32  	if err := (proto.UnmarshalOptions{AllowPartial: true}).Unmarshal(blob, &ent); err != nil {
    33  		return nil, err
    34  	}
    35  
    36  	switch {
    37  	case ent.Key != nil:
    38  		return nil, fmt.Errorf("unexpectedly populated `key` in %s", &ent)
    39  	case ent.EntityGroup != nil:
    40  		return nil, fmt.Errorf("unexpectedly populated `entity_group` in %s", &ent)
    41  	case ent.Owner != nil:
    42  		return nil, fmt.Errorf("unexpectedly populated `owner` in %s", &ent)
    43  	case ent.Kind != nil:
    44  		return nil, fmt.Errorf("unexpectedly populated `kind` in %s", &ent)
    45  	case ent.KindUri != nil:
    46  		return nil, fmt.Errorf("unexpectedly populated `kind_uri` in %s", &ent)
    47  	}
    48  
    49  	pmap := make(PropertyMap, len(ent.Property)+len(ent.RawProperty))
    50  
    51  	addOne := func(p *pb.Property) error {
    52  		name, val, err := decodeLSPProp(p)
    53  		if err != nil {
    54  			return err
    55  		}
    56  		switch cur := pmap[name].(type) {
    57  		case PropertySlice:
    58  			pmap[name] = append(cur, val)
    59  		case Property:
    60  			pmap[name] = PropertySlice{cur, val}
    61  		case nil:
    62  			if p.GetMultiple() {
    63  				pmap[name] = PropertySlice{val}
    64  			} else {
    65  				pmap[name] = val
    66  			}
    67  		default:
    68  			panic("impossible")
    69  		}
    70  		return nil
    71  	}
    72  
    73  	for _, prop := range ent.Property {
    74  		if err := addOne(prop); err != nil {
    75  			return nil, err
    76  		}
    77  	}
    78  	for _, prop := range ent.RawProperty {
    79  		if err := addOne(prop); err != nil {
    80  			return nil, err
    81  		}
    82  	}
    83  	return pmap, nil
    84  }
    85  
    86  func decodeLSPProp(p *pb.Property) (name string, prop Property, err error) {
    87  	if name = p.GetName(); name == "" {
    88  		err = fmt.Errorf("there's a property without a name")
    89  		return
    90  	}
    91  
    92  	v := p.Value
    93  	if v == nil {
    94  		err = fmt.Errorf("property %q has no value", name)
    95  		return
    96  	}
    97  
    98  	switch {
    99  	case v.Int64Value != nil:
   100  		prop = MkPropertyNI(*v.Int64Value)
   101  	case v.BooleanValue != nil:
   102  		prop = MkPropertyNI(*v.BooleanValue)
   103  	case v.StringValue != nil && propertyIsBytes(p.GetMeaning()):
   104  		prop = MkPropertyNI([]byte(*v.StringValue))
   105  	case v.StringValue != nil:
   106  		prop = MkPropertyNI(*v.StringValue)
   107  	case v.DoubleValue != nil:
   108  		prop = MkPropertyNI(*v.DoubleValue)
   109  	case v.Pointvalue != nil || v.Referencevalue != nil || v.Uservalue != nil:
   110  		err = fmt.Errorf("unsupported LSP property: %s", p)
   111  	default:
   112  		prop = MkPropertyNI(nil)
   113  	}
   114  
   115  	return
   116  }
   117  
   118  func propertyIsBytes(m pb.Property_Meaning) bool {
   119  	return m == pb.Property_BLOB || m == pb.Property_BYTESTRING || m == pb.Property_ENTITY_PROTO
   120  }