mirror of
https://github.com/miniflux/v2.git
synced 2025-08-16 18:01:37 +00:00
Update vendor dependencies
This commit is contained in:
parent
34a3fe426b
commit
459bb4531f
747 changed files with 89857 additions and 39711 deletions
5
vendor/google.golang.org/appengine/datastore/datastore.go
generated
vendored
5
vendor/google.golang.org/appengine/datastore/datastore.go
generated
vendored
|
@ -43,7 +43,8 @@ func (e *ErrFieldMismatch) Error() string {
|
|||
e.FieldName, e.StructType, e.Reason)
|
||||
}
|
||||
|
||||
// protoToKey converts a Reference proto to a *Key.
|
||||
// protoToKey converts a Reference proto to a *Key. If the key is invalid,
|
||||
// protoToKey will return the invalid key along with ErrInvalidKey.
|
||||
func protoToKey(r *pb.Reference) (k *Key, err error) {
|
||||
appID := r.GetApp()
|
||||
namespace := r.GetNameSpace()
|
||||
|
@ -57,7 +58,7 @@ func protoToKey(r *pb.Reference) (k *Key, err error) {
|
|||
namespace: namespace,
|
||||
}
|
||||
if !k.valid() {
|
||||
return nil, ErrInvalidKey
|
||||
return k, ErrInvalidKey
|
||||
}
|
||||
}
|
||||
return
|
||||
|
|
247
vendor/google.golang.org/appengine/datastore/datastore_test.go
generated
vendored
247
vendor/google.golang.org/appengine/datastore/datastore_test.go
generated
vendored
|
@ -10,6 +10,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -70,6 +71,8 @@ var (
|
|||
testGeoPt0 = appengine.GeoPoint{Lat: 1.2, Lng: 3.4}
|
||||
testGeoPt1 = appengine.GeoPoint{Lat: 5, Lng: 10}
|
||||
testBadGeoPt = appengine.GeoPoint{Lat: 1000, Lng: 34}
|
||||
|
||||
now = time.Unix(1e9, 0).UTC()
|
||||
)
|
||||
|
||||
type B0 struct {
|
||||
|
@ -133,6 +136,39 @@ type K1 struct {
|
|||
K []*Key
|
||||
}
|
||||
|
||||
type S struct {
|
||||
St string
|
||||
}
|
||||
|
||||
type NoOmit struct {
|
||||
A string
|
||||
B int `datastore:"Bb"`
|
||||
C bool `datastore:",noindex"`
|
||||
}
|
||||
|
||||
type OmitAll struct {
|
||||
A string `datastore:",omitempty"`
|
||||
B int `datastore:"Bb,omitempty"`
|
||||
C bool `datastore:",omitempty,noindex"`
|
||||
D time.Time `datastore:",omitempty"`
|
||||
F []int `datastore:",omitempty"`
|
||||
}
|
||||
|
||||
type Omit struct {
|
||||
A string `datastore:",omitempty"`
|
||||
B int `datastore:"Bb,omitempty"`
|
||||
C bool `datastore:",omitempty,noindex"`
|
||||
D time.Time `datastore:",omitempty"`
|
||||
F []int `datastore:",omitempty"`
|
||||
S `datastore:",omitempty"`
|
||||
}
|
||||
|
||||
type NoOmits struct {
|
||||
No []NoOmit `datastore:",omitempty"`
|
||||
S `datastore:",omitempty"`
|
||||
Ss S `datastore:",omitempty"`
|
||||
}
|
||||
|
||||
type N0 struct {
|
||||
X0
|
||||
Nonymous X0
|
||||
|
@ -306,10 +342,27 @@ type Doubler struct {
|
|||
B bool
|
||||
}
|
||||
|
||||
type Repeat struct {
|
||||
Key string
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type Repeated struct {
|
||||
Repeats []Repeat
|
||||
}
|
||||
|
||||
func (d *Doubler) Load(props []Property) error {
|
||||
return LoadStruct(d, props)
|
||||
}
|
||||
|
||||
type EmbeddedTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
type SpecialTime struct {
|
||||
MyTime EmbeddedTime
|
||||
}
|
||||
|
||||
func (d *Doubler) Save() ([]Property, error) {
|
||||
// Save the default Property slice to an in-memory buffer (a PropertyList).
|
||||
props, err := SaveStruct(d)
|
||||
|
@ -475,6 +528,81 @@ var testCases = []testCase{
|
|||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"omit empty, all",
|
||||
&OmitAll{},
|
||||
new(PropertyList),
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"omit empty",
|
||||
&Omit{},
|
||||
&PropertyList{
|
||||
Property{Name: "St", Value: "", NoIndex: false, Multiple: false},
|
||||
},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"omit empty, fields populated",
|
||||
&Omit{
|
||||
A: "a",
|
||||
B: 10,
|
||||
C: true,
|
||||
D: now,
|
||||
F: []int{11},
|
||||
},
|
||||
&PropertyList{
|
||||
Property{Name: "A", Value: "a", NoIndex: false, Multiple: false},
|
||||
Property{Name: "Bb", Value: int64(10), NoIndex: false, Multiple: false},
|
||||
Property{Name: "C", Value: true, NoIndex: true, Multiple: false},
|
||||
Property{Name: "D", Value: now, NoIndex: false, Multiple: false},
|
||||
Property{Name: "F", Value: int64(11), NoIndex: false, Multiple: true},
|
||||
Property{Name: "St", Value: "", NoIndex: false, Multiple: false},
|
||||
},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"omit empty, fields populated",
|
||||
&Omit{
|
||||
A: "a",
|
||||
B: 10,
|
||||
C: true,
|
||||
D: now,
|
||||
F: []int{11},
|
||||
S: S{St: "string"},
|
||||
},
|
||||
&PropertyList{
|
||||
Property{Name: "A", Value: "a", NoIndex: false, Multiple: false},
|
||||
Property{Name: "Bb", Value: int64(10), NoIndex: false, Multiple: false},
|
||||
Property{Name: "C", Value: true, NoIndex: true, Multiple: false},
|
||||
Property{Name: "D", Value: now, NoIndex: false, Multiple: false},
|
||||
Property{Name: "F", Value: int64(11), NoIndex: false, Multiple: true},
|
||||
Property{Name: "St", Value: "string", NoIndex: false, Multiple: false},
|
||||
},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"omit empty does not propagate",
|
||||
&NoOmits{
|
||||
No: []NoOmit{
|
||||
NoOmit{},
|
||||
},
|
||||
S: S{},
|
||||
Ss: S{},
|
||||
},
|
||||
&PropertyList{
|
||||
Property{Name: "No.A", Value: "", NoIndex: false, Multiple: true},
|
||||
Property{Name: "No.Bb", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "No.C", Value: false, NoIndex: true, Multiple: true},
|
||||
Property{Name: "Ss.St", Value: "", NoIndex: false, Multiple: false},
|
||||
Property{Name: "St", Value: "", NoIndex: false, Multiple: false}},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"key",
|
||||
&K0{K: testKey1a},
|
||||
|
@ -624,6 +752,35 @@ var testCases = []testCase{
|
|||
"too many indexed properties",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"slice of slices of bytes",
|
||||
&Repeated{
|
||||
Repeats: []Repeat{
|
||||
{
|
||||
Key: "key 1",
|
||||
Value: []byte("value 1"),
|
||||
},
|
||||
{
|
||||
Key: "key 2",
|
||||
Value: []byte("value 2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
&Repeated{
|
||||
Repeats: []Repeat{
|
||||
{
|
||||
Key: "key 1",
|
||||
Value: []byte("value 1"),
|
||||
},
|
||||
{
|
||||
Key: "key 2",
|
||||
Value: []byte("value 2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"long blob",
|
||||
&B0{B: makeUint8Slice(maxIndexedProperties + 1)},
|
||||
|
@ -727,16 +884,16 @@ var testCases = []testCase{
|
|||
// A and B are renamed to a and b; A and C are noindex, I is ignored.
|
||||
// Indexed properties are loaded before raw properties. Thus, the
|
||||
// result is: b, b, b, D, E, a, c.
|
||||
Property{Name: "C", Value: int64(3), NoIndex: true, Multiple: false},
|
||||
Property{Name: "D", Value: int64(4), NoIndex: false, Multiple: false},
|
||||
Property{Name: "E", Value: int64(5), NoIndex: false, Multiple: false},
|
||||
Property{Name: "F", Value: int64(6), NoIndex: true, Multiple: false},
|
||||
Property{Name: "G", Value: int64(7), NoIndex: false, Multiple: false},
|
||||
Property{Name: "J", Value: int64(9), NoIndex: true, Multiple: false},
|
||||
Property{Name: "a", Value: int64(1), NoIndex: true, Multiple: false},
|
||||
Property{Name: "b", Value: int64(21), NoIndex: false, Multiple: true},
|
||||
Property{Name: "b", Value: int64(22), NoIndex: false, Multiple: true},
|
||||
Property{Name: "b", Value: int64(23), NoIndex: false, Multiple: true},
|
||||
Property{Name: "D", Value: int64(4), NoIndex: false, Multiple: false},
|
||||
Property{Name: "E", Value: int64(5), NoIndex: false, Multiple: false},
|
||||
Property{Name: "G", Value: int64(7), NoIndex: false, Multiple: false},
|
||||
Property{Name: "a", Value: int64(1), NoIndex: true, Multiple: false},
|
||||
Property{Name: "C", Value: int64(3), NoIndex: true, Multiple: false},
|
||||
Property{Name: "F", Value: int64(6), NoIndex: true, Multiple: false},
|
||||
Property{Name: "J", Value: int64(9), NoIndex: true, Multiple: false},
|
||||
},
|
||||
"",
|
||||
"",
|
||||
|
@ -783,8 +940,8 @@ var testCases = []testCase{
|
|||
"save struct load props",
|
||||
&X0{S: "s", I: 1},
|
||||
&PropertyList{
|
||||
Property{Name: "S", Value: "s", NoIndex: false, Multiple: false},
|
||||
Property{Name: "I", Value: int64(1), NoIndex: false, Multiple: false},
|
||||
Property{Name: "S", Value: "s", NoIndex: false, Multiple: false},
|
||||
},
|
||||
"",
|
||||
"",
|
||||
|
@ -845,10 +1002,10 @@ var testCases = []testCase{
|
|||
&PropertyList{
|
||||
Property{Name: "A", Value: int64(1), NoIndex: false, Multiple: false},
|
||||
Property{Name: "I.W", Value: int64(10), NoIndex: false, Multiple: true},
|
||||
Property{Name: "I.X", Value: "ten", NoIndex: false, Multiple: true},
|
||||
Property{Name: "I.W", Value: int64(20), NoIndex: false, Multiple: true},
|
||||
Property{Name: "I.X", Value: "twenty", NoIndex: false, Multiple: true},
|
||||
Property{Name: "I.W", Value: int64(30), NoIndex: false, Multiple: true},
|
||||
Property{Name: "I.X", Value: "ten", NoIndex: false, Multiple: true},
|
||||
Property{Name: "I.X", Value: "twenty", NoIndex: false, Multiple: true},
|
||||
Property{Name: "I.X", Value: "thirty", NoIndex: false, Multiple: true},
|
||||
Property{Name: "J.Y", Value: float64(3.14), NoIndex: false, Multiple: false},
|
||||
Property{Name: "Z", Value: true, NoIndex: false, Multiple: false},
|
||||
|
@ -1115,33 +1272,33 @@ var testCases = []testCase{
|
|||
},
|
||||
},
|
||||
&PropertyList{
|
||||
Property{Name: "red.S", Value: "rouge", NoIndex: false, Multiple: false},
|
||||
Property{Name: "red.I", Value: int64(0), NoIndex: false, Multiple: false},
|
||||
Property{Name: "red.Nonymous.S", Value: "rosso0", NoIndex: false, Multiple: true},
|
||||
Property{Name: "red.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "red.Nonymous.S", Value: "rosso1", NoIndex: false, Multiple: true},
|
||||
Property{Name: "red.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "red.Other", Value: "", NoIndex: false, Multiple: false},
|
||||
Property{Name: "green.S", Value: "vert", NoIndex: false, Multiple: false},
|
||||
Property{Name: "green.I", Value: int64(0), NoIndex: false, Multiple: false},
|
||||
Property{Name: "green.Nonymous.S", Value: "verde0", NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.S", Value: "verde1", NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.S", Value: "verde2", NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Other", Value: "", NoIndex: false, Multiple: false},
|
||||
Property{Name: "Blue.S", Value: "bleu", NoIndex: false, Multiple: false},
|
||||
Property{Name: "Blue.I", Value: int64(0), NoIndex: false, Multiple: false},
|
||||
Property{Name: "Blue.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.S", Value: "blu0", NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.S", Value: "blu1", NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.S", Value: "blu2", NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.S", Value: "blu3", NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "Blue.Other", Value: "", NoIndex: false, Multiple: false},
|
||||
Property{Name: "Blue.S", Value: "bleu", NoIndex: false, Multiple: false},
|
||||
Property{Name: "green.I", Value: int64(0), NoIndex: false, Multiple: false},
|
||||
Property{Name: "green.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.S", Value: "verde0", NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.S", Value: "verde1", NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Nonymous.S", Value: "verde2", NoIndex: false, Multiple: true},
|
||||
Property{Name: "green.Other", Value: "", NoIndex: false, Multiple: false},
|
||||
Property{Name: "green.S", Value: "vert", NoIndex: false, Multiple: false},
|
||||
Property{Name: "red.I", Value: int64(0), NoIndex: false, Multiple: false},
|
||||
Property{Name: "red.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "red.Nonymous.I", Value: int64(0), NoIndex: false, Multiple: true},
|
||||
Property{Name: "red.Nonymous.S", Value: "rosso0", NoIndex: false, Multiple: true},
|
||||
Property{Name: "red.Nonymous.S", Value: "rosso1", NoIndex: false, Multiple: true},
|
||||
Property{Name: "red.Other", Value: "", NoIndex: false, Multiple: false},
|
||||
Property{Name: "red.S", Value: "rouge", NoIndex: false, Multiple: false},
|
||||
},
|
||||
"",
|
||||
"",
|
||||
|
@ -1196,10 +1353,10 @@ var testCases = []testCase{
|
|||
}
|
||||
}{},
|
||||
&PropertyList{
|
||||
Property{Name: "B.Y", Value: "", NoIndex: false, Multiple: false},
|
||||
Property{Name: "A.X", Value: "", NoIndex: true, Multiple: false},
|
||||
Property{Name: "A.Y", Value: "", NoIndex: true, Multiple: false},
|
||||
Property{Name: "B.X", Value: "", NoIndex: true, Multiple: false},
|
||||
Property{Name: "B.Y", Value: "", NoIndex: false, Multiple: false},
|
||||
},
|
||||
"",
|
||||
"",
|
||||
|
@ -1272,6 +1429,22 @@ var testCases = []testCase{
|
|||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"embedded time field",
|
||||
&SpecialTime{MyTime: EmbeddedTime{now}},
|
||||
&SpecialTime{MyTime: EmbeddedTime{now}},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
"embedded time load",
|
||||
&PropertyList{
|
||||
Property{Name: "MyTime.", Value: now, NoIndex: false, Multiple: false},
|
||||
},
|
||||
&SpecialTime{MyTime: EmbeddedTime{now}},
|
||||
"",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
// checkErr returns the empty string if either both want and err are zero,
|
||||
|
@ -1309,6 +1482,10 @@ func TestRoundTrip(t *testing.T) {
|
|||
t.Errorf("%s: load: %s", tc.desc, s)
|
||||
continue
|
||||
}
|
||||
if pl, ok := got.(*PropertyList); ok {
|
||||
// Sort by name to make sure we have a deterministic order.
|
||||
sort.Stable(byName(*pl))
|
||||
}
|
||||
equal := false
|
||||
if gotT, ok := got.(*T); ok {
|
||||
// Round tripping a time.Time can result in a different time.Location: Local instead of UTC.
|
||||
|
@ -1324,6 +1501,12 @@ func TestRoundTrip(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type byName PropertyList
|
||||
|
||||
func (s byName) Len() int { return len(s) }
|
||||
func (s byName) Less(i, j int) bool { return s[i].Name < s[j].Name }
|
||||
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func TestQueryConstruction(t *testing.T) {
|
||||
tests := []struct {
|
||||
q, exp *Query
|
||||
|
|
40
vendor/google.golang.org/appengine/datastore/doc.go
generated
vendored
40
vendor/google.golang.org/appengine/datastore/doc.go
generated
vendored
|
@ -87,7 +87,7 @@ behavior for struct pointers. Struct pointers are more strongly typed and are
|
|||
easier to use; PropertyLoadSavers are more flexible.
|
||||
|
||||
The actual types passed do not have to match between Get and Put calls or even
|
||||
across different App Engine requests. It is valid to put a *PropertyList and
|
||||
across different calls to datastore. It is valid to put a *PropertyList and
|
||||
get that same entity as a *myStruct, or put a *myStruct0 and get a *myStruct1.
|
||||
Conceptually, any entity is saved as a sequence of properties, and is loaded
|
||||
into the destination value on a property-by-property basis. When loading into
|
||||
|
@ -97,18 +97,28 @@ caller whether this error is fatal, recoverable or ignorable.
|
|||
|
||||
By default, for struct pointers, all properties are potentially indexed, and
|
||||
the property name is the same as the field name (and hence must start with an
|
||||
upper case letter). Fields may have a `datastore:"name,options"` tag. The tag
|
||||
name is the property name, which must be one or more valid Go identifiers
|
||||
joined by ".", but may start with a lower case letter. An empty tag name means
|
||||
to just use the field name. A "-" tag name means that the datastore will
|
||||
ignore that field. If options is "noindex" then the field will not be indexed.
|
||||
If the options is "" then the comma may be omitted. There are no other
|
||||
recognized options.
|
||||
upper case letter).
|
||||
|
||||
Fields (except for []byte) are indexed by default. Strings longer than 1500
|
||||
bytes cannot be indexed; fields used to store long strings should be
|
||||
tagged with "noindex". Similarly, ByteStrings longer than 1500 bytes cannot be
|
||||
indexed.
|
||||
Fields may have a `datastore:"name,options"` tag. The tag name is the
|
||||
property name, which must be one or more valid Go identifiers joined by ".",
|
||||
but may start with a lower case letter. An empty tag name means to just use the
|
||||
field name. A "-" tag name means that the datastore will ignore that field.
|
||||
|
||||
The only valid options are "omitempty" and "noindex".
|
||||
|
||||
If the options include "omitempty" and the value of the field is empty, then the field will be omitted on Save.
|
||||
The empty values are false, 0, any nil interface value, and any array, slice, map, or string of length zero.
|
||||
Struct field values will never be empty.
|
||||
|
||||
If options include "noindex" then the field will not be indexed. All fields are indexed
|
||||
by default. Strings or byte slices longer than 1500 bytes cannot be indexed;
|
||||
fields used to store long strings and byte slices must be tagged with "noindex"
|
||||
or they will cause Put operations to fail.
|
||||
|
||||
To use multiple options together, separate them by a comma.
|
||||
The order does not matter.
|
||||
|
||||
If the options is "" then the comma may be omitted.
|
||||
|
||||
Example code:
|
||||
|
||||
|
@ -200,7 +210,7 @@ Example code:
|
|||
func (x *CustomPropsExample) Save() ([]datastore.Property, error) {
|
||||
// Validate the Sum field.
|
||||
if x.Sum != x.I + x.J {
|
||||
return errors.New("CustomPropsExample has inconsistent sum")
|
||||
return nil, errors.New("CustomPropsExample has inconsistent sum")
|
||||
}
|
||||
// Save I and J as usual. The code below is equivalent to calling
|
||||
// "return datastore.SaveStruct(x)", but is done manually for
|
||||
|
@ -214,7 +224,7 @@ Example code:
|
|||
Name: "J",
|
||||
Value: int64(x.J),
|
||||
},
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
The *PropertyList type implements PropertyLoadSaver, and can therefore hold an
|
||||
|
@ -343,7 +353,7 @@ Example code:
|
|||
continue
|
||||
}
|
||||
for p, rep := range props {
|
||||
fmt.Fprintf(w, "\t-%s (%s)\n", p, strings.Join(", ", rep))
|
||||
fmt.Fprintf(w, "\t-%s (%s)\n", p, strings.Join(rep, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
87
vendor/google.golang.org/appengine/datastore/key.go
generated
vendored
87
vendor/google.golang.org/appengine/datastore/key.go
generated
vendored
|
@ -20,6 +20,26 @@ import (
|
|||
pb "google.golang.org/appengine/internal/datastore"
|
||||
)
|
||||
|
||||
type KeyRangeCollisionError struct {
|
||||
start int64
|
||||
end int64
|
||||
}
|
||||
|
||||
func (e *KeyRangeCollisionError) Error() string {
|
||||
return fmt.Sprintf("datastore: Collision when attempting to allocate range [%d, %d]",
|
||||
e.start, e.end)
|
||||
}
|
||||
|
||||
type KeyRangeContentionError struct {
|
||||
start int64
|
||||
end int64
|
||||
}
|
||||
|
||||
func (e *KeyRangeContentionError) Error() string {
|
||||
return fmt.Sprintf("datastore: Contention when attempting to allocate range [%d, %d]",
|
||||
e.start, e.end)
|
||||
}
|
||||
|
||||
// Key represents the datastore key for a stored entity, and is immutable.
|
||||
type Key struct {
|
||||
kind string
|
||||
|
@ -307,3 +327,70 @@ func AllocateIDs(c context.Context, kind string, parent *Key, n int) (low, high
|
|||
}
|
||||
return low, high, nil
|
||||
}
|
||||
|
||||
// AllocateIDRange allocates a range of IDs with specific endpoints.
|
||||
// The range is inclusive at both the low and high end. Once these IDs have been
|
||||
// allocated, you can manually assign them to newly created entities.
|
||||
//
|
||||
// The Datastore's automatic ID allocator never assigns a key that has already
|
||||
// been allocated (either through automatic ID allocation or through an explicit
|
||||
// AllocateIDs call). As a result, entities written to the given key range will
|
||||
// never be overwritten. However, writing entities with manually assigned keys in
|
||||
// this range may overwrite existing entities (or new entities written by a separate
|
||||
// request), depending on the error returned.
|
||||
//
|
||||
// Use this only if you have an existing numeric ID range that you want to reserve
|
||||
// (for example, bulk loading entities that already have IDs). If you don't care
|
||||
// about which IDs you receive, use AllocateIDs instead.
|
||||
//
|
||||
// AllocateIDRange returns nil if the range is successfully allocated. If one or more
|
||||
// entities with an ID in the given range already exist, it returns a KeyRangeCollisionError.
|
||||
// If the Datastore has already cached IDs in this range (e.g. from a previous call to
|
||||
// AllocateIDRange), it returns a KeyRangeContentionError. Errors of other types indicate
|
||||
// problems with arguments or an error returned directly from the Datastore.
|
||||
func AllocateIDRange(c context.Context, kind string, parent *Key, start, end int64) (err error) {
|
||||
if kind == "" {
|
||||
return errors.New("datastore: AllocateIDRange given an empty kind")
|
||||
}
|
||||
|
||||
if start < 1 || end < 1 {
|
||||
return errors.New("datastore: AllocateIDRange start and end must both be greater than 0")
|
||||
}
|
||||
|
||||
if start > end {
|
||||
return errors.New("datastore: AllocateIDRange start must be before end")
|
||||
}
|
||||
|
||||
req := &pb.AllocateIdsRequest{
|
||||
ModelKey: keyToProto("", NewIncompleteKey(c, kind, parent)),
|
||||
Max: proto.Int64(end),
|
||||
}
|
||||
res := &pb.AllocateIdsResponse{}
|
||||
if err := internal.Call(c, "datastore_v3", "AllocateIds", req, res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check for collisions, i.e. existing entities with IDs in this range.
|
||||
// We could do this before the allocation, but we'd still have to do it
|
||||
// afterward as well to catch the race condition where an entity is inserted
|
||||
// after that initial check but before the allocation. Skip the up-front check
|
||||
// and just do it once.
|
||||
q := NewQuery(kind).Filter("__key__ >=", NewKey(c, kind, "", start, parent)).
|
||||
Filter("__key__ <=", NewKey(c, kind, "", end, parent)).KeysOnly().Limit(1)
|
||||
|
||||
keys, err := q.GetAll(c, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(keys) != 0 {
|
||||
return &KeyRangeCollisionError{start: start, end: end}
|
||||
}
|
||||
|
||||
// Check for a race condition, i.e. cases where the datastore may have
|
||||
// cached ID batches that contain IDs in this range.
|
||||
if start < res.GetStart() {
|
||||
return &KeyRangeContentionError{start: start, end: end}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
175
vendor/google.golang.org/appengine/datastore/load.go
generated
vendored
175
vendor/google.golang.org/appengine/datastore/load.go
generated
vendored
|
@ -7,8 +7,10 @@ package datastore
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/appengine"
|
||||
pb "google.golang.org/appengine/internal/datastore"
|
||||
)
|
||||
|
@ -19,13 +21,15 @@ var (
|
|||
typeOfByteString = reflect.TypeOf(ByteString(nil))
|
||||
typeOfGeoPoint = reflect.TypeOf(appengine.GeoPoint{})
|
||||
typeOfTime = reflect.TypeOf(time.Time{})
|
||||
typeOfKeyPtr = reflect.TypeOf(&Key{})
|
||||
typeOfEntityPtr = reflect.TypeOf(&Entity{})
|
||||
)
|
||||
|
||||
// typeMismatchReason returns a string explaining why the property p could not
|
||||
// be stored in an entity field of type v.Type().
|
||||
func typeMismatchReason(p Property, v reflect.Value) string {
|
||||
func typeMismatchReason(pValue interface{}, v reflect.Value) string {
|
||||
entityType := "empty"
|
||||
switch p.Value.(type) {
|
||||
switch pValue.(type) {
|
||||
case int64:
|
||||
entityType = "int"
|
||||
case bool:
|
||||
|
@ -58,13 +62,41 @@ type propertyLoader struct {
|
|||
|
||||
func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p Property, requireSlice bool) string {
|
||||
var v reflect.Value
|
||||
// Traverse a struct's struct-typed fields.
|
||||
for name := p.Name; ; {
|
||||
decoder, ok := codec.byName[name]
|
||||
var sliceIndex int
|
||||
|
||||
name := p.Name
|
||||
|
||||
// If name ends with a '.', the last field is anonymous.
|
||||
// In this case, strings.Split will give us "" as the
|
||||
// last element of our fields slice, which will match the ""
|
||||
// field name in the substruct codec.
|
||||
fields := strings.Split(name, ".")
|
||||
|
||||
for len(fields) > 0 {
|
||||
var decoder fieldCodec
|
||||
var ok bool
|
||||
|
||||
// Cut off the last field (delimited by ".") and find its parent
|
||||
// in the codec.
|
||||
// eg. for name "A.B.C.D", split off "A.B.C" and try to
|
||||
// find a field in the codec with this name.
|
||||
// Loop again with "A.B", etc.
|
||||
for i := len(fields); i > 0; i-- {
|
||||
parent := strings.Join(fields[:i], ".")
|
||||
decoder, ok = codec.fields[parent]
|
||||
if ok {
|
||||
fields = fields[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If we never found a matching field in the codec, return
|
||||
// error message.
|
||||
if !ok {
|
||||
return "no such struct field"
|
||||
}
|
||||
v = structValue.Field(decoder.index)
|
||||
|
||||
v = initField(structValue, decoder.path)
|
||||
if !v.IsValid() {
|
||||
return "no such struct field"
|
||||
}
|
||||
|
@ -72,27 +104,23 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
|
|||
return "cannot set struct field"
|
||||
}
|
||||
|
||||
if decoder.substructCodec == nil {
|
||||
break
|
||||
if decoder.structCodec != nil {
|
||||
codec = decoder.structCodec
|
||||
structValue = v
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Slice {
|
||||
if v.Kind() == reflect.Slice && v.Type() != typeOfByteSlice {
|
||||
if l.m == nil {
|
||||
l.m = make(map[string]int)
|
||||
}
|
||||
index := l.m[p.Name]
|
||||
l.m[p.Name] = index + 1
|
||||
for v.Len() <= index {
|
||||
sliceIndex = l.m[p.Name]
|
||||
l.m[p.Name] = sliceIndex + 1
|
||||
for v.Len() <= sliceIndex {
|
||||
v.Set(reflect.Append(v, reflect.New(v.Type().Elem()).Elem()))
|
||||
}
|
||||
structValue = v.Index(index)
|
||||
structValue = v.Index(sliceIndex)
|
||||
requireSlice = false
|
||||
} else {
|
||||
structValue = v
|
||||
}
|
||||
// Strip the "I." from "I.X".
|
||||
name = name[len(codec.byIndex[decoder.index].name):]
|
||||
codec = decoder.substructCodec
|
||||
}
|
||||
|
||||
var slice reflect.Value
|
||||
|
@ -119,6 +147,8 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
|
|||
meaning = pb.Property_GEORSS_POINT
|
||||
case typeOfTime:
|
||||
meaning = pb.Property_GD_WHEN
|
||||
case typeOfEntityPtr:
|
||||
meaning = pb.Property_ENTITY_PROTO
|
||||
}
|
||||
var err error
|
||||
pValue, err = propValue(iv.value, meaning)
|
||||
|
@ -127,11 +157,28 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
|
|||
}
|
||||
}
|
||||
|
||||
if errReason := setVal(v, pValue); errReason != "" {
|
||||
// Set the slice back to its zero value.
|
||||
if slice.IsValid() {
|
||||
slice.Set(reflect.Zero(slice.Type()))
|
||||
}
|
||||
return errReason
|
||||
}
|
||||
|
||||
if slice.IsValid() {
|
||||
slice.Index(sliceIndex).Set(v)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// setVal sets v to the value pValue.
|
||||
func setVal(v reflect.Value, pValue interface{}) string {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x, ok := pValue.(int64)
|
||||
if !ok && pValue != nil {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
if v.OverflowInt(x) {
|
||||
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
|
||||
|
@ -140,7 +187,7 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
|
|||
case reflect.Bool:
|
||||
x, ok := pValue.(bool)
|
||||
if !ok && pValue != nil {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
v.SetBool(x)
|
||||
case reflect.String:
|
||||
|
@ -153,13 +200,13 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
|
|||
v.SetString(x)
|
||||
default:
|
||||
if pValue != nil {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
x, ok := pValue.(float64)
|
||||
if !ok && pValue != nil {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
if v.OverflowFloat(x) {
|
||||
return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type())
|
||||
|
@ -168,10 +215,10 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
|
|||
case reflect.Ptr:
|
||||
x, ok := pValue.(*Key)
|
||||
if !ok && pValue != nil {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
if _, ok := v.Interface().(*Key); !ok {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
v.Set(reflect.ValueOf(x))
|
||||
case reflect.Struct:
|
||||
|
@ -179,17 +226,38 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
|
|||
case typeOfTime:
|
||||
x, ok := pValue.(time.Time)
|
||||
if !ok && pValue != nil {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
v.Set(reflect.ValueOf(x))
|
||||
case typeOfGeoPoint:
|
||||
x, ok := pValue.(appengine.GeoPoint)
|
||||
if !ok && pValue != nil {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
v.Set(reflect.ValueOf(x))
|
||||
default:
|
||||
return typeMismatchReason(p, v)
|
||||
ent, ok := pValue.(*Entity)
|
||||
if !ok {
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
|
||||
// Recursively load nested struct
|
||||
pls, err := newStructPLS(v.Addr().Interface())
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// if ent has a Key value and our struct has a Key field,
|
||||
// load the Entity's Key value into the Key field on the struct.
|
||||
if ent.Key != nil && pls.codec.keyField != -1 {
|
||||
|
||||
pls.v.Field(pls.codec.keyField).Set(reflect.ValueOf(ent.Key))
|
||||
}
|
||||
|
||||
err = pls.Load(ent.Properties)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
x, ok := pValue.([]byte)
|
||||
|
@ -199,31 +267,44 @@ func (l *propertyLoader) load(codec *structCodec, structValue reflect.Value, p P
|
|||
}
|
||||
}
|
||||
if !ok && pValue != nil {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
if v.Type().Elem().Kind() != reflect.Uint8 {
|
||||
return typeMismatchReason(p, v)
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
v.SetBytes(x)
|
||||
default:
|
||||
return typeMismatchReason(p, v)
|
||||
}
|
||||
if slice.IsValid() {
|
||||
slice.Set(reflect.Append(slice, v))
|
||||
return typeMismatchReason(pValue, v)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// initField is similar to reflect's Value.FieldByIndex, in that it
|
||||
// returns the nested struct field corresponding to index, but it
|
||||
// initialises any nil pointers encountered when traversing the structure.
|
||||
func initField(val reflect.Value, index []int) reflect.Value {
|
||||
for _, i := range index[:len(index)-1] {
|
||||
val = val.Field(i)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
}
|
||||
return val.Field(index[len(index)-1])
|
||||
}
|
||||
|
||||
// loadEntity loads an EntityProto into PropertyLoadSaver or struct pointer.
|
||||
func loadEntity(dst interface{}, src *pb.EntityProto) (err error) {
|
||||
props, err := protoToProperties(src)
|
||||
ent, err := protoToEntity(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e, ok := dst.(PropertyLoadSaver); ok {
|
||||
return e.Load(props)
|
||||
return e.Load(ent.Properties)
|
||||
}
|
||||
return LoadStruct(dst, props)
|
||||
return LoadStruct(dst, ent.Properties)
|
||||
}
|
||||
|
||||
func (s structPLS) Load(props []Property) error {
|
||||
|
@ -247,9 +328,9 @@ func (s structPLS) Load(props []Property) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func protoToProperties(src *pb.EntityProto) ([]Property, error) {
|
||||
func protoToEntity(src *pb.EntityProto) (*Entity, error) {
|
||||
props, rawProps := src.Property, src.RawProperty
|
||||
out := make([]Property, 0, len(props)+len(rawProps))
|
||||
outProps := make([]Property, 0, len(props)+len(rawProps))
|
||||
for {
|
||||
var (
|
||||
x *pb.Property
|
||||
|
@ -274,14 +355,21 @@ func protoToProperties(src *pb.EntityProto) ([]Property, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
out = append(out, Property{
|
||||
outProps = append(outProps, Property{
|
||||
Name: x.GetName(),
|
||||
Value: value,
|
||||
NoIndex: noIndex,
|
||||
Multiple: x.GetMultiple(),
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
|
||||
var key *Key
|
||||
if src.Key != nil {
|
||||
// Ignore any error, since nested entity values
|
||||
// are allowed to have an invalid key.
|
||||
key, _ = protoToKey(src.Key)
|
||||
}
|
||||
return &Entity{key, outProps}, nil
|
||||
}
|
||||
|
||||
// propValue returns a Go value that combines the raw PropertyValue with a
|
||||
|
@ -303,6 +391,13 @@ func propValue(v *pb.PropertyValue, m pb.Property_Meaning) (interface{}, error)
|
|||
return appengine.BlobKey(*v.StringValue), nil
|
||||
} else if m == pb.Property_BYTESTRING {
|
||||
return ByteString(*v.StringValue), nil
|
||||
} else if m == pb.Property_ENTITY_PROTO {
|
||||
var ent pb.EntityProto
|
||||
err := proto.Unmarshal([]byte(*v.StringValue), &ent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return protoToEntity(&ent)
|
||||
} else {
|
||||
return *v.StringValue, nil
|
||||
}
|
||||
|
|
656
vendor/google.golang.org/appengine/datastore/load_test.go
generated
vendored
Normal file
656
vendor/google.golang.org/appengine/datastore/load_test.go
generated
vendored
Normal file
|
@ -0,0 +1,656 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
pb "google.golang.org/appengine/internal/datastore"
|
||||
)
|
||||
|
||||
type Simple struct {
|
||||
I int64
|
||||
}
|
||||
|
||||
type SimpleWithTag struct {
|
||||
I int64 `datastore:"II"`
|
||||
}
|
||||
|
||||
type NestedSimpleWithTag struct {
|
||||
A SimpleWithTag `datastore:"AA"`
|
||||
}
|
||||
|
||||
type NestedSliceOfSimple struct {
|
||||
A []Simple
|
||||
}
|
||||
|
||||
type SimpleTwoFields struct {
|
||||
S string
|
||||
SS string
|
||||
}
|
||||
|
||||
type NestedSimpleAnonymous struct {
|
||||
Simple
|
||||
X string
|
||||
}
|
||||
|
||||
type NestedSimple struct {
|
||||
A Simple
|
||||
I int64
|
||||
}
|
||||
|
||||
type NestedSimple1 struct {
|
||||
A Simple
|
||||
X string
|
||||
}
|
||||
|
||||
type NestedSimple2X struct {
|
||||
AA NestedSimple
|
||||
A SimpleTwoFields
|
||||
S string
|
||||
}
|
||||
|
||||
type BDotB struct {
|
||||
B string `datastore:"B.B"`
|
||||
}
|
||||
|
||||
type ABDotB struct {
|
||||
A BDotB
|
||||
}
|
||||
|
||||
type MultiAnonymous struct {
|
||||
Simple
|
||||
SimpleTwoFields
|
||||
X string
|
||||
}
|
||||
|
||||
var (
|
||||
// these values need to be addressable
|
||||
testString2 = "two"
|
||||
testString3 = "three"
|
||||
testInt64 = int64(2)
|
||||
|
||||
fieldNameI = "I"
|
||||
fieldNameX = "X"
|
||||
fieldNameS = "S"
|
||||
fieldNameSS = "SS"
|
||||
fieldNameADotI = "A.I"
|
||||
fieldNameAADotII = "AA.II"
|
||||
fieldNameADotBDotB = "A.B.B"
|
||||
)
|
||||
|
||||
func TestLoadEntityNestedLegacy(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
src *pb.EntityProto
|
||||
want interface{}
|
||||
}{
|
||||
{
|
||||
"nested",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameX,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameADotI,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedSimple1{
|
||||
A: Simple{I: testInt64},
|
||||
X: testString2,
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested with tag",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameAADotII,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedSimpleWithTag{
|
||||
A: SimpleWithTag{I: testInt64},
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested with anonymous struct field",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameX,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameI,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedSimpleAnonymous{
|
||||
Simple: Simple{I: testInt64},
|
||||
X: testString2,
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested with dotted field tag",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameADotBDotB,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&ABDotB{
|
||||
A: BDotB{
|
||||
B: testString2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested with dotted field tag",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameI,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameS,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameSS,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString3,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameX,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&MultiAnonymous{
|
||||
Simple: Simple{I: testInt64},
|
||||
SimpleTwoFields: SimpleTwoFields{S: "two", SS: "three"},
|
||||
X: "three",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
|
||||
err := loadEntity(dst, tc.src)
|
||||
if err != nil {
|
||||
t.Errorf("loadEntity: %s: %v", tc.desc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.want, dst) {
|
||||
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, dst, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WithKey struct {
|
||||
X string
|
||||
I int64
|
||||
K *Key `datastore:"__key__"`
|
||||
}
|
||||
|
||||
type NestedWithKey struct {
|
||||
N WithKey
|
||||
Y string
|
||||
}
|
||||
|
||||
var (
|
||||
incompleteKey = newKey("", nil)
|
||||
invalidKey = newKey("s", incompleteKey)
|
||||
|
||||
// these values need to be addressable
|
||||
fieldNameA = "A"
|
||||
fieldNameK = "K"
|
||||
fieldNameN = "N"
|
||||
fieldNameY = "Y"
|
||||
fieldNameAA = "AA"
|
||||
fieldNameII = "II"
|
||||
fieldNameBDotB = "B.B"
|
||||
|
||||
entityProtoMeaning = pb.Property_ENTITY_PROTO
|
||||
|
||||
TRUE = true
|
||||
FALSE = false
|
||||
)
|
||||
|
||||
var (
|
||||
simpleEntityProto, nestedSimpleEntityProto,
|
||||
simpleTwoFieldsEntityProto, simpleWithTagEntityProto,
|
||||
bDotBEntityProto, withKeyEntityProto string
|
||||
)
|
||||
|
||||
func init() {
|
||||
// simpleEntityProto corresponds to:
|
||||
// Simple{I: testInt64}
|
||||
simpleEntityProtob, err := proto.Marshal(&pb.EntityProto{
|
||||
Key: keyToProto("", incompleteKey),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameI,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
},
|
||||
EntityGroup: &pb.Path{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
simpleEntityProto = string(simpleEntityProtob)
|
||||
|
||||
// nestedSimpleEntityProto corresponds to:
|
||||
// NestedSimple{
|
||||
// A: Simple{I: testInt64},
|
||||
// I: testInt64,
|
||||
// }
|
||||
nestedSimpleEntityProtob, err := proto.Marshal(&pb.EntityProto{
|
||||
Key: keyToProto("", incompleteKey),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameA,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &simpleEntityProto,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameI,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
},
|
||||
EntityGroup: &pb.Path{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nestedSimpleEntityProto = string(nestedSimpleEntityProtob)
|
||||
|
||||
// simpleTwoFieldsEntityProto corresponds to:
|
||||
// SimpleTwoFields{S: testString2, SS: testString3}
|
||||
simpleTwoFieldsEntityProtob, err := proto.Marshal(&pb.EntityProto{
|
||||
Key: keyToProto("", incompleteKey),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameS,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameSS,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString3,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
},
|
||||
EntityGroup: &pb.Path{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
simpleTwoFieldsEntityProto = string(simpleTwoFieldsEntityProtob)
|
||||
|
||||
// simpleWithTagEntityProto corresponds to:
|
||||
// SimpleWithTag{I: testInt64}
|
||||
simpleWithTagEntityProtob, err := proto.Marshal(&pb.EntityProto{
|
||||
Key: keyToProto("", incompleteKey),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameII,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
},
|
||||
EntityGroup: &pb.Path{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
simpleWithTagEntityProto = string(simpleWithTagEntityProtob)
|
||||
|
||||
// bDotBEntityProto corresponds to:
|
||||
// BDotB{
|
||||
// B: testString2,
|
||||
// }
|
||||
bDotBEntityProtob, err := proto.Marshal(&pb.EntityProto{
|
||||
Key: keyToProto("", incompleteKey),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameBDotB,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
},
|
||||
EntityGroup: &pb.Path{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bDotBEntityProto = string(bDotBEntityProtob)
|
||||
|
||||
// withKeyEntityProto corresponds to:
|
||||
// WithKey{
|
||||
// X: testString3,
|
||||
// I: testInt64,
|
||||
// K: testKey1a,
|
||||
// }
|
||||
withKeyEntityProtob, err := proto.Marshal(&pb.EntityProto{
|
||||
Key: keyToProto("", testKey1a),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameX,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString3,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameI,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
Multiple: &FALSE,
|
||||
},
|
||||
},
|
||||
EntityGroup: &pb.Path{},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
withKeyEntityProto = string(withKeyEntityProtob)
|
||||
|
||||
}
|
||||
|
||||
func TestLoadEntityNested(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
src *pb.EntityProto
|
||||
want interface{}
|
||||
}{
|
||||
{
|
||||
"nested basic",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameA,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &simpleEntityProto,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameI,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedSimple{
|
||||
A: Simple{I: 2},
|
||||
I: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested with struct tags",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameAA,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &simpleWithTagEntityProto,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedSimpleWithTag{
|
||||
A: SimpleWithTag{I: testInt64},
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested 2x",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameAA,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &nestedSimpleEntityProto,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameA,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &simpleTwoFieldsEntityProto,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameS,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedSimple2X{
|
||||
AA: NestedSimple{
|
||||
A: Simple{I: testInt64},
|
||||
I: testInt64,
|
||||
},
|
||||
A: SimpleTwoFields{S: testString2, SS: testString3},
|
||||
S: testString3,
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested anonymous",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameI,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameX,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedSimpleAnonymous{
|
||||
Simple: Simple{I: testInt64},
|
||||
X: testString2,
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested simple with slice",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameA,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Multiple: &TRUE,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &simpleEntityProto,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameA,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Multiple: &TRUE,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &simpleEntityProto,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedSliceOfSimple{
|
||||
A: []Simple{Simple{I: testInt64}, Simple{I: testInt64}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested with multiple anonymous fields",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameI,
|
||||
Value: &pb.PropertyValue{
|
||||
Int64Value: &testInt64,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameS,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameSS,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString3,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameX,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&MultiAnonymous{
|
||||
Simple: Simple{I: testInt64},
|
||||
SimpleTwoFields: SimpleTwoFields{S: testString2, SS: testString3},
|
||||
X: testString2,
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested with dotted field tag",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameA,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &bDotBEntityProto,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&ABDotB{
|
||||
A: BDotB{
|
||||
B: testString2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"nested entity with key",
|
||||
&pb.EntityProto{
|
||||
Key: keyToProto("some-app-id", testKey0),
|
||||
Property: []*pb.Property{
|
||||
&pb.Property{
|
||||
Name: &fieldNameY,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &testString2,
|
||||
},
|
||||
},
|
||||
&pb.Property{
|
||||
Name: &fieldNameN,
|
||||
Meaning: &entityProtoMeaning,
|
||||
Value: &pb.PropertyValue{
|
||||
StringValue: &withKeyEntityProto,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&NestedWithKey{
|
||||
Y: testString2,
|
||||
N: WithKey{
|
||||
X: testString3,
|
||||
I: testInt64,
|
||||
K: testKey1a,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
|
||||
err := loadEntity(dst, tc.src)
|
||||
if err != nil {
|
||||
t.Errorf("loadEntity: %s: %v", tc.desc, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.want, dst) {
|
||||
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, dst, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
2
vendor/google.golang.org/appengine/datastore/metadata.go
generated
vendored
2
vendor/google.golang.org/appengine/datastore/metadata.go
generated
vendored
|
@ -64,7 +64,7 @@ func KindProperties(ctx context.Context, kind string) (map[string][]string, erro
|
|||
|
||||
propMap := map[string][]string{}
|
||||
props := []struct {
|
||||
Repr []string `datastore:property_representation`
|
||||
Repr []string `datastore:"property_representation"`
|
||||
}{}
|
||||
|
||||
keys, err := q.GetAll(ctx, &props)
|
||||
|
|
118
vendor/google.golang.org/appengine/datastore/prop.go
generated
vendored
118
vendor/google.golang.org/appengine/datastore/prop.go
generated
vendored
|
@ -36,6 +36,7 @@ type Property struct {
|
|||
// - appengine.BlobKey
|
||||
// - appengine.GeoPoint
|
||||
// - []byte (up to 1 megabyte in length)
|
||||
// - *Entity (representing a nested struct)
|
||||
// This set is smaller than the set of valid struct field types that the
|
||||
// datastore can load and save. A Property Value cannot be a slice (apart
|
||||
// from []byte); use multiple Properties instead. Also, a Value's type
|
||||
|
@ -63,6 +64,13 @@ type Property struct {
|
|||
Multiple bool
|
||||
}
|
||||
|
||||
// An Entity is the value type for a nested struct.
|
||||
// This type is only used for a Property's Value.
|
||||
type Entity struct {
|
||||
Key *Key
|
||||
Properties []Property
|
||||
}
|
||||
|
||||
// ByteString is a short byte slice (up to 1500 bytes) that can be indexed.
|
||||
type ByteString []byte
|
||||
|
||||
|
@ -119,25 +127,18 @@ func validPropertyName(name string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// structTag is the parsed `datastore:"name,options"` tag of a struct field.
|
||||
// If a field has no tag, or the tag has an empty name, then the structTag's
|
||||
// name is just the field name. A "-" name means that the datastore ignores
|
||||
// that field.
|
||||
type structTag struct {
|
||||
name string
|
||||
noIndex bool
|
||||
}
|
||||
|
||||
// structCodec describes how to convert a struct to and from a sequence of
|
||||
// properties.
|
||||
type structCodec struct {
|
||||
// byIndex gives the structTag for the i'th field.
|
||||
byIndex []structTag
|
||||
// byName gives the field codec for the structTag with the given name.
|
||||
byName map[string]fieldCodec
|
||||
// fields gives the field codec for the structTag with the given name.
|
||||
fields map[string]fieldCodec
|
||||
// hasSlice is whether a struct or any of its nested or embedded structs
|
||||
// has a slice-typed field (other than []byte).
|
||||
hasSlice bool
|
||||
// keyField is the index of a *Key field with structTag __key__.
|
||||
// This field is not relevant for the top level struct, only for
|
||||
// nested structs.
|
||||
keyField int
|
||||
// complete is whether the structCodec is complete. An incomplete
|
||||
// structCodec may be encountered when walking a recursive struct.
|
||||
complete bool
|
||||
|
@ -146,8 +147,15 @@ type structCodec struct {
|
|||
// fieldCodec is a struct field's index and, if that struct field's type is
|
||||
// itself a struct, that substruct's structCodec.
|
||||
type fieldCodec struct {
|
||||
index int
|
||||
substructCodec *structCodec
|
||||
// path is the index path to the field
|
||||
path []int
|
||||
noIndex bool
|
||||
// omitEmpty indicates that the field should be omitted on save
|
||||
// if empty.
|
||||
omitEmpty bool
|
||||
// structCodec is the codec fot the struct field at index 'path',
|
||||
// or nil if the field is not a struct.
|
||||
structCodec *structCodec
|
||||
}
|
||||
|
||||
// structCodecs collects the structCodecs that have already been calculated.
|
||||
|
@ -171,8 +179,10 @@ func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
|
|||
return c, nil
|
||||
}
|
||||
c = &structCodec{
|
||||
byIndex: make([]structTag, t.NumField()),
|
||||
byName: make(map[string]fieldCodec),
|
||||
fields: make(map[string]fieldCodec),
|
||||
// We initialize keyField to -1 so that the zero-value is not
|
||||
// misinterpreted as index 0.
|
||||
keyField: -1,
|
||||
}
|
||||
|
||||
// Add c to the structCodecs map before we are sure it is good. If t is
|
||||
|
@ -185,22 +195,34 @@ func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
|
|||
}
|
||||
}()
|
||||
|
||||
for i := range c.byIndex {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
// Skip unexported fields.
|
||||
// Note that if f is an anonymous, unexported struct field,
|
||||
// we will promote its fields.
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
tags := strings.Split(f.Tag.Get("datastore"), ",")
|
||||
name := tags[0]
|
||||
opts := make(map[string]bool)
|
||||
for _, t := range tags[1:] {
|
||||
opts[t] = true
|
||||
}
|
||||
if name == "" {
|
||||
switch {
|
||||
case name == "":
|
||||
if !f.Anonymous {
|
||||
name = f.Name
|
||||
}
|
||||
} else if name == "-" {
|
||||
c.byIndex[i] = structTag{name: name}
|
||||
case name == "-":
|
||||
continue
|
||||
} else if !validPropertyName(name) {
|
||||
case name == "__key__":
|
||||
if f.Type != typeOfKeyPtr {
|
||||
return nil, fmt.Errorf("datastore: __key__ field on struct %v is not a *datastore.Key", t)
|
||||
}
|
||||
c.keyField = i
|
||||
case !validPropertyName(name):
|
||||
return nil, fmt.Errorf("datastore: struct tag has invalid property name: %q", name)
|
||||
}
|
||||
|
||||
|
@ -216,11 +238,10 @@ func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
|
|||
c.hasSlice = c.hasSlice || fIsSlice
|
||||
}
|
||||
|
||||
var sub *structCodec
|
||||
if substructType != nil && substructType != typeOfTime && substructType != typeOfGeoPoint {
|
||||
if name != "" {
|
||||
name = name + "."
|
||||
}
|
||||
sub, err := getStructCodecLocked(substructType)
|
||||
var err error
|
||||
sub, err = getStructCodecLocked(substructType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -232,23 +253,35 @@ func getStructCodecLocked(t reflect.Type) (ret *structCodec, retErr error) {
|
|||
"datastore: flattening nested structs leads to a slice of slices: field %q", f.Name)
|
||||
}
|
||||
c.hasSlice = c.hasSlice || sub.hasSlice
|
||||
for relName := range sub.byName {
|
||||
absName := name + relName
|
||||
if _, ok := c.byName[absName]; ok {
|
||||
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", absName)
|
||||
// If f is an anonymous struct field, we promote the substruct's fields up to this level
|
||||
// in the linked list of struct codecs.
|
||||
if f.Anonymous {
|
||||
for subname, subfield := range sub.fields {
|
||||
if name != "" {
|
||||
subname = name + "." + subname
|
||||
}
|
||||
if _, ok := c.fields[subname]; ok {
|
||||
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", subname)
|
||||
}
|
||||
c.fields[subname] = fieldCodec{
|
||||
path: append([]int{i}, subfield.path...),
|
||||
noIndex: subfield.noIndex || opts["noindex"],
|
||||
omitEmpty: subfield.omitEmpty,
|
||||
structCodec: subfield.structCodec,
|
||||
}
|
||||
}
|
||||
c.byName[absName] = fieldCodec{index: i, substructCodec: sub}
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if _, ok := c.byName[name]; ok {
|
||||
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", name)
|
||||
}
|
||||
c.byName[name] = fieldCodec{index: i}
|
||||
}
|
||||
|
||||
c.byIndex[i] = structTag{
|
||||
name: name,
|
||||
noIndex: opts["noindex"],
|
||||
if _, ok := c.fields[name]; ok {
|
||||
return nil, fmt.Errorf("datastore: struct tag has repeated property name: %q", name)
|
||||
}
|
||||
c.fields[name] = fieldCodec{
|
||||
path: []int{i},
|
||||
noIndex: opts["noindex"],
|
||||
omitEmpty: opts["omitempty"],
|
||||
structCodec: sub,
|
||||
}
|
||||
}
|
||||
c.complete = true
|
||||
|
@ -261,8 +294,9 @@ type structPLS struct {
|
|||
codec *structCodec
|
||||
}
|
||||
|
||||
// newStructPLS returns a PropertyLoadSaver for the struct pointer p.
|
||||
func newStructPLS(p interface{}) (PropertyLoadSaver, error) {
|
||||
// newStructPLS returns a structPLS, which implements the
|
||||
// PropertyLoadSaver interface, for the struct pointer p.
|
||||
func newStructPLS(p interface{}) (*structPLS, error) {
|
||||
v := reflect.ValueOf(p)
|
||||
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
|
||||
return nil, ErrInvalidEntityType
|
||||
|
@ -272,7 +306,7 @@ func newStructPLS(p interface{}) (PropertyLoadSaver, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return structPLS{v, codec}, nil
|
||||
return &structPLS{v, codec}, nil
|
||||
}
|
||||
|
||||
// LoadStruct loads the properties from p to dst.
|
||||
|
|
380
vendor/google.golang.org/appengine/datastore/prop_test.go
generated
vendored
380
vendor/google.golang.org/appengine/datastore/prop_test.go
generated
vendored
|
@ -6,6 +6,7 @@ package datastore
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -80,58 +81,36 @@ func TestStructCodec(t *testing.T) {
|
|||
V string `datastore:",noindex"`
|
||||
}
|
||||
oStructCodec := &structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "O"},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"O": {index: 0},
|
||||
fields: map[string]fieldCodec{
|
||||
"O": {path: []int{0}},
|
||||
},
|
||||
complete: true,
|
||||
}
|
||||
pStructCodec := &structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "P"},
|
||||
{name: "Q"},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"P": {index: 0},
|
||||
"Q": {index: 1},
|
||||
fields: map[string]fieldCodec{
|
||||
"P": {path: []int{0}},
|
||||
"Q": {path: []int{1}},
|
||||
},
|
||||
complete: true,
|
||||
}
|
||||
rStructCodec := &structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "R"},
|
||||
{name: "S."},
|
||||
{name: "T."},
|
||||
{name: ""},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"R": {index: 0},
|
||||
"S.P": {index: 1, substructCodec: pStructCodec},
|
||||
"S.Q": {index: 1, substructCodec: pStructCodec},
|
||||
"T.O": {index: 2, substructCodec: oStructCodec},
|
||||
"O": {index: 3, substructCodec: oStructCodec},
|
||||
fields: map[string]fieldCodec{
|
||||
"R": {path: []int{0}},
|
||||
"S": {path: []int{1}, structCodec: pStructCodec},
|
||||
"T": {path: []int{2}, structCodec: oStructCodec},
|
||||
"O": {path: []int{3, 0}},
|
||||
},
|
||||
complete: true,
|
||||
}
|
||||
uStructCodec := &structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "U"},
|
||||
{name: "v"},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"U": {index: 0},
|
||||
"v": {index: 1},
|
||||
fields: map[string]fieldCodec{
|
||||
"U": {path: []int{0}},
|
||||
},
|
||||
complete: true,
|
||||
}
|
||||
vStructCodec := &structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "V", noIndex: true},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"V": {index: 0},
|
||||
fields: map[string]fieldCodec{
|
||||
"V": {path: []int{0}, noIndex: true},
|
||||
},
|
||||
complete: true,
|
||||
}
|
||||
|
@ -169,15 +148,10 @@ func TestStructCodec(t *testing.T) {
|
|||
T time.Time
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "B"},
|
||||
{name: "K"},
|
||||
{name: "T"},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"B": {index: 0},
|
||||
"K": {index: 1},
|
||||
"T": {index: 2},
|
||||
fields: map[string]fieldCodec{
|
||||
"B": {path: []int{0}},
|
||||
"K": {path: []int{1}},
|
||||
"T": {path: []int{2}},
|
||||
},
|
||||
complete: true,
|
||||
},
|
||||
|
@ -195,23 +169,13 @@ func TestStructCodec(t *testing.T) {
|
|||
oStruct `datastore:"-"`
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "a", noIndex: true},
|
||||
{name: "b", noIndex: false},
|
||||
{name: "C", noIndex: true},
|
||||
{name: "D", noIndex: false},
|
||||
{name: "E", noIndex: false},
|
||||
{name: "-", noIndex: false},
|
||||
{name: "J", noIndex: true},
|
||||
{name: "-", noIndex: false},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"a": {index: 0},
|
||||
"b": {index: 1},
|
||||
"C": {index: 2},
|
||||
"D": {index: 3},
|
||||
"E": {index: 4},
|
||||
"J": {index: 6},
|
||||
fields: map[string]fieldCodec{
|
||||
"a": {path: []int{0}, noIndex: true},
|
||||
"b": {path: []int{1}},
|
||||
"C": {path: []int{2}, noIndex: true},
|
||||
"D": {path: []int{3}},
|
||||
"E": {path: []int{4}},
|
||||
"J": {path: []int{6}, noIndex: true},
|
||||
},
|
||||
complete: true,
|
||||
},
|
||||
|
@ -225,17 +189,9 @@ func TestStructCodec(t *testing.T) {
|
|||
d int `datastore:"Y"`
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "A"},
|
||||
{name: "b"},
|
||||
{name: "x"},
|
||||
{name: "Y"},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"A": {index: 0},
|
||||
"b": {index: 1},
|
||||
"x": {index: 2},
|
||||
"Y": {index: 3},
|
||||
fields: map[string]fieldCodec{
|
||||
"A": {path: []int{0}},
|
||||
"x": {path: []int{2}},
|
||||
},
|
||||
complete: true,
|
||||
},
|
||||
|
@ -250,23 +206,12 @@ func TestStructCodec(t *testing.T) {
|
|||
oStruct
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "A"},
|
||||
{name: "B"},
|
||||
{name: "CC."},
|
||||
{name: "DDD."},
|
||||
{name: ""},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"A": {index: 0},
|
||||
"B": {index: 1},
|
||||
"CC.O": {index: 2, substructCodec: oStructCodec},
|
||||
"DDD.R": {index: 3, substructCodec: rStructCodec},
|
||||
"DDD.S.P": {index: 3, substructCodec: rStructCodec},
|
||||
"DDD.S.Q": {index: 3, substructCodec: rStructCodec},
|
||||
"DDD.T.O": {index: 3, substructCodec: rStructCodec},
|
||||
"DDD.O": {index: 3, substructCodec: rStructCodec},
|
||||
"O": {index: 4, substructCodec: oStructCodec},
|
||||
fields: map[string]fieldCodec{
|
||||
"A": {path: []int{0}},
|
||||
"B": {path: []int{1}},
|
||||
"CC": {path: []int{2}, structCodec: oStructCodec},
|
||||
"DDD": {path: []int{3}, structCodec: rStructCodec},
|
||||
"O": {path: []int{4, 0}},
|
||||
},
|
||||
complete: true,
|
||||
},
|
||||
|
@ -281,22 +226,11 @@ func TestStructCodec(t *testing.T) {
|
|||
oStruct `datastore:"z"`
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "-"},
|
||||
{name: "w"},
|
||||
{name: "xx."},
|
||||
{name: "y."},
|
||||
{name: "z."},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"w": {index: 1},
|
||||
"xx.O": {index: 2, substructCodec: oStructCodec},
|
||||
"y.R": {index: 3, substructCodec: rStructCodec},
|
||||
"y.S.P": {index: 3, substructCodec: rStructCodec},
|
||||
"y.S.Q": {index: 3, substructCodec: rStructCodec},
|
||||
"y.T.O": {index: 3, substructCodec: rStructCodec},
|
||||
"y.O": {index: 3, substructCodec: rStructCodec},
|
||||
"z.O": {index: 4, substructCodec: oStructCodec},
|
||||
fields: map[string]fieldCodec{
|
||||
"w": {path: []int{1}},
|
||||
"xx": {path: []int{2}, structCodec: oStructCodec},
|
||||
"y": {path: []int{3}, structCodec: rStructCodec},
|
||||
"z.O": {path: []int{4, 0}},
|
||||
},
|
||||
complete: true,
|
||||
},
|
||||
|
@ -311,22 +245,10 @@ func TestStructCodec(t *testing.T) {
|
|||
uStruct
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "a"},
|
||||
{name: "B"},
|
||||
{name: "c."},
|
||||
{name: "D."},
|
||||
{name: ""},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"a": {index: 0},
|
||||
"B": {index: 1},
|
||||
"c.U": {index: 2, substructCodec: uStructCodec},
|
||||
"c.v": {index: 2, substructCodec: uStructCodec},
|
||||
"D.U": {index: 3, substructCodec: uStructCodec},
|
||||
"D.v": {index: 3, substructCodec: uStructCodec},
|
||||
"U": {index: 4, substructCodec: uStructCodec},
|
||||
"v": {index: 4, substructCodec: uStructCodec},
|
||||
fields: map[string]fieldCodec{
|
||||
"B": {path: []int{1}},
|
||||
"D": {path: []int{3}, structCodec: uStructCodec},
|
||||
"U": {path: []int{4, 0}},
|
||||
},
|
||||
complete: true,
|
||||
},
|
||||
|
@ -337,11 +259,8 @@ func TestStructCodec(t *testing.T) {
|
|||
A oStruct `datastore:",noindex"`
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "A.", noIndex: true},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"A.O": {index: 0, substructCodec: oStructCodec},
|
||||
fields: map[string]fieldCodec{
|
||||
"A": {path: []int{0}, structCodec: oStructCodec, noIndex: true},
|
||||
},
|
||||
complete: true,
|
||||
},
|
||||
|
@ -352,11 +271,8 @@ func TestStructCodec(t *testing.T) {
|
|||
A []string `datastore:",noindex"`
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "A", noIndex: true},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"A": {index: 0},
|
||||
fields: map[string]fieldCodec{
|
||||
"A": {path: []int{0}, noIndex: true},
|
||||
},
|
||||
hasSlice: true,
|
||||
complete: true,
|
||||
|
@ -369,11 +285,8 @@ func TestStructCodec(t *testing.T) {
|
|||
A []vStruct `datastore:",noindex"`
|
||||
}{},
|
||||
&structCodec{
|
||||
byIndex: []structTag{
|
||||
{name: "A.", noIndex: true},
|
||||
},
|
||||
byName: map[string]fieldCodec{
|
||||
"A.V": {index: 0, substructCodec: vStructCodec},
|
||||
fields: map[string]fieldCodec{
|
||||
"A": {path: []int{0}, structCodec: vStructCodec, noIndex: true},
|
||||
},
|
||||
hasSlice: true,
|
||||
complete: true,
|
||||
|
@ -387,13 +300,44 @@ func TestStructCodec(t *testing.T) {
|
|||
t.Errorf("%s: getStructCodec: %v", tc.desc, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
// can't reflect.DeepEqual b/c element order in fields map may differ
|
||||
if !isEqualStructCodec(got, tc.want) {
|
||||
t.Errorf("%s\ngot %+v\nwant %+v\n", tc.desc, got, tc.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isEqualStructCodec(got, want *structCodec) bool {
|
||||
if got.complete != want.complete {
|
||||
return false
|
||||
}
|
||||
if got.hasSlice != want.hasSlice {
|
||||
return false
|
||||
}
|
||||
if len(got.fields) != len(want.fields) {
|
||||
return false
|
||||
}
|
||||
for name, wantF := range want.fields {
|
||||
gotF := got.fields[name]
|
||||
if !reflect.DeepEqual(wantF.path, gotF.path) {
|
||||
return false
|
||||
}
|
||||
if wantF.noIndex != gotF.noIndex {
|
||||
return false
|
||||
}
|
||||
if wantF.structCodec != nil {
|
||||
if gotF.structCodec == nil {
|
||||
return false
|
||||
}
|
||||
if !isEqualStructCodec(gotF.structCodec, wantF.structCodec) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func TestRepeatedPropertyName(t *testing.T) {
|
||||
good := []interface{}{
|
||||
struct {
|
||||
|
@ -453,7 +397,7 @@ func TestRepeatedPropertyName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFlatteningNestedStructs(t *testing.T) {
|
||||
type deepGood struct {
|
||||
type DeepGood struct {
|
||||
A struct {
|
||||
B []struct {
|
||||
C struct {
|
||||
|
@ -462,7 +406,7 @@ func TestFlatteningNestedStructs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
type deepBad struct {
|
||||
type DeepBad struct {
|
||||
A struct {
|
||||
B []struct {
|
||||
C struct {
|
||||
|
@ -471,16 +415,16 @@ func TestFlatteningNestedStructs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
type iSay struct {
|
||||
type ISay struct {
|
||||
Tomato int
|
||||
}
|
||||
type youSay struct {
|
||||
type YouSay struct {
|
||||
Tomato int
|
||||
}
|
||||
type tweedledee struct {
|
||||
type Tweedledee struct {
|
||||
Dee int `datastore:"D"`
|
||||
}
|
||||
type tweedledum struct {
|
||||
type Tweedledum struct {
|
||||
Dum int `datastore:"D"`
|
||||
}
|
||||
|
||||
|
@ -515,18 +459,18 @@ func TestFlatteningNestedStructs(t *testing.T) {
|
|||
Q []int
|
||||
}{},
|
||||
struct {
|
||||
deepGood
|
||||
DeepGood
|
||||
}{},
|
||||
struct {
|
||||
DG deepGood
|
||||
DG DeepGood
|
||||
}{},
|
||||
struct {
|
||||
Foo struct {
|
||||
Z int `datastore:"X"`
|
||||
Z int
|
||||
} `datastore:"A"`
|
||||
Bar struct {
|
||||
Z int `datastore:"Y"`
|
||||
} `datastore:"A"`
|
||||
Z int
|
||||
} `datastore:"B"`
|
||||
}{},
|
||||
}
|
||||
bad := []interface{}{
|
||||
|
@ -541,18 +485,18 @@ func TestFlatteningNestedStructs(t *testing.T) {
|
|||
}
|
||||
}{},
|
||||
struct {
|
||||
deepBad
|
||||
DeepBad
|
||||
}{},
|
||||
struct {
|
||||
DB deepBad
|
||||
DB DeepBad
|
||||
}{},
|
||||
struct {
|
||||
iSay
|
||||
youSay
|
||||
ISay
|
||||
YouSay
|
||||
}{},
|
||||
struct {
|
||||
tweedledee
|
||||
tweedledum
|
||||
Tweedledee
|
||||
Tweedledum
|
||||
}{},
|
||||
struct {
|
||||
Foo struct {
|
||||
|
@ -602,3 +546,127 @@ func TestNilKeyIsStored(t *testing.T) {
|
|||
t.Errorf("I field was not zero")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveStructOmitEmpty(t *testing.T) {
|
||||
// Expected props names are sorted alphabetically
|
||||
expectedPropNamesForSingles := []string{"EmptyValue", "NonEmptyValue", "OmitEmptyWithValue"}
|
||||
expectedPropNamesForSlices := []string{"NonEmptyValue", "NonEmptyValue", "OmitEmptyWithValue", "OmitEmptyWithValue"}
|
||||
|
||||
testOmitted := func(expectedPropNames []string, src interface{}) {
|
||||
// t.Helper() - this is available from Go version 1.9, but we also support Go versions 1.6, 1.7, 1.8
|
||||
if props, err := SaveStruct(src); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
// Collect names for reporting if diffs from expected and for easier sorting
|
||||
actualPropNames := make([]string, len(props))
|
||||
for i := range props {
|
||||
actualPropNames[i] = props[i].Name
|
||||
}
|
||||
// Sort actuals for comparing with already sorted expected names
|
||||
sort.Sort(sort.StringSlice(actualPropNames))
|
||||
if !reflect.DeepEqual(actualPropNames, expectedPropNames) {
|
||||
t.Errorf("Expected this properties: %v, got: %v", expectedPropNames, actualPropNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testOmitted(expectedPropNamesForSingles, &struct {
|
||||
EmptyValue int
|
||||
NonEmptyValue int
|
||||
OmitEmptyNoValue int `datastore:",omitempty"`
|
||||
OmitEmptyWithValue int `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: 1,
|
||||
OmitEmptyWithValue: 2,
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSlices, &struct {
|
||||
EmptyValue []int
|
||||
NonEmptyValue []int
|
||||
OmitEmptyNoValue []int `datastore:",omitempty"`
|
||||
OmitEmptyWithValue []int `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: []int{1, 2},
|
||||
OmitEmptyWithValue: []int{3, 4},
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSingles, &struct {
|
||||
EmptyValue bool
|
||||
NonEmptyValue bool
|
||||
OmitEmptyNoValue bool `datastore:",omitempty"`
|
||||
OmitEmptyWithValue bool `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: true,
|
||||
OmitEmptyWithValue: true,
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSlices, &struct {
|
||||
EmptyValue []bool
|
||||
NonEmptyValue []bool
|
||||
OmitEmptyNoValue []bool `datastore:",omitempty"`
|
||||
OmitEmptyWithValue []bool `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: []bool{true, true},
|
||||
OmitEmptyWithValue: []bool{true, true},
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSingles, &struct {
|
||||
EmptyValue string
|
||||
NonEmptyValue string
|
||||
OmitEmptyNoValue string `datastore:",omitempty"`
|
||||
OmitEmptyWithValue string `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: "s",
|
||||
OmitEmptyWithValue: "s",
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSlices, &struct {
|
||||
EmptyValue []string
|
||||
NonEmptyValue []string
|
||||
OmitEmptyNoValue []string `datastore:",omitempty"`
|
||||
OmitEmptyWithValue []string `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: []string{"s1", "s2"},
|
||||
OmitEmptyWithValue: []string{"s3", "s4"},
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSingles, &struct {
|
||||
EmptyValue float32
|
||||
NonEmptyValue float32
|
||||
OmitEmptyNoValue float32 `datastore:",omitempty"`
|
||||
OmitEmptyWithValue float32 `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: 1.1,
|
||||
OmitEmptyWithValue: 1.2,
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSlices, &struct {
|
||||
EmptyValue []float32
|
||||
NonEmptyValue []float32
|
||||
OmitEmptyNoValue []float32 `datastore:",omitempty"`
|
||||
OmitEmptyWithValue []float32 `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: []float32{1.1, 2.2},
|
||||
OmitEmptyWithValue: []float32{3.3, 4.4},
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSingles, &struct {
|
||||
EmptyValue time.Time
|
||||
NonEmptyValue time.Time
|
||||
OmitEmptyNoValue time.Time `datastore:",omitempty"`
|
||||
OmitEmptyWithValue time.Time `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: now,
|
||||
OmitEmptyWithValue: now,
|
||||
})
|
||||
|
||||
testOmitted(expectedPropNamesForSlices, &struct {
|
||||
EmptyValue []time.Time
|
||||
NonEmptyValue []time.Time
|
||||
OmitEmptyNoValue []time.Time `datastore:",omitempty"`
|
||||
OmitEmptyWithValue []time.Time `datastore:",omitempty"`
|
||||
}{
|
||||
NonEmptyValue: []time.Time{now, now},
|
||||
OmitEmptyWithValue: []time.Time{now, now},
|
||||
})
|
||||
}
|
||||
|
|
47
vendor/google.golang.org/appengine/datastore/query.go
generated
vendored
47
vendor/google.golang.org/appengine/datastore/query.go
generated
vendored
|
@ -87,6 +87,7 @@ type Query struct {
|
|||
eventual bool
|
||||
limit int32
|
||||
offset int32
|
||||
count int32
|
||||
start *pb.CompiledCursor
|
||||
end *pb.CompiledCursor
|
||||
|
||||
|
@ -241,6 +242,19 @@ func (q *Query) Offset(offset int) *Query {
|
|||
return q
|
||||
}
|
||||
|
||||
// BatchSize returns a derivative query to fetch the supplied number of results
|
||||
// at once. This value should be greater than zero, and equal to or less than
|
||||
// the Limit.
|
||||
func (q *Query) BatchSize(size int) *Query {
|
||||
q = q.clone()
|
||||
if size <= 0 || size > math.MaxInt32 {
|
||||
q.err = errors.New("datastore: query batch size overflow")
|
||||
return q
|
||||
}
|
||||
q.count = int32(size)
|
||||
return q
|
||||
}
|
||||
|
||||
// Start returns a derivative query with the given start point.
|
||||
func (q *Query) Start(c Cursor) *Query {
|
||||
q = q.clone()
|
||||
|
@ -325,6 +339,9 @@ func (q *Query) toProto(dst *pb.Query, appID string) error {
|
|||
if q.offset != 0 {
|
||||
dst.Offset = proto.Int32(q.offset)
|
||||
}
|
||||
if q.count != 0 {
|
||||
dst.Count = proto.Int32(q.count)
|
||||
}
|
||||
dst.CompiledCursor = q.start
|
||||
dst.EndCompiledCursor = q.end
|
||||
dst.Compile = proto.Bool(true)
|
||||
|
@ -394,7 +411,7 @@ func (q *Query) Count(c context.Context) (int, error) {
|
|||
if !res.GetMoreResults() {
|
||||
break
|
||||
}
|
||||
if err := callNext(c, res, newQ.offset-n, 0); err != nil {
|
||||
if err := callNext(c, res, newQ.offset-n, q.count); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
@ -409,15 +426,15 @@ func (q *Query) Count(c context.Context) (int, error) {
|
|||
|
||||
// callNext issues a datastore_v3/Next RPC to advance a cursor, such as that
|
||||
// returned by a query with more results.
|
||||
func callNext(c context.Context, res *pb.QueryResult, offset, limit int32) error {
|
||||
func callNext(c context.Context, res *pb.QueryResult, offset, count int32) error {
|
||||
if res.Cursor == nil {
|
||||
return errors.New("datastore: internal error: server did not return a cursor")
|
||||
}
|
||||
req := &pb.NextRequest{
|
||||
Cursor: res.Cursor,
|
||||
}
|
||||
if limit >= 0 {
|
||||
req.Count = proto.Int32(limit)
|
||||
if count >= 0 {
|
||||
req.Count = proto.Int32(count)
|
||||
}
|
||||
if offset != 0 {
|
||||
req.Offset = proto.Int32(offset)
|
||||
|
@ -445,7 +462,7 @@ func callNext(c context.Context, res *pb.QueryResult, offset, limit int32) error
|
|||
// If q is a ``keys-only'' query, GetAll ignores dst and only returns the keys.
|
||||
//
|
||||
// The running time and number of API calls made by GetAll scale linearly with
|
||||
// with the sum of the query's offset and limit. Unless the result count is
|
||||
// the sum of the query's offset and limit. Unless the result count is
|
||||
// expected to be small, it is best to specify a limit; otherwise GetAll will
|
||||
// continue until it finishes collecting results or the provided context
|
||||
// expires.
|
||||
|
@ -523,6 +540,7 @@ func (q *Query) Run(c context.Context) *Iterator {
|
|||
t := &Iterator{
|
||||
c: c,
|
||||
limit: q.limit,
|
||||
count: q.count,
|
||||
q: q,
|
||||
prevCC: q.start,
|
||||
}
|
||||
|
@ -536,9 +554,15 @@ func (q *Query) Run(c context.Context) *Iterator {
|
|||
return t
|
||||
}
|
||||
offset := q.offset - t.res.GetSkippedResults()
|
||||
var count int32
|
||||
if t.count > 0 && (t.limit < 0 || t.count < t.limit) {
|
||||
count = t.count
|
||||
} else {
|
||||
count = t.limit
|
||||
}
|
||||
for offset > 0 && t.res.GetMoreResults() {
|
||||
t.prevCC = t.res.CompiledCursor
|
||||
if err := callNext(t.c, &t.res, offset, t.limit); err != nil {
|
||||
if err := callNext(t.c, &t.res, offset, count); err != nil {
|
||||
t.err = err
|
||||
break
|
||||
}
|
||||
|
@ -566,6 +590,9 @@ type Iterator struct {
|
|||
// limit is the limit on the number of results this iterator should return.
|
||||
// A negative value means unlimited.
|
||||
limit int32
|
||||
// count is the number of results this iterator should fetch at once. This
|
||||
// should be equal to or greater than zero.
|
||||
count int32
|
||||
// q is the original query which yielded this iterator.
|
||||
q *Query
|
||||
// prevCC is the compiled cursor that marks the end of the previous batch
|
||||
|
@ -605,7 +632,13 @@ func (t *Iterator) next() (*Key, *pb.EntityProto, error) {
|
|||
return nil, nil, t.err
|
||||
}
|
||||
t.prevCC = t.res.CompiledCursor
|
||||
if err := callNext(t.c, &t.res, 0, t.limit); err != nil {
|
||||
var count int32
|
||||
if t.count > 0 && (t.limit < 0 || t.count < t.limit) {
|
||||
count = t.count
|
||||
} else {
|
||||
count = t.limit
|
||||
}
|
||||
if err := callNext(t.c, &t.res, 0, count); err != nil {
|
||||
t.err = err
|
||||
return nil, nil, t.err
|
||||
}
|
||||
|
|
3
vendor/google.golang.org/appengine/datastore/query_test.go
generated
vendored
3
vendor/google.golang.org/appengine/datastore/query_test.go
generated
vendored
|
@ -464,7 +464,7 @@ func TestQueryToProto(t *testing.T) {
|
|||
},
|
||||
{
|
||||
desc: "standard query",
|
||||
query: NewQuery("kind").Order("-I").Filter("I >", 17).Filter("U =", "Dave").Limit(7).Offset(42),
|
||||
query: NewQuery("kind").Order("-I").Filter("I >", 17).Filter("U =", "Dave").Limit(7).Offset(42).BatchSize(5),
|
||||
want: &pb.Query{
|
||||
Kind: proto.String("kind"),
|
||||
Filter: []*pb.Query_Filter{
|
||||
|
@ -497,6 +497,7 @@ func TestQueryToProto(t *testing.T) {
|
|||
},
|
||||
Limit: proto.Int32(7),
|
||||
Offset: proto.Int32(42),
|
||||
Count: proto.Int32(5),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
69
vendor/google.golang.org/appengine/datastore/save.go
generated
vendored
69
vendor/google.golang.org/appengine/datastore/save.go
generated
vendored
|
@ -111,6 +111,12 @@ func valueToProto(defaultAppID, name string, v reflect.Value, multiple bool) (p
|
|||
return p, ""
|
||||
}
|
||||
|
||||
type saveOpts struct {
|
||||
noIndex bool
|
||||
multiple bool
|
||||
omitEmpty bool
|
||||
}
|
||||
|
||||
// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer.
|
||||
func saveEntity(defaultAppID string, key *Key, src interface{}) (*pb.EntityProto, error) {
|
||||
var err error
|
||||
|
@ -126,11 +132,14 @@ func saveEntity(defaultAppID string, key *Key, src interface{}) (*pb.EntityProto
|
|||
return propertiesToProto(defaultAppID, key, props)
|
||||
}
|
||||
|
||||
func saveStructProperty(props *[]Property, name string, noIndex, multiple bool, v reflect.Value) error {
|
||||
func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect.Value) error {
|
||||
if opts.omitEmpty && isEmptyValue(v) {
|
||||
return nil
|
||||
}
|
||||
p := Property{
|
||||
Name: name,
|
||||
NoIndex: noIndex,
|
||||
Multiple: multiple,
|
||||
NoIndex: opts.noIndex,
|
||||
Multiple: opts.multiple,
|
||||
}
|
||||
switch x := v.Interface().(type) {
|
||||
case *Key:
|
||||
|
@ -166,7 +175,7 @@ func saveStructProperty(props *[]Property, name string, noIndex, multiple bool,
|
|||
if err != nil {
|
||||
return fmt.Errorf("datastore: unsupported struct field: %v", err)
|
||||
}
|
||||
return sub.(structPLS).save(props, name, noIndex, multiple)
|
||||
return sub.save(props, name+".", opts)
|
||||
}
|
||||
}
|
||||
if p.Value == nil {
|
||||
|
@ -178,37 +187,35 @@ func saveStructProperty(props *[]Property, name string, noIndex, multiple bool,
|
|||
|
||||
func (s structPLS) Save() ([]Property, error) {
|
||||
var props []Property
|
||||
if err := s.save(&props, "", false, false); err != nil {
|
||||
if err := s.save(&props, "", saveOpts{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return props, nil
|
||||
}
|
||||
|
||||
func (s structPLS) save(props *[]Property, prefix string, noIndex, multiple bool) error {
|
||||
for i, t := range s.codec.byIndex {
|
||||
if t.name == "-" {
|
||||
continue
|
||||
}
|
||||
name := t.name
|
||||
if prefix != "" {
|
||||
name = prefix + name
|
||||
}
|
||||
v := s.v.Field(i)
|
||||
func (s structPLS) save(props *[]Property, prefix string, opts saveOpts) error {
|
||||
for name, f := range s.codec.fields {
|
||||
name = prefix + name
|
||||
v := s.v.FieldByIndex(f.path)
|
||||
if !v.IsValid() || !v.CanSet() {
|
||||
continue
|
||||
}
|
||||
noIndex1 := noIndex || t.noIndex
|
||||
var opts1 saveOpts
|
||||
opts1.noIndex = opts.noIndex || f.noIndex
|
||||
opts1.multiple = opts.multiple
|
||||
opts1.omitEmpty = f.omitEmpty // don't propagate
|
||||
// For slice fields that aren't []byte, save each element.
|
||||
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
|
||||
opts1.multiple = true
|
||||
for j := 0; j < v.Len(); j++ {
|
||||
if err := saveStructProperty(props, name, noIndex1, true, v.Index(j)); err != nil {
|
||||
if err := saveStructProperty(props, name, opts1, v.Index(j)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Otherwise, save the field itself.
|
||||
if err := saveStructProperty(props, name, noIndex1, multiple, v); err != nil {
|
||||
if err := saveStructProperty(props, name, opts1, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -298,3 +305,29 @@ func propertiesToProto(defaultAppID string, key *Key, props []Property) (*pb.Ent
|
|||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// isEmptyValue is taken from the encoding/json package in the standard library.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
// TODO(perfomance): Only reflect.String needed, other property types are not supported (copy/paste from json package)
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
// TODO(perfomance): Uint* are unsupported property types - should be removed (copy/paste from json package)
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
case reflect.Struct:
|
||||
switch x := v.Interface().(type) {
|
||||
case time.Time:
|
||||
return x.IsZero()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
11
vendor/google.golang.org/appengine/datastore/transaction.go
generated
vendored
11
vendor/google.golang.org/appengine/datastore/transaction.go
generated
vendored
|
@ -58,12 +58,18 @@ func RunInTransaction(c context.Context, f func(tc context.Context) error, opts
|
|||
if opts != nil {
|
||||
xg = opts.XG
|
||||
}
|
||||
readOnly := false
|
||||
if opts != nil {
|
||||
readOnly = opts.ReadOnly
|
||||
}
|
||||
attempts := 3
|
||||
if opts != nil && opts.Attempts > 0 {
|
||||
attempts = opts.Attempts
|
||||
}
|
||||
var t *pb.Transaction
|
||||
var err error
|
||||
for i := 0; i < attempts; i++ {
|
||||
if err := internal.RunTransactionOnce(c, f, xg); err != internal.ErrConcurrentTransaction {
|
||||
if t, err = internal.RunTransactionOnce(c, f, xg, readOnly, t); err != internal.ErrConcurrentTransaction {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -84,4 +90,7 @@ type TransactionOptions struct {
|
|||
// Attempts controls the number of retries to perform when commits fail
|
||||
// due to a conflicting transaction. If omitted, it defaults to 3.
|
||||
Attempts int
|
||||
// ReadOnly controls whether the transaction is a read only transaction.
|
||||
// Read only transactions are potentially more efficient.
|
||||
ReadOnly bool
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue