package store import ( "context" "database/sql" "errors" "time" ) // ErrSettingsChanged is a change made against a version of the cluster's // settings that is no longer the newest: somebody else changed them first. var ErrSettingsChanged = errors.New("the cluster's settings were changed since they were read; read them again") // SettingsVersion is one version of the cluster's settings. type SettingsVersion struct { Version int64 Body string Note string At time.Time By string } // ClusterSettings is the newest version, Version 0 when there is none yet. func (s *DB) ClusterSettings(ctx context.Context) (SettingsVersion, error) { return s.settingsWhere(ctx, `version = (SELECT MAX(version) FROM cluster_settings)`) } // ClusterSettingsAt is one version, Version 0 when there is no such version. func (s *DB) ClusterSettingsAt(ctx context.Context, version int64) (SettingsVersion, error) { return s.settingsWhere(ctx, `version = ?`, version) } func (s *DB) settingsWhere(ctx context.Context, where string, args ...any) (SettingsVersion, error) { var v SettingsVersion var at int64 err := s.db.QueryRowContext(ctx, `SELECT version, body, note, created_at, created_by FROM cluster_settings WHERE `+where, args...). Scan(&v.Version, &v.Body, &v.Note, &at, &v.By) if errors.Is(err, sql.ErrNoRows) { return SettingsVersion{}, nil } v.At = time.UnixMilli(at) return v, err } // ClusterSettingsVersion is the newest version's number, 0 when there is // none: what every control plane asks every few seconds. func (s *DB) ClusterSettingsVersion(ctx context.Context) (int64, error) { var v sql.NullInt64 err := s.db.QueryRowContext(ctx, `SELECT MAX(version) FROM cluster_settings`).Scan(&v) return v.Int64, err } // PutClusterSettings records a new version made from base, the version the // change was read from, and returns its number. Refused with // ErrSettingsChanged when base is not the newest. func (s *DB) PutClusterSettings(ctx context.Context, base int64, body, by, note string) (int64, error) { cur, err := s.ClusterSettingsVersion(ctx) if err != nil { return 0, err } if cur != base { return 0, ErrSettingsChanged } // The key settles a race the read above cannot: of two writers from the // same base, one inserts base+1 and the other is refused. _, err = s.db.ExecContext(ctx, `INSERT INTO cluster_settings (version, body, note, created_at, created_by) VALUES (?, ?, ?, ?, ?)`, base+1, body, note, time.Now().UnixMilli(), by) if isUniqueViolation(err) { return 0, ErrSettingsChanged } if err != nil { return 0, err } return base + 1, nil } // ClusterSettingsHistory is the newest versions first, at most limit. func (s *DB) ClusterSettingsHistory(ctx context.Context, limit int) ([]SettingsVersion, error) { rows, err := s.db.QueryContext(ctx, `SELECT version, body, note, created_at, created_by FROM cluster_settings ORDER BY version DESC LIMIT ?`, limit) if err != nil { return nil, err } defer rows.Close() var out []SettingsVersion for rows.Next() { var v SettingsVersion var at int64 if err := rows.Scan(&v.Version, &v.Body, &v.Note, &at, &v.By); err != nil { return nil, err } v.At = time.UnixMilli(at) out = append(out, v) } return out, rows.Err() }