Two things that were only ever confusing, and one of them stopped a command working on the cluster. The store type was named SQLite and kept the name when PostgreSQL was added, so OpenPostgres returned a *SQLite and all seventy-odd methods on the production store were spelled after the database it was not running on. Nothing broke; people did. Earlier today I added three methods to *SQLite for a table that on this cluster only ever lives in PostgreSQL, and the name did not once look wrong. It is a field, which is all it ever was. `llmgw users` and `llmgw keys` defaulted --store to a SQLite path. On a deployment whose store is PostgreSQL every one of them failed with "unable to open database file" while the right answer sat in auth.store in the config file beside them; the conclusion an operator draws is that the tool is broken, and they are not far off. They now ask the server's own configuration before falling back, and the error names which of the three sources chose the address — the failure was never the path, it was which one picked it. The single-machine default still applies last, so a deployment with no config file keeps working with no flags. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
69 lines
2.0 KiB
Go
69 lines
2.0 KiB
Go
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()
|
|
}
|