github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/poll_store.go (about) 1 package common 2 3 import ( 4 "database/sql" 5 "encoding/json" 6 "errors" 7 "log" 8 "strconv" 9 10 qgen "github.com/Azareal/Gosora/query_gen" 11 ) 12 13 var Polls PollStore 14 15 type PollOption struct { 16 ID int 17 Value string 18 } 19 20 type Pollable interface { 21 GetID() int 22 GetTable() string 23 SetPoll(pollID int) error 24 } 25 26 type PollStore interface { 27 Get(id int) (*Poll, error) 28 Exists(id int) bool 29 ClearIPs() error 30 Create(parent Pollable, pollType int, pollOptions map[int]string) (int, error) 31 Reload(id int) error 32 Count() int 33 34 SetCache(cache PollCache) 35 GetCache() PollCache 36 } 37 38 type DefaultPollStore struct { 39 cache PollCache 40 41 get *sql.Stmt 42 exists *sql.Stmt 43 createPoll *sql.Stmt 44 createPollOption *sql.Stmt 45 delete *sql.Stmt 46 count *sql.Stmt 47 48 clearIPs *sql.Stmt 49 } 50 51 func NewDefaultPollStore(cache PollCache) (*DefaultPollStore, error) { 52 acc := qgen.NewAcc() 53 if cache == nil { 54 cache = NewNullPollCache() 55 } 56 // TODO: Add an admin version of registerStmt with more flexibility? 57 p := "polls" 58 return &DefaultPollStore{ 59 cache: cache, 60 get: acc.Select(p).Columns("parentID,parentTable,type,options,votes").Where("pollID=?").Stmt(), 61 exists: acc.Select(p).Columns("pollID").Where("pollID=?").Stmt(), 62 createPoll: acc.Insert(p).Columns("parentID,parentTable,type,options").Fields("?,?,?,?").Prepare(), 63 createPollOption: acc.Insert("polls_options").Columns("pollID,option,votes").Fields("?,?,0").Prepare(), 64 count: acc.Count(p).Prepare(), 65 66 clearIPs: acc.Update("polls_votes").Set("ip=''").Where("ip!=''").Stmt(), 67 }, acc.FirstError() 68 } 69 70 func (s *DefaultPollStore) Exists(id int) bool { 71 e := s.exists.QueryRow(id).Scan(&id) 72 if e != nil && e != ErrNoRows { 73 LogError(e) 74 } 75 return e != ErrNoRows 76 } 77 78 func (s *DefaultPollStore) Get(id int) (*Poll, error) { 79 p, err := s.cache.Get(id) 80 if err == nil { 81 return p, nil 82 } 83 84 p = &Poll{ID: id} 85 var optionTxt []byte 86 err = s.get.QueryRow(id).Scan(&p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount) 87 if err != nil { 88 return nil, err 89 } 90 91 err = json.Unmarshal(optionTxt, &p.Options) 92 if err == nil { 93 p.QuickOptions = s.unpackOptionsMap(p.Options) 94 s.cache.Set(p) 95 } 96 return p, err 97 } 98 99 // TODO: Optimise the query to avoid preparing it on the spot? Maybe, use knowledge of the most common IN() parameter counts? 100 // TODO: ID of 0 should always error? 101 func (s *DefaultPollStore) BulkGetMap(ids []int) (list map[int]*Poll, err error) { 102 idCount := len(ids) 103 list = make(map[int]*Poll) 104 if idCount == 0 { 105 return list, nil 106 } 107 108 var stillHere []int 109 sliceList := s.cache.BulkGet(ids) 110 for i, sliceItem := range sliceList { 111 if sliceItem != nil { 112 list[sliceItem.ID] = sliceItem 113 } else { 114 stillHere = append(stillHere, ids[i]) 115 } 116 } 117 ids = stillHere 118 119 // If every user is in the cache, then return immediately 120 if len(ids) == 0 { 121 return list, nil 122 } 123 124 idList, q := inqbuild(ids) 125 rows, err := qgen.NewAcc().Select("polls").Columns("pollID,parentID,parentTable,type,options,votes").Where("pollID IN(" + q + ")").Query(idList...) 126 if err != nil { 127 return list, err 128 } 129 130 for rows.Next() { 131 p := &Poll{ID: 0} 132 var optionTxt []byte 133 err := rows.Scan(&p.ID, &p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount) 134 if err != nil { 135 return list, err 136 } 137 138 err = json.Unmarshal(optionTxt, &p.Options) 139 if err != nil { 140 return list, err 141 } 142 p.QuickOptions = s.unpackOptionsMap(p.Options) 143 s.cache.Set(p) 144 145 list[p.ID] = p 146 } 147 148 // Did we miss any polls? 149 if idCount > len(list) { 150 var sidList string 151 for _, id := range ids { 152 if _, ok := list[id]; !ok { 153 sidList += strconv.Itoa(id) + "," 154 } 155 } 156 157 // We probably don't need this, but it might be useful in case of bugs in BulkCascadeGetMap 158 if sidList == "" { 159 // TODO: Bulk log this 160 if Dev.DebugMode { 161 log.Print("This data is sampled later in the BulkCascadeGetMap function, so it might miss the cached IDs") 162 log.Print("idCount", idCount) 163 log.Print("ids", ids) 164 log.Print("list", list) 165 } 166 return list, errors.New("We weren't able to find a poll, but we don't know which one") 167 } 168 sidList = sidList[0 : len(sidList)-1] 169 170 err = errors.New("Unable to find the polls with the following IDs: " + sidList) 171 } 172 173 return list, err 174 } 175 176 func (s *DefaultPollStore) Reload(id int) error { 177 p := &Poll{ID: id} 178 var optionTxt []byte 179 e := s.get.QueryRow(id).Scan(&p.ParentID, &p.ParentTable, &p.Type, &optionTxt, &p.VoteCount) 180 if e != nil { 181 _ = s.cache.Remove(id) 182 return e 183 } 184 e = json.Unmarshal(optionTxt, &p.Options) 185 if e != nil { 186 _ = s.cache.Remove(id) 187 return e 188 } 189 p.QuickOptions = s.unpackOptionsMap(p.Options) 190 _ = s.cache.Set(p) 191 return nil 192 } 193 194 func (s *DefaultPollStore) unpackOptionsMap(rawOptions map[int]string) []PollOption { 195 opts := make([]PollOption, len(rawOptions)) 196 for id, opt := range rawOptions { 197 opts[id] = PollOption{id, opt} 198 } 199 return opts 200 } 201 202 func (s *DefaultPollStore) ClearIPs() error { 203 _, e := s.clearIPs.Exec() 204 return e 205 } 206 207 // TODO: Use a transaction for this 208 func (s *DefaultPollStore) Create(parent Pollable, pollType int, pollOptions map[int]string) (id int, e error) { 209 // TODO: Move the option names into the polls_options table and get rid of this json sludge? 210 pollOptionsTxt, e := json.Marshal(pollOptions) 211 if e != nil { 212 return 0, e 213 } 214 res, e := s.createPoll.Exec(parent.GetID(), parent.GetTable(), pollType, pollOptionsTxt) 215 if e != nil { 216 return 0, e 217 } 218 lastID, e := res.LastInsertId() 219 if e != nil { 220 return 0, e 221 } 222 223 for i := 0; i < len(pollOptions); i++ { 224 _, e := s.createPollOption.Exec(lastID, i) 225 if e != nil { 226 return 0, e 227 } 228 } 229 230 id = int(lastID) 231 return id, parent.SetPoll(id) // TODO: Delete the poll (and options) if SetPoll fails 232 } 233 234 func (s *DefaultPollStore) Count() int { 235 return Count(s.count) 236 } 237 238 func (s *DefaultPollStore) SetCache(cache PollCache) { 239 s.cache = cache 240 } 241 242 // TODO: We're temporarily doing this so that you can do ucache != nil in getTopicUser. Refactor it. 243 func (s *DefaultPollStore) GetCache() PollCache { 244 _, ok := s.cache.(*NullPollCache) 245 if ok { 246 return nil 247 } 248 return s.cache 249 }