// ticketnumber.go is the forge's ticket numbering (54c5175f, option C):
// one sequence per repository the forge owns, in creation order across
// the local tier and the store. The forge is the allocator because a
// store ticket is minted by whoever holds a clone, with no authority in
// the middle; this database is one. The hex id stays the address; the
// number is what people say.
package oltp
import (
"database/sql"
"errors"
)
const ticketNumberSchema = `
CREATE TABLE IF NOT EXISTS ticket_number (
repo TEXT NOT NULL,
number INTEGER NOT NULL,
ticket TEXT NOT NULL,
PRIMARY KEY (repo, number),
UNIQUE (repo, ticket)
);`
// TicketNumbersAssign numbers, in the order given, every ticket in ids
// that has no number yet, continuing the repository's sequence. Already
// numbered tickets keep theirs, so calling it again is harmless.
func (d *DB) TicketNumbersAssign(repo string, ids []string) error {
if len(ids) == 0 {
return nil
}
tx, err := d.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
var max int
if err := tx.QueryRow(`SELECT COALESCE(MAX(number), 0) FROM ticket_number WHERE repo = ?`, repo).Scan(&max); err != nil {
return err
}
for _, id := range ids {
var have int
err := tx.QueryRow(`SELECT number FROM ticket_number WHERE repo = ? AND ticket = ?`, repo, id).Scan(&have)
if err == nil {
continue
}
if !errors.Is(err, sql.ErrNoRows) {
return err
}
max++
if _, err := tx.Exec(`INSERT INTO ticket_number (repo, number, ticket) VALUES (?, ?, ?)`, repo, max, id); err != nil {
return err
}
}
return tx.Commit()
}
// TicketNumbers is every numbered ticket of a repository, id → number.
func (d *DB) TicketNumbers(repo string) (map[string]int, error) {
rows, err := d.db.Query(`SELECT ticket, number FROM ticket_number WHERE repo = ?`, repo)
if err != nil {
return nil, err
}
defer rows.Close()
out := map[string]int{}
for rows.Next() {
var id string
var n int
if err := rows.Scan(&id, &n); err != nil {
return nil, err
}
out[id] = n
}
return out, rows.Err()
}
// TicketNumbered is the ticket a repository's number names.
func (d *DB) TicketNumbered(repo string, n int) (string, bool, error) {
var id string
err := d.db.QueryRow(`SELECT ticket FROM ticket_number WHERE repo = ? AND number = ?`, repo, n).Scan(&id)
if errors.Is(err, sql.ErrNoRows) {
return "", false, nil
}
return id, err == nil, err
}
// TicketNumberRekey moves a number from one id to another — a local
// ticket's number to the artifact it was promoted into, so the name
// people used keeps naming the same ticket.
func (d *DB) TicketNumberRekey(repo, from, to string) error {
_, err := d.db.Exec(`UPDATE ticket_number SET ticket = ? WHERE repo = ? AND ticket = ?`, to, repo, from)
return err
}