12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499 |
- // Copyright 2011 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 (
- "encoding/json"
- "errors"
- "fmt"
- "reflect"
- "strings"
- "testing"
- "time"
- "google.golang.org/appengine"
- pb "google.golang.org/appengine/internal/datastore"
- )
- const testAppID = "testApp"
- type (
- myBlob []byte
- myByte byte
- myString string
- )
- func makeMyByteSlice(n int) []myByte {
- b := make([]myByte, n)
- for i := range b {
- b[i] = myByte(i)
- }
- return b
- }
- func makeInt8Slice(n int) []int8 {
- b := make([]int8, n)
- for i := range b {
- b[i] = int8(i)
- }
- return b
- }
- func makeUint8Slice(n int) []uint8 {
- b := make([]uint8, n)
- for i := range b {
- b[i] = uint8(i)
- }
- return b
- }
- func newKey(stringID string, parent *Key) *Key {
- return &Key{
- kind: "kind",
- stringID: stringID,
- intID: 0,
- parent: parent,
- appID: testAppID,
- }
- }
- var (
- testKey0 = newKey("name0", nil)
- testKey1a = newKey("name1", nil)
- testKey1b = newKey("name1", nil)
- testKey2a = newKey("name2", testKey0)
- testKey2b = newKey("name2", testKey0)
- testGeoPt0 = appengine.GeoPoint{Lat: 1.2, Lng: 3.4}
- testGeoPt1 = appengine.GeoPoint{Lat: 5, Lng: 10}
- testBadGeoPt = appengine.GeoPoint{Lat: 1000, Lng: 34}
- )
- type B0 struct {
- B []byte
- }
- type B1 struct {
- B []int8
- }
- type B2 struct {
- B myBlob
- }
- type B3 struct {
- B []myByte
- }
- type B4 struct {
- B [][]byte
- }
- type B5 struct {
- B ByteString
- }
- type C0 struct {
- I int
- C chan int
- }
- type C1 struct {
- I int
- C *chan int
- }
- type C2 struct {
- I int
- C []chan int
- }
- type C3 struct {
- C string
- }
- type E struct{}
- type G0 struct {
- G appengine.GeoPoint
- }
- type G1 struct {
- G []appengine.GeoPoint
- }
- type K0 struct {
- K *Key
- }
- type K1 struct {
- K []*Key
- }
- type N0 struct {
- X0
- Nonymous X0
- Ignore string `datastore:"-"`
- Other string
- }
- type N1 struct {
- X0
- Nonymous []X0
- Ignore string `datastore:"-"`
- Other string
- }
- type N2 struct {
- N1 `datastore:"red"`
- Green N1 `datastore:"green"`
- Blue N1
- White N1 `datastore:"-"`
- }
- type O0 struct {
- I int64
- }
- type O1 struct {
- I int32
- }
- type U0 struct {
- U uint
- }
- type U1 struct {
- U string
- }
- type T struct {
- T time.Time
- }
- type X0 struct {
- S string
- I int
- i int
- }
- type X1 struct {
- S myString
- I int32
- J int64
- }
- type X2 struct {
- Z string
- i int
- }
- type X3 struct {
- S bool
- I int
- }
- type Y0 struct {
- B bool
- F []float64
- G []float64
- }
- type Y1 struct {
- B bool
- F float64
- }
- type Y2 struct {
- B bool
- F []int64
- }
- type Tagged struct {
- A int `datastore:"a,noindex"`
- B []int `datastore:"b"`
- C int `datastore:",noindex"`
- D int `datastore:""`
- E int
- I int `datastore:"-"`
- J int `datastore:",noindex" json:"j"`
- Y0 `datastore:"-"`
- Z chan int `datastore:"-,"`
- }
- type InvalidTagged1 struct {
- I int `datastore:"\t"`
- }
- type InvalidTagged2 struct {
- I int
- J int `datastore:"I"`
- }
- type Inner1 struct {
- W int32
- X string
- }
- type Inner2 struct {
- Y float64
- }
- type Inner3 struct {
- Z bool
- }
- type Outer struct {
- A int16
- I []Inner1
- J Inner2
- Inner3
- }
- type OuterEquivalent struct {
- A int16
- IDotW []int32 `datastore:"I.W"`
- IDotX []string `datastore:"I.X"`
- JDotY float64 `datastore:"J.Y"`
- Z bool
- }
- type Dotted struct {
- A DottedA `datastore:"A0.A1.A2"`
- }
- type DottedA struct {
- B DottedB `datastore:"B3"`
- }
- type DottedB struct {
- C int `datastore:"C4.C5"`
- }
- type SliceOfSlices struct {
- I int
- S []struct {
- J int
- F []float64
- }
- }
- type Recursive struct {
- I int
- R []Recursive
- }
- type MutuallyRecursive0 struct {
- I int
- R []MutuallyRecursive1
- }
- type MutuallyRecursive1 struct {
- I int
- R []MutuallyRecursive0
- }
- type Doubler struct {
- S string
- I int64
- B bool
- }
- func (d *Doubler) Load(props []Property) error {
- return LoadStruct(d, props)
- }
- func (d *Doubler) Save() ([]Property, error) {
- // Save the default Property slice to an in-memory buffer (a PropertyList).
- props, err := SaveStruct(d)
- if err != nil {
- return nil, err
- }
- var list PropertyList
- if err := list.Load(props); err != nil {
- return nil, err
- }
- // Edit that PropertyList, and send it on.
- for i := range list {
- switch v := list[i].Value.(type) {
- case string:
- // + means string concatenation.
- list[i].Value = v + v
- case int64:
- // + means integer addition.
- list[i].Value = v + v
- }
- }
- return list.Save()
- }
- var _ PropertyLoadSaver = (*Doubler)(nil)
- type Deriver struct {
- S, Derived, Ignored string
- }
- func (e *Deriver) Load(props []Property) error {
- for _, p := range props {
- if p.Name != "S" {
- continue
- }
- e.S = p.Value.(string)
- e.Derived = "derived+" + e.S
- }
- return nil
- }
- func (e *Deriver) Save() ([]Property, error) {
- return []Property{
- {
- Name: "S",
- Value: e.S,
- },
- }, nil
- }
- var _ PropertyLoadSaver = (*Deriver)(nil)
- type BadMultiPropEntity struct{}
- func (e *BadMultiPropEntity) Load(props []Property) error {
- return errors.New("unimplemented")
- }
- func (e *BadMultiPropEntity) Save() ([]Property, error) {
- // Write multiple properties with the same name "I", but Multiple is false.
- var props []Property
- for i := 0; i < 3; i++ {
- props = append(props, Property{
- Name: "I",
- Value: int64(i),
- })
- }
- return props, nil
- }
- var _ PropertyLoadSaver = (*BadMultiPropEntity)(nil)
- type BK struct {
- Key appengine.BlobKey
- }
- type testCase struct {
- desc string
- src interface{}
- want interface{}
- putErr string
- getErr string
- }
- var testCases = []testCase{
- {
- "chan save fails",
- &C0{I: -1},
- &E{},
- "unsupported struct field",
- "",
- },
- {
- "*chan save fails",
- &C1{I: -1},
- &E{},
- "unsupported struct field",
- "",
- },
- {
- "[]chan save fails",
- &C2{I: -1, C: make([]chan int, 8)},
- &E{},
- "unsupported struct field",
- "",
- },
- {
- "chan load fails",
- &C3{C: "not a chan"},
- &C0{},
- "",
- "type mismatch",
- },
- {
- "*chan load fails",
- &C3{C: "not a *chan"},
- &C1{},
- "",
- "type mismatch",
- },
- {
- "[]chan load fails",
- &C3{C: "not a []chan"},
- &C2{},
- "",
- "type mismatch",
- },
- {
- "empty struct",
- &E{},
- &E{},
- "",
- "",
- },
- {
- "geopoint",
- &G0{G: testGeoPt0},
- &G0{G: testGeoPt0},
- "",
- "",
- },
- {
- "geopoint invalid",
- &G0{G: testBadGeoPt},
- &G0{},
- "invalid GeoPoint value",
- "",
- },
- {
- "geopoint as props",
- &G0{G: testGeoPt0},
- &PropertyList{
- Property{Name: "G", Value: testGeoPt0, NoIndex: false, Multiple: false},
- },
- "",
- "",
- },
- {
- "geopoint slice",
- &G1{G: []appengine.GeoPoint{testGeoPt0, testGeoPt1}},
- &G1{G: []appengine.GeoPoint{testGeoPt0, testGeoPt1}},
- "",
- "",
- },
- {
- "key",
- &K0{K: testKey1a},
- &K0{K: testKey1b},
- "",
- "",
- },
- {
- "key with parent",
- &K0{K: testKey2a},
- &K0{K: testKey2b},
- "",
- "",
- },
- {
- "nil key",
- &K0{},
- &K0{},
- "",
- "",
- },
- {
- "all nil keys in slice",
- &K1{[]*Key{nil, nil}},
- &K1{[]*Key{nil, nil}},
- "",
- "",
- },
- {
- "some nil keys in slice",
- &K1{[]*Key{testKey1a, nil, testKey2a}},
- &K1{[]*Key{testKey1b, nil, testKey2b}},
- "",
- "",
- },
- {
- "overflow",
- &O0{I: 1 << 48},
- &O1{},
- "",
- "overflow",
- },
- {
- "time",
- &T{T: time.Unix(1e9, 0)},
- &T{T: time.Unix(1e9, 0)},
- "",
- "",
- },
- {
- "time as props",
- &T{T: time.Unix(1e9, 0)},
- &PropertyList{
- Property{Name: "T", Value: time.Unix(1e9, 0), NoIndex: false, Multiple: false},
- },
- "",
- "",
- },
- {
- "uint save",
- &U0{U: 1},
- &U0{},
- "unsupported struct field",
- "",
- },
- {
- "uint load",
- &U1{U: "not a uint"},
- &U0{},
- "",
- "type mismatch",
- },
- {
- "zero",
- &X0{},
- &X0{},
- "",
- "",
- },
- {
- "basic",
- &X0{S: "one", I: 2, i: 3},
- &X0{S: "one", I: 2},
- "",
- "",
- },
- {
- "save string/int load myString/int32",
- &X0{S: "one", I: 2, i: 3},
- &X1{S: "one", I: 2},
- "",
- "",
- },
- {
- "missing fields",
- &X0{S: "one", I: 2, i: 3},
- &X2{},
- "",
- "no such struct field",
- },
- {
- "save string load bool",
- &X0{S: "one", I: 2, i: 3},
- &X3{I: 2},
- "",
- "type mismatch",
- },
- {
- "basic slice",
- &Y0{B: true, F: []float64{7, 8, 9}},
- &Y0{B: true, F: []float64{7, 8, 9}},
- "",
- "",
- },
- {
- "save []float64 load float64",
- &Y0{B: true, F: []float64{7, 8, 9}},
- &Y1{B: true},
- "",
- "requires a slice",
- },
- {
- "save []float64 load []int64",
- &Y0{B: true, F: []float64{7, 8, 9}},
- &Y2{B: true},
- "",
- "type mismatch",
- },
- {
- "single slice is too long",
- &Y0{F: make([]float64, maxIndexedProperties+1)},
- &Y0{},
- "too many indexed properties",
- "",
- },
- {
- "two slices are too long",
- &Y0{F: make([]float64, maxIndexedProperties), G: make([]float64, maxIndexedProperties)},
- &Y0{},
- "too many indexed properties",
- "",
- },
- {
- "one slice and one scalar are too long",
- &Y0{F: make([]float64, maxIndexedProperties), B: true},
- &Y0{},
- "too many indexed properties",
- "",
- },
- {
- "long blob",
- &B0{B: makeUint8Slice(maxIndexedProperties + 1)},
- &B0{B: makeUint8Slice(maxIndexedProperties + 1)},
- "",
- "",
- },
- {
- "long []int8 is too long",
- &B1{B: makeInt8Slice(maxIndexedProperties + 1)},
- &B1{},
- "too many indexed properties",
- "",
- },
- {
- "short []int8",
- &B1{B: makeInt8Slice(3)},
- &B1{B: makeInt8Slice(3)},
- "",
- "",
- },
- {
- "long myBlob",
- &B2{B: makeUint8Slice(maxIndexedProperties + 1)},
- &B2{B: makeUint8Slice(maxIndexedProperties + 1)},
- "",
- "",
- },
- {
- "short myBlob",
- &B2{B: makeUint8Slice(3)},
- &B2{B: makeUint8Slice(3)},
- "",
- "",
- },
- {
- "long []myByte",
- &B3{B: makeMyByteSlice(maxIndexedProperties + 1)},
- &B3{B: makeMyByteSlice(maxIndexedProperties + 1)},
- "",
- "",
- },
- {
- "short []myByte",
- &B3{B: makeMyByteSlice(3)},
- &B3{B: makeMyByteSlice(3)},
- "",
- "",
- },
- {
- "slice of blobs",
- &B4{B: [][]byte{
- makeUint8Slice(3),
- makeUint8Slice(4),
- makeUint8Slice(5),
- }},
- &B4{B: [][]byte{
- makeUint8Slice(3),
- makeUint8Slice(4),
- makeUint8Slice(5),
- }},
- "",
- "",
- },
- {
- "short ByteString",
- &B5{B: ByteString(makeUint8Slice(3))},
- &B5{B: ByteString(makeUint8Slice(3))},
- "",
- "",
- },
- {
- "short ByteString as props",
- &B5{B: ByteString(makeUint8Slice(3))},
- &PropertyList{
- Property{Name: "B", Value: ByteString(makeUint8Slice(3)), NoIndex: false, Multiple: false},
- },
- "",
- "",
- },
- {
- "short ByteString into string",
- &B5{B: ByteString("legacy")},
- &struct{ B string }{"legacy"},
- "",
- "",
- },
- {
- "[]byte must be noindex",
- &PropertyList{
- Property{Name: "B", Value: makeUint8Slice(3), NoIndex: false},
- },
- nil,
- "cannot index a []byte valued Property",
- "",
- },
- {
- "save tagged load props",
- &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7},
- &PropertyList{
- // 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: "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: "a", Value: int64(1), NoIndex: true, Multiple: false},
- Property{Name: "C", Value: int64(3), NoIndex: true, Multiple: false},
- Property{Name: "J", Value: int64(7), NoIndex: true, Multiple: false},
- },
- "",
- "",
- },
- {
- "save tagged load tagged",
- &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7},
- &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7},
- "",
- "",
- },
- {
- "save props load tagged",
- &PropertyList{
- Property{Name: "A", Value: int64(11), NoIndex: true, Multiple: false},
- Property{Name: "a", Value: int64(12), NoIndex: true, Multiple: false},
- },
- &Tagged{A: 12},
- "",
- `cannot load field "A"`,
- },
- {
- "invalid tagged1",
- &InvalidTagged1{I: 1},
- &InvalidTagged1{},
- "struct tag has invalid property name",
- "",
- },
- {
- "invalid tagged2",
- &InvalidTagged2{I: 1, J: 2},
- &InvalidTagged2{},
- "struct tag has repeated property name",
- "",
- },
- {
- "doubler",
- &Doubler{S: "s", I: 1, B: true},
- &Doubler{S: "ss", I: 2, B: true},
- "",
- "",
- },
- {
- "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},
- },
- "",
- "",
- },
- {
- "save props load struct",
- &PropertyList{
- Property{Name: "S", Value: "s", NoIndex: false, Multiple: false},
- Property{Name: "I", Value: int64(1), NoIndex: false, Multiple: false},
- },
- &X0{S: "s", I: 1},
- "",
- "",
- },
- {
- "nil-value props",
- &PropertyList{
- Property{Name: "I", Value: nil, NoIndex: false, Multiple: false},
- Property{Name: "B", Value: nil, NoIndex: false, Multiple: false},
- Property{Name: "S", Value: nil, NoIndex: false, Multiple: false},
- Property{Name: "F", Value: nil, NoIndex: false, Multiple: false},
- Property{Name: "K", Value: nil, NoIndex: false, Multiple: false},
- Property{Name: "T", Value: nil, NoIndex: false, Multiple: false},
- Property{Name: "J", Value: nil, NoIndex: false, Multiple: true},
- Property{Name: "J", Value: int64(7), NoIndex: false, Multiple: true},
- Property{Name: "J", Value: nil, NoIndex: false, Multiple: true},
- },
- &struct {
- I int64
- B bool
- S string
- F float64
- K *Key
- T time.Time
- J []int64
- }{
- J: []int64{0, 7, 0},
- },
- "",
- "",
- },
- {
- "save outer load props",
- &Outer{
- A: 1,
- I: []Inner1{
- {10, "ten"},
- {20, "twenty"},
- {30, "thirty"},
- },
- J: Inner2{
- Y: 3.14,
- },
- Inner3: Inner3{
- Z: true,
- },
- },
- &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: "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},
- },
- "",
- "",
- },
- {
- "save props load outer-equivalent",
- &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: "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},
- },
- &OuterEquivalent{
- A: 1,
- IDotW: []int32{10, 20, 30},
- IDotX: []string{"ten", "twenty", "thirty"},
- JDotY: 3.14,
- Z: true,
- },
- "",
- "",
- },
- {
- "save outer-equivalent load outer",
- &OuterEquivalent{
- A: 1,
- IDotW: []int32{10, 20, 30},
- IDotX: []string{"ten", "twenty", "thirty"},
- JDotY: 3.14,
- Z: true,
- },
- &Outer{
- A: 1,
- I: []Inner1{
- {10, "ten"},
- {20, "twenty"},
- {30, "thirty"},
- },
- J: Inner2{
- Y: 3.14,
- },
- Inner3: Inner3{
- Z: true,
- },
- },
- "",
- "",
- },
- {
- "dotted names save",
- &Dotted{A: DottedA{B: DottedB{C: 88}}},
- &PropertyList{
- Property{Name: "A0.A1.A2.B3.C4.C5", Value: int64(88), NoIndex: false, Multiple: false},
- },
- "",
- "",
- },
- {
- "dotted names load",
- &PropertyList{
- Property{Name: "A0.A1.A2.B3.C4.C5", Value: int64(99), NoIndex: false, Multiple: false},
- },
- &Dotted{A: DottedA{B: DottedB{C: 99}}},
- "",
- "",
- },
- {
- "save struct load deriver",
- &X0{S: "s", I: 1},
- &Deriver{S: "s", Derived: "derived+s"},
- "",
- "",
- },
- {
- "save deriver load struct",
- &Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"},
- &X0{S: "s"},
- "",
- "",
- },
- {
- "bad multi-prop entity",
- &BadMultiPropEntity{},
- &BadMultiPropEntity{},
- "Multiple is false",
- "",
- },
- // Regression: CL 25062824 broke handling of appengine.BlobKey fields.
- {
- "appengine.BlobKey",
- &BK{Key: "blah"},
- &BK{Key: "blah"},
- "",
- "",
- },
- {
- "zero time.Time",
- &T{T: time.Time{}},
- &T{T: time.Time{}},
- "",
- "",
- },
- {
- "time.Time near Unix zero time",
- &T{T: time.Unix(0, 4e3)},
- &T{T: time.Unix(0, 4e3)},
- "",
- "",
- },
- {
- "time.Time, far in the future",
- &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)},
- &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)},
- "",
- "",
- },
- {
- "time.Time, very far in the past",
- &T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)},
- &T{},
- "time value out of range",
- "",
- },
- {
- "time.Time, very far in the future",
- &T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)},
- &T{},
- "time value out of range",
- "",
- },
- {
- "structs",
- &N0{
- X0: X0{S: "one", I: 2, i: 3},
- Nonymous: X0{S: "four", I: 5, i: 6},
- Ignore: "ignore",
- Other: "other",
- },
- &N0{
- X0: X0{S: "one", I: 2},
- Nonymous: X0{S: "four", I: 5},
- Other: "other",
- },
- "",
- "",
- },
- {
- "slice of structs",
- &N1{
- X0: X0{S: "one", I: 2, i: 3},
- Nonymous: []X0{
- {S: "four", I: 5, i: 6},
- {S: "seven", I: 8, i: 9},
- {S: "ten", I: 11, i: 12},
- {S: "thirteen", I: 14, i: 15},
- },
- Ignore: "ignore",
- Other: "other",
- },
- &N1{
- X0: X0{S: "one", I: 2},
- Nonymous: []X0{
- {S: "four", I: 5},
- {S: "seven", I: 8},
- {S: "ten", I: 11},
- {S: "thirteen", I: 14},
- },
- Other: "other",
- },
- "",
- "",
- },
- {
- "structs with slices of structs",
- &N2{
- N1: N1{
- X0: X0{S: "rouge"},
- Nonymous: []X0{
- {S: "rosso0"},
- {S: "rosso1"},
- },
- },
- Green: N1{
- X0: X0{S: "vert"},
- Nonymous: []X0{
- {S: "verde0"},
- {S: "verde1"},
- {S: "verde2"},
- },
- },
- Blue: N1{
- X0: X0{S: "bleu"},
- Nonymous: []X0{
- {S: "blu0"},
- {S: "blu1"},
- {S: "blu2"},
- {S: "blu3"},
- },
- },
- },
- &N2{
- N1: N1{
- X0: X0{S: "rouge"},
- Nonymous: []X0{
- {S: "rosso0"},
- {S: "rosso1"},
- },
- },
- Green: N1{
- X0: X0{S: "vert"},
- Nonymous: []X0{
- {S: "verde0"},
- {S: "verde1"},
- {S: "verde2"},
- },
- },
- Blue: N1{
- X0: X0{S: "bleu"},
- Nonymous: []X0{
- {S: "blu0"},
- {S: "blu1"},
- {S: "blu2"},
- {S: "blu3"},
- },
- },
- },
- "",
- "",
- },
- {
- "save structs load props",
- &N2{
- N1: N1{
- X0: X0{S: "rouge"},
- Nonymous: []X0{
- {S: "rosso0"},
- {S: "rosso1"},
- },
- },
- Green: N1{
- X0: X0{S: "vert"},
- Nonymous: []X0{
- {S: "verde0"},
- {S: "verde1"},
- {S: "verde2"},
- },
- },
- Blue: N1{
- X0: X0{S: "bleu"},
- Nonymous: []X0{
- {S: "blu0"},
- {S: "blu1"},
- {S: "blu2"},
- {S: "blu3"},
- },
- },
- },
- &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.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},
- },
- "",
- "",
- },
- {
- "save props load structs with ragged fields",
- &PropertyList{
- Property{Name: "red.S", Value: "rot", NoIndex: false, Multiple: false},
- Property{Name: "green.Nonymous.I", Value: int64(10), NoIndex: false, Multiple: true},
- Property{Name: "green.Nonymous.I", Value: int64(11), NoIndex: false, Multiple: true},
- Property{Name: "green.Nonymous.I", Value: int64(12), NoIndex: false, Multiple: true},
- Property{Name: "green.Nonymous.I", Value: int64(13), NoIndex: false, Multiple: true},
- Property{Name: "Blue.Nonymous.S", Value: "blau0", NoIndex: false, Multiple: true},
- Property{Name: "Blue.Nonymous.I", Value: int64(20), NoIndex: false, Multiple: true},
- Property{Name: "Blue.Nonymous.S", Value: "blau1", NoIndex: false, Multiple: true},
- Property{Name: "Blue.Nonymous.I", Value: int64(21), NoIndex: false, Multiple: true},
- Property{Name: "Blue.Nonymous.S", Value: "blau2", NoIndex: false, Multiple: true},
- },
- &N2{
- N1: N1{
- X0: X0{S: "rot"},
- },
- Green: N1{
- Nonymous: []X0{
- {I: 10},
- {I: 11},
- {I: 12},
- {I: 13},
- },
- },
- Blue: N1{
- Nonymous: []X0{
- {S: "blau0", I: 20},
- {S: "blau1", I: 21},
- {S: "blau2"},
- },
- },
- },
- "",
- "",
- },
- {
- "save structs with noindex tags",
- &struct {
- A struct {
- X string `datastore:",noindex"`
- Y string
- } `datastore:",noindex"`
- B struct {
- X string `datastore:",noindex"`
- Y string
- }
- }{},
- &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},
- },
- "",
- "",
- },
- {
- "embedded struct with name override",
- &struct {
- Inner1 `datastore:"foo"`
- }{},
- &PropertyList{
- Property{Name: "foo.W", Value: int64(0), NoIndex: false, Multiple: false},
- Property{Name: "foo.X", Value: "", NoIndex: false, Multiple: false},
- },
- "",
- "",
- },
- {
- "slice of slices",
- &SliceOfSlices{},
- nil,
- "flattening nested structs leads to a slice of slices",
- "",
- },
- {
- "recursive struct",
- &Recursive{},
- nil,
- "recursive struct",
- "",
- },
- {
- "mutually recursive struct",
- &MutuallyRecursive0{},
- nil,
- "recursive struct",
- "",
- },
- {
- "non-exported struct fields",
- &struct {
- i, J int64
- }{i: 1, J: 2},
- &PropertyList{
- Property{Name: "J", Value: int64(2), NoIndex: false, Multiple: false},
- },
- "",
- "",
- },
- {
- "json.RawMessage",
- &struct {
- J json.RawMessage
- }{
- J: json.RawMessage("rawr"),
- },
- &PropertyList{
- Property{Name: "J", Value: []byte("rawr"), NoIndex: true, Multiple: false},
- },
- "",
- "",
- },
- {
- "json.RawMessage to myBlob",
- &struct {
- B json.RawMessage
- }{
- B: json.RawMessage("rawr"),
- },
- &B2{B: myBlob("rawr")},
- "",
- "",
- },
- }
- // checkErr returns the empty string if either both want and err are zero,
- // or if want is a non-empty substring of err's string representation.
- func checkErr(want string, err error) string {
- if err != nil {
- got := err.Error()
- if want == "" || strings.Index(got, want) == -1 {
- return got
- }
- } else if want != "" {
- return fmt.Sprintf("want error %q", want)
- }
- return ""
- }
- func TestRoundTrip(t *testing.T) {
- for _, tc := range testCases {
- p, err := saveEntity(testAppID, testKey0, tc.src)
- if s := checkErr(tc.putErr, err); s != "" {
- t.Errorf("%s: save: %s", tc.desc, s)
- continue
- }
- if p == nil {
- continue
- }
- var got interface{}
- if _, ok := tc.want.(*PropertyList); ok {
- got = new(PropertyList)
- } else {
- got = reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
- }
- err = loadEntity(got, p)
- if s := checkErr(tc.getErr, err); s != "" {
- t.Errorf("%s: load: %s", tc.desc, s)
- continue
- }
- equal := false
- if gotT, ok := got.(*T); ok {
- // Round tripping a time.Time can result in a different time.Location: Local instead of UTC.
- // We therefore test equality explicitly, instead of relying on reflect.DeepEqual.
- equal = gotT.T.Equal(tc.want.(*T).T)
- } else {
- equal = reflect.DeepEqual(got, tc.want)
- }
- if !equal {
- t.Errorf("%s: compare: got %v want %v", tc.desc, got, tc.want)
- continue
- }
- }
- }
- func TestQueryConstruction(t *testing.T) {
- tests := []struct {
- q, exp *Query
- err string
- }{
- {
- q: NewQuery("Foo"),
- exp: &Query{
- kind: "Foo",
- limit: -1,
- },
- },
- {
- // Regular filtered query with standard spacing.
- q: NewQuery("Foo").Filter("foo >", 7),
- exp: &Query{
- kind: "Foo",
- filter: []filter{
- {
- FieldName: "foo",
- Op: greaterThan,
- Value: 7,
- },
- },
- limit: -1,
- },
- },
- {
- // Filtered query with no spacing.
- q: NewQuery("Foo").Filter("foo=", 6),
- exp: &Query{
- kind: "Foo",
- filter: []filter{
- {
- FieldName: "foo",
- Op: equal,
- Value: 6,
- },
- },
- limit: -1,
- },
- },
- {
- // Filtered query with funky spacing.
- q: NewQuery("Foo").Filter(" foo< ", 8),
- exp: &Query{
- kind: "Foo",
- filter: []filter{
- {
- FieldName: "foo",
- Op: lessThan,
- Value: 8,
- },
- },
- limit: -1,
- },
- },
- {
- // Filtered query with multicharacter op.
- q: NewQuery("Foo").Filter("foo >=", 9),
- exp: &Query{
- kind: "Foo",
- filter: []filter{
- {
- FieldName: "foo",
- Op: greaterEq,
- Value: 9,
- },
- },
- limit: -1,
- },
- },
- {
- // Query with ordering.
- q: NewQuery("Foo").Order("bar"),
- exp: &Query{
- kind: "Foo",
- order: []order{
- {
- FieldName: "bar",
- Direction: ascending,
- },
- },
- limit: -1,
- },
- },
- {
- // Query with reverse ordering, and funky spacing.
- q: NewQuery("Foo").Order(" - bar"),
- exp: &Query{
- kind: "Foo",
- order: []order{
- {
- FieldName: "bar",
- Direction: descending,
- },
- },
- limit: -1,
- },
- },
- {
- // Query with an empty ordering.
- q: NewQuery("Foo").Order(""),
- err: "empty order",
- },
- {
- // Query with a + ordering.
- q: NewQuery("Foo").Order("+bar"),
- err: "invalid order",
- },
- }
- for i, test := range tests {
- if test.q.err != nil {
- got := test.q.err.Error()
- if !strings.Contains(got, test.err) {
- t.Errorf("%d: error mismatch: got %q want something containing %q", i, got, test.err)
- }
- continue
- }
- if !reflect.DeepEqual(test.q, test.exp) {
- t.Errorf("%d: mismatch: got %v want %v", i, test.q, test.exp)
- }
- }
- }
- func TestStringMeaning(t *testing.T) {
- var xx [4]interface{}
- xx[0] = &struct {
- X string
- }{"xx0"}
- xx[1] = &struct {
- X string `datastore:",noindex"`
- }{"xx1"}
- xx[2] = &struct {
- X []byte
- }{[]byte("xx2")}
- xx[3] = &struct {
- X []byte `datastore:",noindex"`
- }{[]byte("xx3")}
- indexed := [4]bool{
- true,
- false,
- false, // A []byte is always no-index.
- false,
- }
- want := [4]pb.Property_Meaning{
- pb.Property_NO_MEANING,
- pb.Property_TEXT,
- pb.Property_BLOB,
- pb.Property_BLOB,
- }
- for i, x := range xx {
- props, err := SaveStruct(x)
- if err != nil {
- t.Errorf("i=%d: SaveStruct: %v", i, err)
- continue
- }
- e, err := propertiesToProto("appID", testKey0, props)
- if err != nil {
- t.Errorf("i=%d: propertiesToProto: %v", i, err)
- continue
- }
- var p *pb.Property
- switch {
- case indexed[i] && len(e.Property) == 1:
- p = e.Property[0]
- case !indexed[i] && len(e.RawProperty) == 1:
- p = e.RawProperty[0]
- default:
- t.Errorf("i=%d: EntityProto did not have expected property slice", i)
- continue
- }
- if got := p.GetMeaning(); got != want[i] {
- t.Errorf("i=%d: meaning: got %v, want %v", i, got, want[i])
- continue
- }
- }
- }
|