-
Notifications
You must be signed in to change notification settings - Fork 1
/
eventraces.go
241 lines (219 loc) · 8.18 KB
/
eventraces.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package dbrepo
import (
"context"
"fmt"
"github.com/jimmc/jraceman/dbrepo/conn"
"github.com/jimmc/jraceman/domain"
"github.com/golang/glog"
"gopkg.in/d4l3k/messagediff.v1"
)
type DBEventRacesRepo struct {
db conn.DB
repos *Repos // We need access to repos for the table types
}
func (r *DBEventRacesRepo) RequireTx(ctx context.Context) (commit func() error, rollback func(), rr *DBEventRacesRepo, err error) {
commitf, rollbackf, rrepos, err := r.repos.RequireTx(ctx)
if err != nil {
return nil, nil, nil, err
}
return commitf, rollbackf, rrepos.EventRaces().(*DBEventRacesRepo), nil
}
func (r *DBEventRacesRepo) EventRaceInfo(eventId string) (*domain.EventRaces, error) {
if eventId == "" {
return nil, fmt.Errorf("Event ID must be specified")
}
db := r.db
// Collect summary info about the entries for this event.
entryCountQuery :=
`(SELECT count(1) as entryCount
FROM entry JOIN event on entry.eventid = event.id
WHERE event.id=? AND NOT entry.scratched)`
groupCountQuery :=
`(SELECT count(distinct groupname) as groupCount
FROM entry JOIN event on entry.eventid = event.id
WHERE event.id=? AND NOT entry.scratched)`
query := "SELECT "+entryCountQuery+" as EntryCount,"+
groupCountQuery+` as GroupCount,
COALESCE(competition.groupsize,0) as GroupSize,
event.Name || ' [' || event.ID || ']' as Summary,
COALESCE(event.areaid,"") as areaID,
COALESCE(area.Name,"") as areaName,
COALESCE(area.Lanes,0) as areaLanes,
COALESCE(area.ExtraLanes,0) as areaExtraLanes,
COALESCE(event.ProgressionState,"") as progressionState
FROM event
LEFT JOIN competition on event.competitionid = competition.id
LEFT JOIN area on event.areaid = area.id
WHERE event.id=?`
whereVals := make([]interface{}, 3)
whereVals[0] = eventId
whereVals[1] = eventId
whereVals[2] = eventId
glog.V(3).Infof("SQL: %s", query)
result := &domain.EventRaces{}
err := db.QueryRow(query, whereVals...).Scan(
&result.EntryCount, &result.GroupCount, &result.GroupSize, &result.Summary,
&result.AreaID, &result.AreaName, &result.AreaLanes, &result.AreaExtraLanes,
&result.ProgressionState)
if err != nil {
return nil, fmt.Errorf("Error collecting event %q info: %w", eventId, err)
}
result.EventID = eventId
// Collect information about the races for this event.
races, err := loadEventRaces(db, eventId)
if err != nil {
return nil, fmt.Errorf("Error collecting races for event %q: %w", eventId, err)
}
result.Races = races
// Get the count of races that exist for this event.
roundCounts, err := loadEventRoundCounts(db, eventId)
if err != nil {
return nil, fmt.Errorf("Error collecting round counts for event %q: %w", eventId, err)
}
result.RoundCounts = roundCounts
return result, nil
}
func loadEventRaces(db conn.DB, eventId string) ([]*domain.RaceInfo, error) {
laneCountQuery :=
`(SELECT lCount FROM
(SELECT count(1) as lCount, lrace.id as lraceid
FROM lane JOIN race as lrace on lane.raceid = lrace.id
GROUP BY lrace.id) as LaneCounts
WHERE LaneCounts.lraceid = race.id)`
query := `SELECT COALESCE(stage.name,"") as StageName,
COALESCE(stage.number,0) as StageNumber, COALESCE(stage.isfinal,false) as IsFinal,
race.round as Round, race.section as Section,
COALESCE(area.name,"") as AreaName, COALESCE(race.number,0) as RaceNumber, race.ID as RaceID,
COALESCE(`+laneCountQuery+`,0) as LaneCount,
COALESCE(race.stageid,"") as StageID, COALESCE(race.areaid,"") as AreaID
FROM race LEFT JOIN stage on race.stageid = stage.id
LEFT JOIN area on race.areaid = area.id
WHERE race.eventid = ?
ORDER BY race.round, race.section`
whereVals := make([]interface{}, 1)
whereVals[0] = eventId
glog.V(3).Infof("SQL: %s; with whereVals=%v", query, whereVals)
rows, err := db.Query(query, whereVals...)
if err != nil {
return nil, fmt.Errorf("Error collecting races for event %q: %w", eventId, err)
}
defer rows.Close()
rr := make([]*domain.RaceInfo,0)
for rows.Next() {
r := &domain.RaceInfo{}
if err = rows.Scan(&r.StageName, &r.StageNumber, &r.IsFinal,
&r.Round, &r.Section, &r.AreaName, &r.RaceNumber, &r.RaceID, &r.LaneCount, &r.StageID,
&r.AreaID); err != nil {
return nil, fmt.Errorf("Error collecting race data for event %q: %w", eventId, err)
}
r.EventID = eventId
rr = append(rr, r)
}
return rr, nil
}
func loadEventRoundCounts(db conn.DB, eventId string) ([]*domain.EventRoundCounts, error) {
query := `SELECT count(1) as count, race.round as round,
race.stageid as StageID, stage.name as StageName
FROM event JOIN race on event.id = race.eventid
JOIN stage on race.stageid=stage.id
WHERE event.id=?
GROUP BY race.round
ORDER BY race.round
`
whereVals := make([]interface{}, 1)
whereVals[0] = eventId
glog.V(3).Infof("SQL: %s; with whereVals=%v", query, whereVals)
rows, err := db.Query(query, whereVals...)
if err != nil {
return nil, fmt.Errorf("Error collecting race info for event %q: %w", eventId, err)
}
defer rows.Close()
rr := make([]*domain.EventRoundCounts,0)
for rows.Next() {
r := &domain.EventRoundCounts{}
if err = rows.Scan(&r.Count, &r.Round, &r.StageID, &r.StageName); err != nil {
return nil, fmt.Errorf("Error collecting race count row for event %q: %w", eventId, err)
}
rr = append(rr, r)
}
return rr, nil
}
// UpdateRaceInfo updates the database to create, delete, and modify races
// according to the given data.
func (r *DBEventRacesRepo) UpdateRaceInfo(ctx context.Context, eventRaces *domain.EventRaces,
racesToCreate, racesToDelete, racesToModFrom, racesToModTo []*domain.RaceInfo) error {
// We want all our writes to succeed or fail together.
commit, rollback, r, err := r.RequireTx(ctx)
if err!=nil {
return err
}
defer rollback()
err = r.updateRaceInfoInTx(ctx, eventRaces, racesToCreate, racesToDelete, racesToModFrom, racesToModTo)
if err != nil {
return err
}
if err = commit(); err!=nil {
return err
}
return nil
}
// UpdateRaceInfo updates the database to create, delete, and modify races
// according to the given data. Return error if any operations fail.
func (r *DBEventRacesRepo) updateRaceInfoInTx(ctx context.Context, eventRaces *domain.EventRaces,
racesToCreate, racesToDelete, racesToModFrom, racesToModTo []*domain.RaceInfo) error {
// Create
for _, raceInfo := range racesToCreate {
race := raceInfoToRace(raceInfo)
id, err := r.repos.Race().Save(race)
if err!=nil {
return fmt.Errorf("error creating race for event ID=%q: %w", raceInfo.EventID, err)
}
raceInfo.RaceID = id
}
// Delete
for _, raceInfo := range racesToDelete {
if err := r.repos.Race().DeleteByID(raceInfo.RaceID); err!=nil {
return fmt.Errorf("error deleting race ID=%q: %w", raceInfo.RaceID, err)
}
}
// Update
for i, raceInfo := range racesToModFrom {
oldRace := raceInfoToRace(raceInfo)
newRace := raceInfoToRace(racesToModTo[i])
diffs, _ := messagediff.DeepDiff(oldRace, newRace)
diffMap := make(map[string]interface{})
for k, v := range diffs.Modified {
diffMap[k.String()] = v
}
raceDiffs := &manualDiffs{ diffMap }
err := r.repos.Race().UpdateByID(oldRace.ID, oldRace, newRace, raceDiffs)
if err!=nil {
return fmt.Errorf("error updating race ID=%q: %w", raceInfo.RaceID, err)
}
raceInfo.RaceID = oldRace.ID
}
return nil
}
type manualDiffs struct {
diffMap map[string]interface{}
}
func (md *manualDiffs) Modified() map[string]interface{} { return md.diffMap }
// Create a Race struct that we can use with the database.
func raceInfoToRace(raceInfo *domain.RaceInfo) *domain.Race {
race := &domain.Race{
ID: raceInfo.RaceID,
EventID: raceInfo.EventID,
Round: &raceInfo.Round, // We know we have Round and Section.
Section: &raceInfo.Section,
}
if raceInfo.AreaID != "" {
race.AreaID = &raceInfo.AreaID
}
if raceInfo.RaceNumber != 0 {
race.Number = &raceInfo.RaceNumber
}
if raceInfo.StageID != "" {
race.StageID = &raceInfo.StageID
}
return race
}