package store import ( "context" "encoding/json" "fmt" "time" ) // StoredModel is a model added at runtime rather than declared in configuration. type StoredModel struct { Name string // Definition is the catalog entry, as JSON. Stored whole because a profile // is a nested and still-growing shape: as columns, every field added to it // would be a migration, and the catalog already knows how to validate one. Definition []byte CreatedAt time.Time CreatedBy string } // PutModel adds or replaces a runtime model. func (s *DB) PutModel(ctx context.Context, m StoredModel) error { if m.Name == "" { return fmt.Errorf("a model needs a name") } if !json.Valid(m.Definition) { return fmt.Errorf("model %q: the definition is not valid JSON", m.Name) } if m.CreatedAt.IsZero() { m.CreatedAt = time.Now() } _, err := s.db.ExecContext(ctx, `INSERT INTO models (name, definition, created_at, created_by) VALUES (?, ?, ?, ?) ON CONFLICT(name) DO UPDATE SET definition = excluded.definition`, m.Name, string(m.Definition), m.CreatedAt, m.CreatedBy) return err } // DeleteModel removes a runtime model. Removing one that is not there is not an // error: the caller wanted it gone, and it is. func (s *DB) DeleteModel(ctx context.Context, name string) error { _, err := s.db.ExecContext(ctx, `DELETE FROM models WHERE name = ?`, name) return err } // Models lists the runtime models, oldest first so the catalog is built in a // stable order and two starts produce the same result. func (s *DB) Models(ctx context.Context) ([]StoredModel, error) { rows, err := s.db.QueryContext(ctx, `SELECT name, definition, created_at, created_by FROM models ORDER BY created_at, name`) if err != nil { return nil, err } defer rows.Close() var out []StoredModel for rows.Next() { var m StoredModel var def string if err := rows.Scan(&m.Name, &def, &m.CreatedAt, &m.CreatedBy); err != nil { return nil, err } m.Definition = []byte(def) out = append(out, m) } return out, rows.Err() }