github.com/pbberlin/tools@v0.0.0-20160910141205-7aa5421c2169/dsu/ancestored_gb_entries/persistence.go (about) 1 // Package ancestored_gb_entries demonstrates putting all entries 2 // under one ancestor, making them a strongly consistent 'entity group' 3 package ancestored_gb_entries 4 5 import ( 6 "net/http" 7 "time" 8 9 "appengine" 10 ds "appengine/datastore" 11 "appengine/user" 12 13 "bytes" 14 "fmt" 15 "strings" 16 //"reflect" 17 18 "github.com/pbberlin/tools/net/http/loghttp" 19 "github.com/pbberlin/tools/util" 20 ) 21 22 /* 23 default_guestbook[T:Guestbook] 24 \ 25 - Entry_1 26 - Entry_2 27 28 29 30 */ 31 32 const ( 33 kind_guestbk string = "class_parent_gb" // "classname" of the parent 34 GbEntryKind string = "gbEntry" // "classname" of a guestbookk entry 35 36 ) 37 38 type GbsaveEntry struct { 39 Author string 40 Content string `datastore:"Content,noindex" json:"content"` 41 Date time.Time 42 unsaved string `datastore:"-"` 43 TypeShiftingField int 44 Comment1 string 45 } 46 47 // just for experiment 48 // we retrieve into a different structure then we saved into 49 // 50 type GbEntryRetr struct { 51 Author string 52 Content string 53 Date time.Time 54 Field2 string 55 TypeShiftingField uint8 56 Comment1 string 57 } 58 59 // returns entity group - or parent - key 60 // to store and retrieve all guestbook entries. 61 // the content of this parent is nil 62 // it only servers as umbrella for the entries 63 func keyParent(c appengine.Context) (r *ds.Key) { 64 65 // strPart could be varied for multiple guestbooks. 66 // Either strPart XOR intPart must be zero 67 const strPart = "instance_parent_gb" 68 const intPart = int64(0) 69 r = ds.NewKey(c, kind_guestbk, strPart, intPart, nil) 70 return 71 } 72 73 // implementing the "stringer" interface 74 func (g GbEntryRetr) String() string { 75 76 b1 := new(bytes.Buffer) 77 78 b1.WriteString(g.Author + "<br>\n") 79 b1.WriteString(g.Content + "<br>\n") 80 f2 := "2006-01-02 (Jan 02)" 81 s2 := g.Date.Format(f2) 82 b1.WriteString(s2 + "<br>\n") 83 84 return b1.String() 85 } 86 87 func SaveEntry(w http.ResponseWriter, r *http.Request, m map[string]interface{}) { 88 89 contnt, ok := m["content"].(string) 90 loghttp.E(w, r, ok, false, "need a key 'content' with a string") 91 92 c := appengine.NewContext(r) 93 94 g := GbsaveEntry{ 95 Content: contnt, 96 Date: time.Now(), 97 TypeShiftingField: 2, 98 } 99 if u := user.Current(c); u != nil { 100 g.Author = u.String() 101 } 102 g.Comment1 = "comment" 103 104 /* We set the same parent key on every GB entry entity 105 to ensure each GB entry is in the same entity group. 106 107 Queries across the single entity group will be consistent. 108 However, we should limit write rate to single entity group ~1/sec. 109 110 NewIncompleteKey(appengine.Context, kind string , parent *Key) 111 has neither a string key, nor an integer key 112 only a "kind" (classname) and a parent 113 Upon usage the datastore generates an integer key 114 */ 115 116 incomplete := ds.NewIncompleteKey(c, GbEntryKind, keyParent(c)) 117 concreteNewKey, err := ds.Put(c, incomplete, &g) 118 loghttp.E(w, r, err, false) 119 _ = concreteNewKey // we query entries via keyParent - via parent 120 121 } 122 123 func ListEntries(w http.ResponseWriter, 124 r *http.Request) (gbEntries []GbEntryRetr, report string) { 125 126 c := appengine.NewContext(r) 127 /* High Replication Datastore: 128 Ancestor queries are strongly consistent. 129 Queries spanning MULTIPLE entity groups are EVENTUALLY consistent. 130 If .Ancestor was omitted from this query, there would be slight chance 131 that recent GB entry would not show up in a query. 132 */ 133 q := ds.NewQuery(GbEntryKind).Ancestor(keyParent(c)).Order("-Date").Limit(10) 134 gbEntries = make([]GbEntryRetr, 0, 10) 135 keys, err := q.GetAll(c, &gbEntries) 136 137 if fmt.Sprintf("%T", err) == fmt.Sprintf("%T", new(ds.ErrFieldMismatch)) { 138 //s := fmt.Sprintf("%v %T vs %v %T <br>\n",err,err,ds.ErrFieldMismatch{},ds.ErrFieldMismatch{}) 139 loghttp.E(w, r, err, true) 140 err = nil // ignore this one - it's caused by our deliberate differences between gbsaveEntry and gbEntrieRetr 141 } 142 loghttp.E(w, r, err, false) 143 144 // for investigative purposes, 145 // we 146 var b1 bytes.Buffer 147 var sw string 148 var descrip []string = []string{"class", "path", "key_int_guestbk"} 149 for i0, v0 := range keys { 150 sKey := fmt.Sprintf("%v", v0) 151 v1 := strings.Split(sKey, ",") 152 sw = fmt.Sprintf("key %v", i0) 153 b1.WriteString(sw) 154 for i2, v2 := range v1 { 155 d := descrip[i2] 156 sw = fmt.Sprintf(" \t %v: %q ", d, v2) 157 b1.WriteString(sw) 158 } 159 b1.WriteString("\n") 160 } 161 report = b1.String() 162 163 for _, gbe := range gbEntries { 164 s := gbe.Comment1 165 if len(s) > 0 { 166 if pos := strings.Index(s, "0300"); pos > 1 { 167 i1 := util.Max(pos-4, 0) 168 i2 := util.Min(pos+24, len(s)) 169 s1 := s[i1:i2] 170 s1 = strings.Replace(s1, "3", "E", -1) 171 report = fmt.Sprintf("%v -%v", report, s1) 172 } 173 } 174 } 175 176 return 177 }