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  }