blob: 7405d2fc6f6e73e1eef5c8b059a8e184a7e36b53 [file]
package expstorage
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/skia-dev/glog"
"go.skia.org/infra/go/database"
"go.skia.org/infra/go/util"
)
// Stores expectations in an SQL database without any caching.
type SQLExpectationsStoreV1 struct {
vdb *database.VersionedDB
}
// Helper struct to iterate the expecations table.
type ExpectationsRec struct {
ID int
TS int64
Exp *Expectations
UserID string
Err error
}
func NewSQLExpectationStoreV1(vdb *database.VersionedDB) *SQLExpectationsStoreV1 {
return &SQLExpectationsStoreV1{
vdb: vdb,
}
}
// IterExpectations iterates over the expecations table in time chronological
// order and returns a channel that can be iterated to get the expectations
// over time. If an error ocurs it will be set in Err field of ExpectatonsRec.
// It is inefficient in that it loads all records into memory before
// sending them to the returned channel. This was added to avoid more complicated
// DB interactions. This is used for migration only.
func (e *SQLExpectationsStoreV1) IterExpectations() <-chan *ExpectationsRec {
resultCh := make(chan *ExpectationsRec, 1)
go func() {
defer close(resultCh)
const getRecStmt = `SELECT id, userid, ts, expectations
FROM expectations
ORDER BY ts ASC`
// Get all recores.
allRecs := []*ExpectationsRec{}
rows, err := e.vdb.DB.Query(getRecStmt)
if err != nil {
resultCh <- &ExpectationsRec{Err: err}
return
}
defer util.Close(rows)
var last *ExpectationsRec = nil
for rows.Next() {
rec := &ExpectationsRec{}
var expJSON string
err = rows.Scan(&rec.ID, &rec.UserID, &rec.TS, &expJSON)
if err == nil {
err = json.Unmarshal([]byte(expJSON), &rec.Exp)
}
if err != nil {
rec.Err = err
resultCh <- rec
return
}
// Make sure timestamps and ids are strictly monotonically increasing.
if last != nil {
if (last.ID >= rec.ID) || (last.TS >= rec.TS) {
rec.Err = fmt.Errorf("Records are not increasing: %d->%d : %d -> %d", last.ID, rec.ID, last.TS, rec.TS)
resultCh <- rec
return
}
}
last = rec
allRecs = append(allRecs, rec)
}
glog.Infof("Read %d database records. Starting to fill channel.", len(allRecs))
for _, rec := range allRecs {
resultCh <- rec
}
}()
return resultCh
}
// See ExpectationsStore interface.
func (e *SQLExpectationsStoreV1) Get() (exp *Expectations, err error) {
// Load the newest record from the database.
stmt := `SELECT expectations
FROM expectations
ORDER BY ts DESC
LIMIT 1`
// Read the expectations. If there are no rows, that means we have no
// expectations yet.
var expJSON string
switch err := e.vdb.DB.QueryRow(stmt).Scan(&expJSON); {
case err == sql.ErrNoRows:
return NewExpectations(), nil
case err != nil:
return nil, err
}
var result Expectations
err = json.Unmarshal([]byte(expJSON), &result)
return &result, nil
}
// See ExpectationsStore interface.
func (e *SQLExpectationsStoreV1) Put(exp *Expectations, userId string) error {
// Sererialize the expectations to JSON and store them in the DB.
expectationsJSON, err := json.Marshal(exp)
if err != nil {
return err
}
ts := util.TimeStampMs()
stmt := `INSERT INTO expectations (userid, ts, expectations)
VALUES(?, ?, ?)`
_, err = e.vdb.DB.Exec(stmt, userId, ts, expectationsJSON)
return err
}
// See ExpectationsStore interface.
func (e *SQLExpectationsStoreV1) Changes() <-chan []string {
glog.Fatal("SQLExpectationsStore doesn't really support Changes.")
return nil
}