aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2026-01-13 12:59:57 +0100
committerDmitry Vyukov <dvyukov@google.com>2026-01-13 13:21:16 +0000
commit787e7cd0a324014b6ec3d4d830119c8d71439200 (patch)
treeb0c8ed6bfefafda2e812dc30f0451ec0d2bf6e1d
parente31fcc98a0656b952c90a2bcf6d044efb890e21a (diff)
dashboard/app: prepare for spanner migrations
If the code uses "select *", it's not possible to update spanner schema. Adding a field to spanner first leads to "missing field in Go struct" errors, adding a field to Go struct first leads to "missing field in spanner" errors. Replace "select *" with concrete set of fields the code knowns about. This should allow adding fields to spanner first.
-rw-r--r--dashboard/app/aidb/crud.go33
1 files changed, 27 insertions, 6 deletions
diff --git a/dashboard/app/aidb/crud.go b/dashboard/app/aidb/crud.go
index ccecc8e3f..a01c370b6 100644
--- a/dashboard/app/aidb/crud.go
+++ b/dashboard/app/aidb/crud.go
@@ -7,6 +7,7 @@ import (
"context"
"fmt"
"reflect"
+ "strings"
"time"
"cloud.google.com/go/spanner"
@@ -28,7 +29,7 @@ func init() {
func LoadWorkflows(ctx context.Context) ([]*Workflow, error) {
return selectAll[Workflow](ctx, spanner.Statement{
- SQL: `SELECT * FROM Workflows`,
+ SQL: selectWorkflows(),
})
}
@@ -113,7 +114,7 @@ func StartJob(ctx context.Context, req *dashapi.AIJobPollReq) (*Job, error) {
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
{
iter := txn.Query(ctx, spanner.Statement{
- SQL: `SELECT * FROM Jobs WHERE Workflow IN UNNEST(@workflows)
+ SQL: selectJobs() + `WHERE Workflow IN UNNEST(@workflows)
AND Started IS NULL
ORDER BY Created ASC LIMIT 1`,
Params: map[string]any{
@@ -144,7 +145,7 @@ func StartJob(ctx context.Context, req *dashapi.AIJobPollReq) (*Job, error) {
func LoadNamespaceJobs(ctx context.Context, ns string) ([]*Job, error) {
return selectAll[Job](ctx, spanner.Statement{
- SQL: `SELECT * FROM Jobs WHERE Namespace = @ns ORDER BY Created DESC`,
+ SQL: selectJobs() + `WHERE Namespace = @ns ORDER BY Created DESC`,
Params: map[string]any{
"ns": ns,
},
@@ -153,7 +154,7 @@ func LoadNamespaceJobs(ctx context.Context, ns string) ([]*Job, error) {
func LoadBugJobs(ctx context.Context, bugID string) ([]*Job, error) {
return selectAll[Job](ctx, spanner.Statement{
- SQL: `SELECT * FROM Jobs WHERE BugID = @bugID ORDER BY Created DESC`,
+ SQL: selectJobs() + `WHERE BugID = @bugID ORDER BY Created DESC`,
Params: map[string]any{
"bugID": bugID,
},
@@ -162,7 +163,7 @@ func LoadBugJobs(ctx context.Context, bugID string) ([]*Job, error) {
func LoadJob(ctx context.Context, id string) (*Job, error) {
return selectOne[Job](ctx, spanner.Statement{
- SQL: `SELECT * FROM Jobs WHERE ID = @id`,
+ SQL: selectJobs() + `WHERE ID = @id`,
Params: map[string]any{
"id": id,
},
@@ -201,7 +202,7 @@ func StoreTrajectorySpan(ctx context.Context, jobID string, span *trajectory.Spa
func LoadTrajectory(ctx context.Context, jobID string) ([]*TrajectorySpan, error) {
return selectAll[TrajectorySpan](ctx, spanner.Statement{
- SQL: `SELECT * FROM TrajectorySpans WHERE JobID = @job_id ORDER BY Seq ASC`,
+ SQL: selectTrajectorySpans() + `WHERE JobID = @job_id ORDER BY Seq ASC`,
Params: map[string]any{
"job_id": jobID,
},
@@ -252,6 +253,26 @@ var TimeNow = func(ctx context.Context) time.Time {
return time.Now()
}
+func selectWorkflows() string {
+ return selectAllFrom[Workflow]("Workflows")
+}
+
+func selectJobs() string {
+ return selectAllFrom[Job]("Jobs")
+}
+
+func selectTrajectorySpans() string {
+ return selectAllFrom[TrajectorySpan]("TrajectorySpans")
+}
+
+func selectAllFrom[T any](table string) string {
+ var fields []string
+ for _, field := range reflect.VisibleFields(reflect.TypeFor[T]()) {
+ fields = append(fields, field.Name)
+ }
+ return fmt.Sprintf("SELECT %v FROM %v ", strings.Join(fields, ", "), table)
+}
+
func toNullJSON(v map[string]any) spanner.NullJSON {
if v == nil {
return spanner.NullJSON{}