// companion.go is RFC 0015's binding: which separate store holds a
// repository's forum. Forge-local wiring, like a grant — wiping it
// loses the binding, never a fact the DAG holds, because the parent and
// the companion are each complete on their own.
package oltp
import (
"database/sql"
"time"
)
const companionSchema = `
CREATE TABLE IF NOT EXISTS repo_companion (
repo TEXT NOT NULL, -- the parent, as in repo.name
feature TEXT NOT NULL, -- 'forum' is the only value RFC 0015 defines
store TEXT NOT NULL, -- the companion, as in repo.name
since TEXT NOT NULL,
PRIMARY KEY (repo, feature),
UNIQUE (store) -- a store is one repository's companion, or nobody's
);`
// Companion is one binding row.
type Companion struct {
Repo, Feature, Store, Since string
}
// BindCompanion records that repo's feature lives in store. The two
// uniqueness rules are the table's: one companion per (repo, feature),
// and a store bound at most once. Refusing a store that is itself a
// parent is the op's job, since it needs the whole table to know.
func (d *DB) BindCompanion(repo, feature, store string) error {
_, err := d.w.Exec(`INSERT INTO repo_companion (repo, feature, store, since) VALUES (?, ?, ?, ?)`,
repo, feature, store, time.Now().UTC().Format(time.RFC3339))
return err
}
// UnbindCompanion removes the binding; the store stays.
func (d *DB) UnbindCompanion(repo, feature string) error {
_, err := d.w.Exec(`DELETE FROM repo_companion WHERE repo = ? AND feature = ?`, repo, feature)
return err
}
// Companion answers which store holds repo's feature, if any.
func (d *DB) Companion(repo, feature string) (store string, ok bool, err error) {
err = d.db.QueryRow(`SELECT store FROM repo_companion WHERE repo = ? AND feature = ?`, repo, feature).Scan(&store)
if err == sql.ErrNoRows {
return "", false, nil
}
if err != nil {
return "", false, err
}
return store, true, nil
}
// CompanionParent answers whose companion a store is, if anyone's.
func (d *DB) CompanionParent(store string) (repo, feature string, ok bool, err error) {
err = d.db.QueryRow(`SELECT repo, feature FROM repo_companion WHERE store = ?`, store).Scan(&repo, &feature)
if err == sql.ErrNoRows {
return "", "", false, nil
}
if err != nil {
return "", "", false, err
}
return repo, feature, true, nil
}
// Companions lists every binding.
func (d *DB) Companions() ([]Companion, error) {
rows, err := d.db.Query(`SELECT repo, feature, store, since FROM repo_companion ORDER BY repo, feature`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []Companion
for rows.Next() {
var c Companion
if err := rows.Scan(&c.Repo, &c.Feature, &c.Store, &c.Since); err != nil {
return nil, err
}
out = append(out, c)
}
return out, rows.Err()
}