Skip to content

Commit a9be4e7

Browse files
committed
feat(entities) Adds speculation tree store interface and mysql implementation
1 parent 27dd740 commit a9be4e7

7 files changed

Lines changed: 162 additions & 0 deletions

File tree

extension/storage/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ go_library(
88
"build_store.go",
99
"change_provider_store.go",
1010
"request_store.go",
11+
"speculation_tree_store.go",
1112
"storage.go",
1213
],
1314
importpath = "github.com/uber/submitqueue/extension/storage",

extension/storage/mysql/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ go_library(
88
"build_store.go",
99
"change_provider_store.go",
1010
"request_store.go",
11+
"speculation_tree_store.go",
1112
"storage.go",
1213
],
1314
importpath = "github.com/uber/submitqueue/extension/storage/mysql",
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package mysql
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
10+
"github.com/go-sql-driver/mysql"
11+
12+
"github.com/uber/submitqueue/entity"
13+
"github.com/uber/submitqueue/extension/storage"
14+
)
15+
16+
type speculationTreeStore struct {
17+
db *sql.DB
18+
}
19+
20+
// NewSpeculationTreeStore creates a new MySQL-backed SpeculationTreeStore.
21+
func NewSpeculationTreeStore(db *sql.DB) storage.SpeculationTreeStore {
22+
return &speculationTreeStore{db: db}
23+
}
24+
25+
// Get retrieves the speculation tree by batch ID. Returns ErrNotFound if the speculation tree is not found.
26+
func (s *speculationTreeStore) Get(ctx context.Context, batchID string) (entity.SpeculationTree, error) {
27+
var st entity.SpeculationTree
28+
var speculationsJSON []byte
29+
30+
err := s.db.QueryRowContext(ctx,
31+
"SELECT batch_id, queue, speculations FROM speculation_tree WHERE batch_id = ?",
32+
batchID,
33+
).Scan(&st.BatchID, &st.Queue, &speculationsJSON)
34+
35+
if errors.Is(err, sql.ErrNoRows) {
36+
return entity.SpeculationTree{}, storage.WrapNotFound(err)
37+
}
38+
if err != nil {
39+
return entity.SpeculationTree{}, fmt.Errorf("failed to get speculation tree entity batchID=%s from the database: %w", batchID, err)
40+
}
41+
42+
if err := json.Unmarshal(speculationsJSON, &st.Speculations); err != nil {
43+
return entity.SpeculationTree{}, fmt.Errorf("failed to unmarshal speculations for speculation tree entity batchID=%s from the database: %w", batchID, err)
44+
}
45+
46+
return st, nil
47+
}
48+
49+
// Create creates a new speculation tree. Returns ErrAlreadyExists if the entry already exists.
50+
func (s *speculationTreeStore) Create(ctx context.Context, speculationTree entity.SpeculationTree) error {
51+
speculationsJSON, err := json.Marshal(speculationTree.Speculations)
52+
if err != nil {
53+
return fmt.Errorf("failed to marshal speculations batchID=%s for Create speculation tree entity: %w", speculationTree.BatchID, err)
54+
}
55+
56+
_, err = s.db.ExecContext(ctx,
57+
"INSERT INTO speculation_tree (batch_id, queue, speculations) VALUES (?, ?, ?)",
58+
speculationTree.BatchID, speculationTree.Queue, speculationsJSON,
59+
)
60+
if err != nil {
61+
var mysqlErr *mysql.MySQLError
62+
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
63+
return fmt.Errorf("speculation tree entity batchID=%s: %w", speculationTree.BatchID, storage.ErrAlreadyExists)
64+
}
65+
return fmt.Errorf("failed to insert speculation tree entity batchID=%s: %w", speculationTree.BatchID, err)
66+
}
67+
68+
return nil
69+
}
70+
71+
// UpdateSpeculations updates the speculations of a speculation tree. Returns ErrNotFound if the speculation tree is not found.
72+
func (s *speculationTreeStore) UpdateSpeculations(ctx context.Context, batchID string, speculations []map[string]string) error {
73+
speculationsJSON, err := json.Marshal(speculations)
74+
if err != nil {
75+
return fmt.Errorf("failed to marshal speculations batchID=%s for UpdateSpeculations: %w", batchID, err)
76+
}
77+
78+
result, err := s.db.ExecContext(ctx,
79+
"UPDATE speculation_tree SET speculations = ? WHERE batch_id = ?",
80+
speculationsJSON, batchID,
81+
)
82+
if err != nil {
83+
return fmt.Errorf("failed to update speculations for batchID=%q: %w", batchID, err)
84+
}
85+
86+
rowsAffected, err := result.RowsAffected()
87+
if err != nil {
88+
return fmt.Errorf("failed to get rows affected from update for batchID=%q: %w", batchID, err)
89+
}
90+
91+
if rowsAffected != 1 {
92+
return storage.WrapNotFound(fmt.Errorf("speculation tree entity batchID=%s", batchID))
93+
}
94+
95+
return nil
96+
}

