Skip to content
quarry Artifact [816bda25a2]
Login

Artifact [816bda25a2]

Artifact 816bda25a206e04293a5158b96c2fa6d6a777e8bc87ecc64e030b3df9f278ad7:


// 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
}