Skip to content
quarry Artifact [081cd8b47e]
Login

Artifact [081cd8b47e]

Artifact 081cd8b47ec2b97a106837706ab61a1032f6430a3a1d151216056e5028845bbe:


// poll.go stores poll ballots. The poll DEFINITION lives in the forum
// post's body (a ```poll fenced block — a fossil peer renders readable
// markdown, which is the whole compatibility story); only the ballots
// are forge state, with the same idempotent shape as forum votes: the
// (repo, account, post) key IS the one-ballot rule.
package oltp

import "time"

const pollSchema = `
CREATE TABLE IF NOT EXISTS poll_vote (
	repo    TEXT NOT NULL,
	account TEXT NOT NULL,
	post    TEXT NOT NULL,    -- the forum post carrying the poll block
	choice  INTEGER NOT NULL, -- option index in the block, 0-based
	at      TEXT NOT NULL,
	PRIMARY KEY (repo, account, post)
);
CREATE INDEX IF NOT EXISTS poll_vote_by_post ON poll_vote (repo, post);`

// SetPollVote records (replacing) one account's ballot; changeable
// until the poll closes, which the API enforces from the parsed block.
func (d *DB) SetPollVote(repo, account, post string, choice int) error {
	_, err := d.w.Exec(`INSERT INTO poll_vote (repo, account, post, choice, at)
		VALUES (?, ?, ?, ?, ?)
		ON CONFLICT (repo, account, post) DO UPDATE SET choice = excluded.choice, at = excluded.at`,
		repo, account, post, choice, time.Now().UTC().Format(time.RFC3339))
	return err
}

// PollCounts returns ballots per option index for one poll post.
func (d *DB) PollCounts(repo, post string) (map[int]int, error) {
	rows, err := d.db.Query(`SELECT choice, count(*) FROM poll_vote
		WHERE repo = ? AND post = ? GROUP BY choice`, repo, post)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := map[int]int{}
	for rows.Next() {
		var c, n int
		if err := rows.Scan(&c, &n); err != nil {
			return nil, err
		}
		out[c] = n
	}
	return out, rows.Err()
}

// PollChoice returns one account's ballot; ok is false when they have
// not voted (or are anonymous).
func (d *DB) PollChoice(repo, account, post string) (choice int, ok bool) {
	if account == "" {
		return 0, false
	}
	err := d.db.QueryRow(`SELECT choice FROM poll_vote
		WHERE repo = ? AND account = ? AND post = ?`, repo, account, post).Scan(&choice)
	return choice, err == nil
}