extension/storage/mysql/storage.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type mysqlStorage struct {
1515
batchStore storage.BatchStore
1616
batchDependentStore storage.BatchDependentStore
1717
buildStore storage.BuildStore
18+
speculationTreeStore storage.SpeculationTreeStore
1819
}
1920

2021
// NewStorage creates a new MySQL storage.
@@ -26,6 +27,7 @@ func NewStorage(db *sql.DB) (storage.Storage, error) {
2627
batchStore: NewBatchStore(db),
2728
batchDependentStore: NewBatchDependentStore(db),
2829
buildStore: NewBuildStore(db),
30+
speculationTreeStore: NewSpeculationTreeStore(db),
2931
}, nil
3032
}
3133

@@ -54,6 +56,11 @@ func (f *mysqlStorage) GetBuildStore() storage.BuildStore {
5456
return f.buildStore
5557
}
5658

59+
// GetSpeculationTreeStore returns the MySQL-backed SpeculationTreeStore.
60+
func (f *mysqlStorage) GetSpeculationTreeStore() storage.SpeculationTreeStore {
61+
return f.speculationTreeStore
62+
}
63+
5764
// Close closes the underlying database connection.
5865
func (f *mysqlStorage) Close() error {
5966
return f.db.Close()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package storage
2+
3+
import (
4+
"context"
5+
6+
"github.com/uber/submitqueue/entity"
7+
)
8+
9+
// SpeculationTreeStore is an interface that defines methods for managing speculation trees in the database.
10+
type SpeculationTreeStore interface {
11+
// Get retrieves the speculation tree by batch ID.
12+
// Returns ErrNotFound if the speculation tree is not found.
13+
Get(ctx context.Context, batchID string) (entity.SpeculationTree, error)
14+
15+
// Create creates a new speculation tree.
16+
// Returns ErrAlreadyExists if the entry already exists.
17+
Create(ctx context.Context, speculationTree entity.SpeculationTree) error
18+
19+
// UpdateSpeculations updates the speculations of a speculation tree.
20+
// Returns ErrNotFound if the speculation tree is not found.
21+
UpdateSpeculations(ctx context.Context, batchID string, speculations []map[string]string) error
22+
}

extension/storage/storage.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ type Storage interface {
4141
// GetBuildStore returns the BuildStore instance.
4242
GetBuildStore() BuildStore
4343

44+
// GetSpeculationTreeStore returns the SpeculationTreeStore instance.
45+
GetSpeculationTreeStore() SpeculationTreeStore
46+
4447
// Close closes the storage and all underlying connections. Should only be called once at the end of the program.
4548
Close() error
4649
}

gateway/controller/land_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,40 @@ func (m *mockBuildStore) UpdateStatus(ctx context.Context, id string, newStatus
124124
return nil
125125
}
126126

127+
type mockSpeculationTreeStore struct {
128+
createFunc func(ctx context.Context, speculationTree entity.SpeculationTree) error
129+
getFunc func(ctx context.Context, batchID string) (entity.SpeculationTree, error)
130+
updateSpeculationsFunc func(ctx context.Context, batchID string, speculations []map[string]string) error
131+
}
132+
133+
func (m *mockSpeculationTreeStore) Get(ctx context.Context, batchID string) (entity.SpeculationTree, error) {
134+
if m.getFunc != nil {
135+
return m.getFunc(ctx, batchID)
136+
}
137+
return entity.SpeculationTree{}, nil
138+
}
139+
140+
func (m *mockSpeculationTreeStore) Create(ctx context.Context, speculationTree entity.SpeculationTree) error {
141+
if m.createFunc != nil {
142+
return m.createFunc(ctx, speculationTree)
143+
}
144+
return nil
145+
}
146+
147+
func (m *mockSpeculationTreeStore) UpdateSpeculations(ctx context.Context, batchID string, speculations []map[string]string) error {
148+
if m.updateSpeculationsFunc != nil {
149+
return m.updateSpeculationsFunc(ctx, batchID, speculations)
150+
}
151+
return nil
152+
}
153+
127154
type mockStorage struct {
128155
requestStore storage.RequestStore
129156
changeProviderStore storage.ChangeProviderStore
130157
batchStore storage.BatchStore
131158
batchDependentStore storage.BatchDependentStore
132159
buildStore storage.BuildStore
160+
speculationTreeStore storage.SpeculationTreeStore
133161
}
134162

135163
func (m *mockStorage) GetRequestStore() storage.RequestStore {
@@ -152,6 +180,10 @@ func (m *mockStorage) GetBuildStore() storage.BuildStore {
152180
return m.buildStore
153181
}
154182

183+
func (m *mockStorage) GetSpeculationTreeStore() storage.SpeculationTreeStore {
184+
return m.speculationTreeStore
185+
}
186+
155187
func (m *mockStorage) Close() error {
156188
return nil
157189
}

0 commit comments

Comments
 (0)