OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // adapted from github.com/golang/appengine/datastore | 5 // adapted from github.com/golang/appengine/datastore |
6 | 6 |
7 package datastore | 7 package datastore |
8 | 8 |
9 import ( | 9 import ( |
10 "fmt" | 10 "fmt" |
(...skipping 908 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
919 So(ds.Run(q, func(k *Key, _ CursorCB) bool { | 919 So(ds.Run(q, func(k *Key, _ CursorCB) bool { |
920 So(k.IntID(), ShouldEqual, i+1) | 920 So(k.IntID(), ShouldEqual, i+1) |
921 i++ | 921 i++ |
922 return true | 922 return true |
923 }), ShouldBeNil) | 923 }), ShouldBeNil) |
924 }) | 924 }) |
925 | 925 |
926 }) | 926 }) |
927 }) | 927 }) |
928 } | 928 } |
| 929 |
| 930 type fixedDataDatastore struct { |
| 931 RawInterface |
| 932 |
| 933 data map[string]PropertyMap |
| 934 } |
| 935 |
| 936 func (d *fixedDataDatastore) GetMulti(keys []*Key, _ MultiMetaGetter, cb GetMult
iCB) error { |
| 937 for _, k := range keys { |
| 938 data, ok := d.data[k.String()] |
| 939 if ok { |
| 940 cb(data, nil) |
| 941 } else { |
| 942 cb(nil, ErrNoSuchEntity) |
| 943 } |
| 944 } |
| 945 return nil |
| 946 } |
| 947 |
| 948 func (d *fixedDataDatastore) PutMulti(keys []*Key, vals []PropertyMap, cb PutMul
tiCB) error { |
| 949 if d.data == nil { |
| 950 d.data = make(map[string]PropertyMap, len(keys)) |
| 951 } |
| 952 for i, k := range keys { |
| 953 if k.Incomplete() { |
| 954 panic("key is incomplete, don't do that.") |
| 955 } |
| 956 d.data[k.String()], _ = vals[i].Save(false) |
| 957 cb(k, nil) |
| 958 } |
| 959 return nil |
| 960 } |
| 961 |
| 962 func TestSchemaChange(t *testing.T) { |
| 963 t.Parallel() |
| 964 |
| 965 Convey("Test changing schemas", t, func() { |
| 966 fds := fixedDataDatastore{} |
| 967 ds := &datastoreImpl{&fds, "", ""} |
| 968 |
| 969 Convey("Can add fields", func() { |
| 970 initial := PropertyMap{ |
| 971 "$key": {mpNI(ds.MakeKey("Val", 10))}, |
| 972 "Val": {mp(100)}, |
| 973 } |
| 974 So(ds.Put(initial), ShouldBeNil) |
| 975 |
| 976 type Val struct { |
| 977 ID int64 `gae:"$id"` |
| 978 |
| 979 Val int64 |
| 980 TwoVal int64 // whoa, TWO vals! amazing |
| 981 } |
| 982 tv := &Val{ID: 10, TwoVal: 2} |
| 983 So(ds.Get(tv), ShouldBeNil) |
| 984 So(tv, ShouldResemble, &Val{ID: 10, Val: 100, TwoVal: 2}
) |
| 985 }) |
| 986 |
| 987 Convey("Removing fields", func() { |
| 988 initial := PropertyMap{ |
| 989 "$key": {mpNI(ds.MakeKey("Val", 10))}, |
| 990 "Val": {mp(100)}, |
| 991 "TwoVal": {mp(200)}, |
| 992 } |
| 993 So(ds.Put(initial), ShouldBeNil) |
| 994 |
| 995 Convey("is normally an error", func() { |
| 996 type Val struct { |
| 997 ID int64 `gae:"$id"` |
| 998 |
| 999 Val int64 |
| 1000 } |
| 1001 tv := &Val{ID: 10} |
| 1002 So(ds.Get(tv), ShouldErrLike, |
| 1003 `gae: cannot load field "TwoVal" into a
"datastore.Val`) |
| 1004 So(tv, ShouldResemble, &Val{ID: 10, Val: 100}) |
| 1005 }) |
| 1006 |
| 1007 Convey("Unless you have an ,extra field!", func() { |
| 1008 type Val struct { |
| 1009 ID int64 `gae:"$id"` |
| 1010 |
| 1011 Val int64 |
| 1012 Extra PropertyMap `gae:",extra"` |
| 1013 } |
| 1014 tv := &Val{ID: 10} |
| 1015 So(ds.Get(tv), ShouldBeNil) |
| 1016 So(tv, ShouldResembleV, &Val{ |
| 1017 ID: 10, |
| 1018 Val: 100, |
| 1019 Extra: PropertyMap{ |
| 1020 "TwoVal": {mp(200)}, |
| 1021 }, |
| 1022 }) |
| 1023 }) |
| 1024 }) |
| 1025 |
| 1026 Convey("Can round-trip extra fields", func() { |
| 1027 type Expando struct { |
| 1028 ID int64 `gae:"$id"` |
| 1029 |
| 1030 Something int |
| 1031 Extra PropertyMap `gae:",extra"` |
| 1032 } |
| 1033 ex := &Expando{10, 17, PropertyMap{ |
| 1034 "Hello": {mp("Hello")}, |
| 1035 "World": {mp(true)}, |
| 1036 }} |
| 1037 So(ds.Put(ex), ShouldBeNil) |
| 1038 |
| 1039 ex = &Expando{ID: 10} |
| 1040 So(ds.Get(ex), ShouldBeNil) |
| 1041 So(ex, ShouldResembleV, &Expando{ |
| 1042 ID: 10, |
| 1043 Something: 17, |
| 1044 Extra: PropertyMap{ |
| 1045 "Hello": {mp("Hello")}, |
| 1046 "World": {mp(true)}, |
| 1047 }, |
| 1048 }) |
| 1049 }) |
| 1050 |
| 1051 Convey("Can read-but-not-write", func() { |
| 1052 initial := PropertyMap{ |
| 1053 "$key": {mpNI(ds.MakeKey("Convert", 10))}, |
| 1054 "Val": {mp(100)}, |
| 1055 "TwoVal": {mp(200)}, |
| 1056 } |
| 1057 So(ds.Put(initial), ShouldBeNil) |
| 1058 type Convert struct { |
| 1059 ID int64 `gae:"$id"` |
| 1060 |
| 1061 Val int64 |
| 1062 NewVal int64 |
| 1063 Extra PropertyMap `gae:"-,extra"` |
| 1064 } |
| 1065 c := &Convert{ID: 10} |
| 1066 So(ds.Get(c), ShouldBeNil) |
| 1067 So(c, ShouldResembleV, &Convert{ |
| 1068 ID: 10, Val: 100, NewVal: 0, Extra: PropertyMap{
"TwoVal": {mp(200)}}, |
| 1069 }) |
| 1070 c.NewVal = c.Extra["TwoVal"][0].Value().(int64) |
| 1071 So(ds.Put(c), ShouldBeNil) |
| 1072 |
| 1073 c = &Convert{ID: 10} |
| 1074 So(ds.Get(c), ShouldBeNil) |
| 1075 So(c, ShouldResembleV, &Convert{ |
| 1076 ID: 10, Val: 100, NewVal: 200, Extra: nil, |
| 1077 }) |
| 1078 }) |
| 1079 |
| 1080 Convey("Can black hole", func() { |
| 1081 initial := PropertyMap{ |
| 1082 "$key": {mpNI(ds.MakeKey("BlackHole", 10))}, |
| 1083 "Val": {mp(100)}, |
| 1084 "TwoVal": {mp(200)}, |
| 1085 } |
| 1086 So(ds.Put(initial), ShouldBeNil) |
| 1087 type BlackHole struct { |
| 1088 ID int64 `gae:"$id"` |
| 1089 |
| 1090 NewStuff string |
| 1091 blackHole PropertyMap `gae:"-,extra"` |
| 1092 } |
| 1093 b := &BlackHole{ID: 10, NewStuff: "(╯°□°)╯︵ ┻━┻"} |
| 1094 So(ds.Get(b), ShouldBeNil) |
| 1095 So(b, ShouldResemble, &BlackHole{ID: 10, NewStuff: "(╯°□
°)╯︵ ┻━┻"}) |
| 1096 }) |
| 1097 |
| 1098 Convey("Can change field types", func() { |
| 1099 initial := PropertyMap{ |
| 1100 "$key": {mpNI(ds.MakeKey("IntChange", 10))}, |
| 1101 "Val": {mp(100)}, |
| 1102 } |
| 1103 So(ds.Put(initial), ShouldBeNil) |
| 1104 |
| 1105 type IntChange struct { |
| 1106 ID int64 `gae:"$id"` |
| 1107 Val string |
| 1108 Extra PropertyMap `gae:"-,extra"` |
| 1109 } |
| 1110 i := &IntChange{ID: 10} |
| 1111 So(ds.Get(i), ShouldBeNil) |
| 1112 So(i, ShouldResembleV, &IntChange{ID: 10, Extra: Propert
yMap{"Val": {mp(100)}}}) |
| 1113 i.Val = fmt.Sprint(i.Extra["Val"][0].Value()) |
| 1114 So(ds.Put(i), ShouldBeNil) |
| 1115 |
| 1116 i = &IntChange{ID: 10} |
| 1117 So(ds.Get(i), ShouldBeNil) |
| 1118 So(i, ShouldResembleV, &IntChange{ID: 10, Val: "100"}) |
| 1119 }) |
| 1120 |
| 1121 Convey("Native fields have priority over Extra fields", func() { |
| 1122 type Dup struct { |
| 1123 ID int64 `gae:"$id"` |
| 1124 Val int64 |
| 1125 Extra PropertyMap `gae:",extra"` |
| 1126 } |
| 1127 d := &Dup{ID: 10, Val: 100, Extra: PropertyMap{ |
| 1128 "Val": {mp(200)}, |
| 1129 "Other": {mp("other")}, |
| 1130 }} |
| 1131 So(ds.Put(d), ShouldBeNil) |
| 1132 |
| 1133 d = &Dup{ID: 10} |
| 1134 So(ds.Get(d), ShouldBeNil) |
| 1135 So(d, ShouldResembleV, &Dup{ |
| 1136 ID: 10, Val: 100, Extra: PropertyMap{"Other": {m
p("other")}}, |
| 1137 }) |
| 1138 }) |
| 1139 |
| 1140 Convey("Can change repeated field to non-repeating field", func(
) { |
| 1141 initial := PropertyMap{ |
| 1142 "$key": {mpNI(ds.MakeKey("NonRepeating", 10))}, |
| 1143 "Val": {mp(100), mp(200), mp(400)}, |
| 1144 } |
| 1145 So(ds.Put(initial), ShouldBeNil) |
| 1146 |
| 1147 type NonRepeating struct { |
| 1148 ID int64 `gae:"$id"` |
| 1149 Val int64 |
| 1150 Extra PropertyMap `gae:",extra"` |
| 1151 } |
| 1152 n := &NonRepeating{ID: 10} |
| 1153 So(ds.Get(n), ShouldBeNil) |
| 1154 So(n, ShouldResembleV, &NonRepeating{ |
| 1155 ID: 10, Val: 0, Extra: PropertyMap{ |
| 1156 "Val": {mp(100), mp(200), mp(400)}, |
| 1157 }, |
| 1158 }) |
| 1159 }) |
| 1160 |
| 1161 Convey("Deals correctly with recursive types", func() { |
| 1162 initial := PropertyMap{ |
| 1163 "$key": {mpNI(ds.MakeKey("Outer", 10))}, |
| 1164 "I.A": {mp(1), mp(2), mp(4)}, |
| 1165 "I.B": {mp(10), mp(20), mp(40)}, |
| 1166 "I.C": {mp(100), mp(200), mp(400)}, |
| 1167 } |
| 1168 So(ds.Put(initial), ShouldBeNil) |
| 1169 type Inner struct { |
| 1170 A int64 |
| 1171 B int64 |
| 1172 } |
| 1173 type Outer struct { |
| 1174 ID int64 `gae:"$id"` |
| 1175 |
| 1176 I []Inner |
| 1177 Extra PropertyMap `gae:",extra"` |
| 1178 } |
| 1179 o := &Outer{ID: 10} |
| 1180 So(ds.Get(o), ShouldBeNil) |
| 1181 So(o, ShouldResembleV, &Outer{ |
| 1182 ID: 10, |
| 1183 I: []Inner{ |
| 1184 {1, 10}, |
| 1185 {2, 20}, |
| 1186 {4, 40}, |
| 1187 }, |
| 1188 Extra: PropertyMap{ |
| 1189 "I.C": {mp(100), mp(200), mp(400)}, |
| 1190 }, |
| 1191 }) |
| 1192 }) |
| 1193 |
| 1194 Convey("Problems", func() { |
| 1195 Convey("multiple extra fields", func() { |
| 1196 type Bad struct { |
| 1197 A PropertyMap `gae:",extra"` |
| 1198 B PropertyMap `gae:",extra"` |
| 1199 } |
| 1200 So(func() { GetPLS(&Bad{}) }, ShouldPanicLike, |
| 1201 "multiple fields tagged as 'extra'") |
| 1202 }) |
| 1203 |
| 1204 Convey("extra field with name", func() { |
| 1205 type Bad struct { |
| 1206 A PropertyMap `gae:"wut,extra"` |
| 1207 } |
| 1208 So(func() { GetPLS(&Bad{}) }, ShouldPanicLike, |
| 1209 "struct 'extra' field has invalid name w
ut") |
| 1210 }) |
| 1211 |
| 1212 Convey("extra field with bad type", func() { |
| 1213 type Bad struct { |
| 1214 A int64 `gae:",extra"` |
| 1215 } |
| 1216 So(func() { GetPLS(&Bad{}) }, ShouldPanicLike, |
| 1217 "struct 'extra' field has invalid type i
nt64") |
| 1218 }) |
| 1219 }) |
| 1220 }) |
| 1221 } |
OLD | NEW |