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: "amazeballs"} | |
dnj
2015/12/12 20:18:34
(╯°□°)╯︵ ┻━┻
iannucci
2015/12/12 21:14:42
good point. done.
dnj (Google)
2015/12/13 02:09:23
+1
| |
1094 So(ds.Get(b), ShouldBeNil) | |
1095 So(b, ShouldResemble, &BlackHole{ID: 10, NewStuff: "amaz eballs"}) | |
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 |