blob: 98cc788b73a6c8aca8bffc406b6539dcb34f0351 [file] [log] [blame]
Sabina Leaverc5fd2772022-01-29 17:00:23 -08001package db
2
3import (
4 "database/sql"
Philipp Schrader30005e42022-03-06 13:53:58 -08005 "errors"
Sabina Leaverc5fd2772022-01-29 17:00:23 -08006 "fmt"
7
Philipp Schrader7365d322022-03-06 16:40:08 -08008 _ "github.com/jackc/pgx/stdlib"
Sabina Leaverc5fd2772022-01-29 17:00:23 -08009)
10
11type Database struct {
12 *sql.DB
13}
14
15type Match struct {
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080016 MatchNumber, Round int32
Philipp Schrader1e6c0a92022-02-27 23:30:57 -080017 CompLevel string
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080018 R1, R2, R3, B1, B2, B3 int32
Sabina Leaverc5fd2772022-01-29 17:00:23 -080019 // Each of these variables holds the matchID of the corresponding Stats row
20 r1ID, r2ID, r3ID, b1ID, b2ID, b3ID int
21}
22
23type Stats struct {
Philipp Schrader80ccb662022-03-01 21:47:30 -080024 TeamNumber, MatchNumber int32
25 ShotsMissed, UpperGoalShots, LowerGoalShots int32
26 ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto, PlayedDefense int32
27 Climbing int32
Philipp Schraderfae8a7e2022-03-13 22:51:54 -070028 // The username of the person who collected these statistics.
29 // "unknown" if submitted without logging in.
30 // Empty if the stats have not yet been collected.
31 CollectedBy string
Sabina Leaverc5fd2772022-01-29 17:00:23 -080032}
33
Alex Perry871eab92022-03-12 17:43:52 -080034type NotesData struct {
35 TeamNumber int32
36 Notes []string
37}
38
Philipp Schrader7365d322022-03-06 16:40:08 -080039// Opens a database at the specified port on localhost. We currently don't
40// support connecting to databases on other hosts.
41func NewDatabase(user string, password string, port int) (*Database, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -080042 var err error
Sabina Leaverc5fd2772022-01-29 17:00:23 -080043 database := new(Database)
Philipp Schrader83fc2722022-03-10 21:59:20 -080044
Philipp Schrader7365d322022-03-06 16:40:08 -080045 psqlInfo := fmt.Sprintf("postgres://%s:%s@localhost:%d/postgres", user, password, port)
46 database.DB, err = sql.Open("pgx", psqlInfo)
47 if err != nil {
48 return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
49 }
Philipp Schrader83fc2722022-03-10 21:59:20 -080050 statement, err := database.Prepare("CREATE TABLE IF NOT EXISTS matches (" +
Philipp Schrader7365d322022-03-06 16:40:08 -080051 "id SERIAL PRIMARY KEY, " +
Philipp Schrader83fc2722022-03-10 21:59:20 -080052 "MatchNumber INTEGER, " +
53 "Round INTEGER, " +
Philipp Schrader7365d322022-03-06 16:40:08 -080054 "CompLevel VARCHAR, " +
Philipp Schrader83fc2722022-03-10 21:59:20 -080055 "R1 INTEGER, " +
56 "R2 INTEGER, " +
57 "R3 INTEGER, " +
58 "B1 INTEGER, " +
59 "B2 INTEGER, " +
60 "B3 INTEGER, " +
61 "r1ID INTEGER, " +
62 "r2ID INTEGER, " +
63 "r3ID INTEGER, " +
64 "b1ID INTEGER, " +
65 "b2ID INTEGER, " +
66 "b3ID INTEGER)")
67 if err != nil {
Philipp Schrader7365d322022-03-06 16:40:08 -080068 database.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -080069 return nil, errors.New(fmt.Sprint("Failed to prepare matches table creation: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -080070 }
Philipp Schrader83fc2722022-03-10 21:59:20 -080071 defer statement.Close()
72
73 _, err = statement.Exec()
74 if err != nil {
Philipp Schrader7365d322022-03-06 16:40:08 -080075 database.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -080076 return nil, errors.New(fmt.Sprint("Failed to create matches table: ", err))
77 }
78
79 statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_match_stats (" +
Philipp Schrader7365d322022-03-06 16:40:08 -080080 "id SERIAL PRIMARY KEY, " +
Philipp Schrader83fc2722022-03-10 21:59:20 -080081 "TeamNumber INTEGER, " +
82 "MatchNumber INTEGER, " +
83 "ShotsMissed INTEGER, " +
84 "UpperGoalShots INTEGER, " +
85 "LowerGoalShots INTEGER, " +
86 "ShotsMissedAuto INTEGER, " +
87 "UpperGoalAuto INTEGER, " +
88 "LowerGoalAuto INTEGER, " +
89 "PlayedDefense INTEGER, " +
Philipp Schraderfae8a7e2022-03-13 22:51:54 -070090 "Climbing INTEGER, " +
91 "CollectedBy VARCHAR)")
Philipp Schrader83fc2722022-03-10 21:59:20 -080092 if err != nil {
Philipp Schrader7365d322022-03-06 16:40:08 -080093 database.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -080094 return nil, errors.New(fmt.Sprint("Failed to prepare stats table creation: ", err))
95 }
96 defer statement.Close()
97
98 _, err = statement.Exec()
99 if err != nil {
Philipp Schrader7365d322022-03-06 16:40:08 -0800100 database.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800101 return nil, errors.New(fmt.Sprint("Failed to create team_match_stats table: ", err))
102 }
103
Alex Perry871eab92022-03-12 17:43:52 -0800104 statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_notes (" +
Philipp Schrader7365d322022-03-06 16:40:08 -0800105 "id SERIAL PRIMARY KEY, " +
Alex Perry871eab92022-03-12 17:43:52 -0800106 "TeamNumber INTEGER, " +
107 "Notes TEXT)")
108 if err != nil {
109 return nil, errors.New(fmt.Sprint("Failed to prepare notes table creation: ", err))
110 }
111 defer statement.Close()
112
113 _, err = statement.Exec()
114 if err != nil {
115 return nil, errors.New(fmt.Sprint("Failed to create notes table: ", err))
116 }
117
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800118 return database, nil
119}
120
121func (database *Database) Delete() error {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800122 statement, err := database.Prepare("DROP TABLE IF EXISTS matches")
123 if err != nil {
124 return errors.New(fmt.Sprint("Failed to prepare dropping matches table: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800125 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800126 _, err = statement.Exec()
127 if err != nil {
128 return errors.New(fmt.Sprint("Failed to drop matches table: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800129 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800130
131 statement, err = database.Prepare("DROP TABLE IF EXISTS team_match_stats")
132 if err != nil {
133 return errors.New(fmt.Sprint("Failed to prepare dropping stats table: ", err))
134 }
135 _, err = statement.Exec()
136 if err != nil {
137 return errors.New(fmt.Sprint("Failed to drop stats table: ", err))
138 }
Alex Perry871eab92022-03-12 17:43:52 -0800139
140 statement, err = database.Prepare("DROP TABLE IF EXISTS team_notes")
141 if err != nil {
142 return errors.New(fmt.Sprint("Failed to prepare dropping notes table: ", err))
143 }
144 _, err = statement.Exec()
145 if err != nil {
146 return errors.New(fmt.Sprint("Failed to drop notes table: ", err))
147 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800148 return nil
149}
150
151// This function will also populate the Stats table with six empty rows every time a match is added
152func (database *Database) AddToMatch(m Match) error {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800153 statement, err := database.Prepare("INSERT INTO team_match_stats(" +
154 "TeamNumber, MatchNumber, " +
155 "ShotsMissed, UpperGoalShots, LowerGoalShots, " +
156 "ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto, " +
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700157 "PlayedDefense, Climbing, CollectedBy) " +
Philipp Schrader83fc2722022-03-10 21:59:20 -0800158 "VALUES (" +
Philipp Schrader7365d322022-03-06 16:40:08 -0800159 "$1, $2, " +
160 "$3, $4, $5, " +
161 "$6, $7, $8, " +
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700162 "$9, $10, $11) " +
Philipp Schrader7365d322022-03-06 16:40:08 -0800163 "RETURNING id")
Philipp Schrader83fc2722022-03-10 21:59:20 -0800164 if err != nil {
165 return errors.New(fmt.Sprint("Failed to prepare insertion into stats database: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800166 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800167 defer statement.Close()
168
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800169 var rowIds [6]int64
Philipp Schrader80ccb662022-03-01 21:47:30 -0800170 for i, TeamNumber := range []int32{m.R1, m.R2, m.R3, m.B1, m.B2, m.B3} {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700171 row := statement.QueryRow(TeamNumber, m.MatchNumber, 0, 0, 0, 0, 0, 0, 0, 0, "")
Philipp Schrader7365d322022-03-06 16:40:08 -0800172 err = row.Scan(&rowIds[i])
Philipp Schrader83fc2722022-03-10 21:59:20 -0800173 if err != nil {
174 return errors.New(fmt.Sprint("Failed to insert stats: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800175 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800176 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800177
178 statement, err = database.Prepare("INSERT INTO matches(" +
179 "MatchNumber, Round, CompLevel, " +
180 "R1, R2, R3, B1, B2, B3, " +
181 "r1ID, r2ID, r3ID, b1ID, b2ID, b3ID) " +
182 "VALUES (" +
Philipp Schrader7365d322022-03-06 16:40:08 -0800183 "$1, $2, $3, " +
184 "$4, $5, $6, $7, $8, $9, " +
185 "$10, $11, $12, $13, $14, $15)")
Philipp Schrader83fc2722022-03-10 21:59:20 -0800186 if err != nil {
187 return errors.New(fmt.Sprint("Failed to prepare insertion into match database: ", err))
188 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800189 defer statement.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800190
191 _, err = statement.Exec(m.MatchNumber, m.Round, m.CompLevel,
192 m.R1, m.R2, m.R3, m.B1, m.B2, m.B3,
193 rowIds[0], rowIds[1], rowIds[2], rowIds[3], rowIds[4], rowIds[5])
194 if err != nil {
195 return errors.New(fmt.Sprint("Failed to insert into match database: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800196 }
197 return nil
198}
199
200func (database *Database) AddToStats(s Stats) error {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800201 statement, err := database.Prepare("UPDATE team_match_stats SET " +
Philipp Schrader7365d322022-03-06 16:40:08 -0800202 "TeamNumber = $1, MatchNumber = $2, " +
203 "ShotsMissed = $3, UpperGoalShots = $4, LowerGoalShots = $5, " +
204 "ShotsMissedAuto = $6, UpperGoalAuto = $7, LowerGoalAuto = $8, " +
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700205 "PlayedDefense = $9, Climbing = $10, CollectedBy = $11 " +
206 "WHERE MatchNumber = $12 AND TeamNumber = $13")
Philipp Schrader83fc2722022-03-10 21:59:20 -0800207 if err != nil {
208 return errors.New(fmt.Sprint("Failed to prepare stats update statement: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800209 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800210 defer statement.Close()
211
212 result, err := statement.Exec(s.TeamNumber, s.MatchNumber,
213 s.ShotsMissed, s.UpperGoalShots, s.LowerGoalShots,
214 s.ShotsMissedAuto, s.UpperGoalAuto, s.LowerGoalAuto,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700215 s.PlayedDefense, s.Climbing, s.CollectedBy,
Philipp Schrader83fc2722022-03-10 21:59:20 -0800216 s.MatchNumber, s.TeamNumber)
217 if err != nil {
218 return errors.New(fmt.Sprint("Failed to update stats database: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800219 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800220
221 numRowsAffected, err := result.RowsAffected()
222 if err != nil {
223 return errors.New(fmt.Sprint("Failed to query rows affected: ", err))
Philipp Schrader30005e42022-03-06 13:53:58 -0800224 }
225 if numRowsAffected == 0 {
226 return errors.New(fmt.Sprint(
227 "Failed to find team ", s.TeamNumber,
228 " in match ", s.MatchNumber, " in the schedule."))
229 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800230 return nil
231}
232
233func (database *Database) ReturnMatches() ([]Match, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800234 rows, err := database.Query("SELECT * FROM matches")
235 if err != nil {
236 return nil, errors.New(fmt.Sprint("Failed to select from matches: ", err))
237 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800238 defer rows.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800239
240 matches := make([]Match, 0)
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800241 for rows.Next() {
242 var match Match
243 var id int
Philipp Schrader83fc2722022-03-10 21:59:20 -0800244 err := rows.Scan(&id, &match.MatchNumber, &match.Round, &match.CompLevel,
245 &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3,
246 &match.r1ID, &match.r2ID, &match.r3ID, &match.b1ID, &match.b2ID, &match.b3ID)
247 if err != nil {
248 return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800249 }
250 matches = append(matches, match)
251 }
252 return matches, nil
253}
254
255func (database *Database) ReturnStats() ([]Stats, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800256 rows, err := database.Query("SELECT * FROM team_match_stats")
257 if err != nil {
258 return nil, errors.New(fmt.Sprint("Failed to SELECT * FROM team_match_stats: ", err))
Philipp Schrader30005e42022-03-06 13:53:58 -0800259 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800260 defer rows.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800261
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800262 teams := make([]Stats, 0)
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800263 for rows.Next() {
264 var team Stats
Philipp Schrader83fc2722022-03-10 21:59:20 -0800265 var id int
266 err = rows.Scan(&id, &team.TeamNumber, &team.MatchNumber,
267 &team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots,
268 &team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700269 &team.PlayedDefense, &team.Climbing, &team.CollectedBy)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800270 if err != nil {
271 return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800272 }
273 teams = append(teams, team)
274 }
275 return teams, nil
276}
277
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800278func (database *Database) QueryMatches(teamNumber_ int32) ([]Match, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800279 rows, err := database.Query("SELECT * FROM matches WHERE "+
Philipp Schrader7365d322022-03-06 16:40:08 -0800280 "R1 = $1 OR R2 = $2 OR R3 = $3 OR B1 = $4 OR B2 = $5 OR B3 = $6",
Philipp Schrader83fc2722022-03-10 21:59:20 -0800281 teamNumber_, teamNumber_, teamNumber_, teamNumber_, teamNumber_, teamNumber_)
282 if err != nil {
283 return nil, errors.New(fmt.Sprint("Failed to select from matches for team: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800284 }
285 defer rows.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800286
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800287 var matches []Match
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800288 for rows.Next() {
289 var match Match
Philipp Schrader83fc2722022-03-10 21:59:20 -0800290 var id int
291 err = rows.Scan(&id, &match.MatchNumber, &match.Round, &match.CompLevel,
292 &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3,
293 &match.r1ID, &match.r2ID, &match.r3ID, &match.b1ID, &match.b2ID, &match.b3ID)
294 if err != nil {
295 return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
296 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800297 matches = append(matches, match)
298 }
299 return matches, nil
300}
301
302func (database *Database) QueryStats(teamNumber_ int) ([]Stats, error) {
Philipp Schrader7365d322022-03-06 16:40:08 -0800303 rows, err := database.Query("SELECT * FROM team_match_stats WHERE TeamNumber = $1", teamNumber_)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800304 if err != nil {
305 return nil, errors.New(fmt.Sprint("Failed to select from stats: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800306 }
307 defer rows.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800308
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800309 var teams []Stats
310 for rows.Next() {
311 var team Stats
312 var id int
Philipp Schrader83fc2722022-03-10 21:59:20 -0800313 err = rows.Scan(&id, &team.TeamNumber, &team.MatchNumber,
314 &team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots,
315 &team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700316 &team.PlayedDefense, &team.Climbing, &team.CollectedBy)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800317 if err != nil {
318 return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err))
319 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800320 teams = append(teams, team)
321 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800322 return teams, nil
323}
Alex Perry871eab92022-03-12 17:43:52 -0800324
325func (database *Database) QueryNotes(TeamNumber int32) (NotesData, error) {
Philipp Schrader7365d322022-03-06 16:40:08 -0800326 rows, err := database.Query("SELECT * FROM team_notes WHERE TeamNumber = $1", TeamNumber)
Alex Perry871eab92022-03-12 17:43:52 -0800327 if err != nil {
328 return NotesData{}, errors.New(fmt.Sprint("Failed to select from notes: ", err))
329 }
330 defer rows.Close()
331
332 var notes []string
333 for rows.Next() {
334 var id int32
335 var data string
336 err = rows.Scan(&id, &TeamNumber, &data)
337 if err != nil {
338 return NotesData{}, errors.New(fmt.Sprint("Failed to scan from notes: ", err))
339 }
340 notes = append(notes, data)
341 }
342 return NotesData{TeamNumber, notes}, nil
343}
344
345func (database *Database) AddNotes(data NotesData) error {
346 if len(data.Notes) > 1 {
347 return errors.New("Can only insert one row of notes at a time")
348 }
349 statement, err := database.Prepare("INSERT INTO " +
350 "team_notes(TeamNumber, Notes)" +
Philipp Schrader7365d322022-03-06 16:40:08 -0800351 "VALUES ($1, $2)")
Alex Perry871eab92022-03-12 17:43:52 -0800352 if err != nil {
353 return errors.New(fmt.Sprint("Failed to prepare insertion into notes table: ", err))
354 }
355 defer statement.Close()
356
357 _, err = statement.Exec(data.TeamNumber, data.Notes[0])
358 if err != nil {
359 return errors.New(fmt.Sprint("Failed to insert into Notes database: ", err))
360 }
361 return nil
362}