blob: 0d9bec163dcbb8ee30f7cea209a9afc31de7e92e [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
Sabina Leaverc5fd2772022-01-29 17:00:23 -080028}
29
Alex Perry871eab92022-03-12 17:43:52 -080030type NotesData struct {
31 TeamNumber int32
32 Notes []string
33}
34
Philipp Schrader7365d322022-03-06 16:40:08 -080035// Opens a database at the specified port on localhost. We currently don't
36// support connecting to databases on other hosts.
37func NewDatabase(user string, password string, port int) (*Database, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -080038 var err error
Sabina Leaverc5fd2772022-01-29 17:00:23 -080039 database := new(Database)
Philipp Schrader83fc2722022-03-10 21:59:20 -080040
Philipp Schrader7365d322022-03-06 16:40:08 -080041 psqlInfo := fmt.Sprintf("postgres://%s:%s@localhost:%d/postgres", user, password, port)
42 database.DB, err = sql.Open("pgx", psqlInfo)
43 if err != nil {
44 return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
45 }
Philipp Schrader83fc2722022-03-10 21:59:20 -080046 statement, err := database.Prepare("CREATE TABLE IF NOT EXISTS matches (" +
Philipp Schrader7365d322022-03-06 16:40:08 -080047 "id SERIAL PRIMARY KEY, " +
Philipp Schrader83fc2722022-03-10 21:59:20 -080048 "MatchNumber INTEGER, " +
49 "Round INTEGER, " +
Philipp Schrader7365d322022-03-06 16:40:08 -080050 "CompLevel VARCHAR, " +
Philipp Schrader83fc2722022-03-10 21:59:20 -080051 "R1 INTEGER, " +
52 "R2 INTEGER, " +
53 "R3 INTEGER, " +
54 "B1 INTEGER, " +
55 "B2 INTEGER, " +
56 "B3 INTEGER, " +
57 "r1ID INTEGER, " +
58 "r2ID INTEGER, " +
59 "r3ID INTEGER, " +
60 "b1ID INTEGER, " +
61 "b2ID INTEGER, " +
62 "b3ID INTEGER)")
63 if err != nil {
Philipp Schrader7365d322022-03-06 16:40:08 -080064 database.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -080065 return nil, errors.New(fmt.Sprint("Failed to prepare matches table creation: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -080066 }
Philipp Schrader83fc2722022-03-10 21:59:20 -080067 defer statement.Close()
68
69 _, err = statement.Exec()
70 if err != nil {
Philipp Schrader7365d322022-03-06 16:40:08 -080071 database.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -080072 return nil, errors.New(fmt.Sprint("Failed to create matches table: ", err))
73 }
74
75 statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_match_stats (" +
Philipp Schrader7365d322022-03-06 16:40:08 -080076 "id SERIAL PRIMARY KEY, " +
Philipp Schrader83fc2722022-03-10 21:59:20 -080077 "TeamNumber INTEGER, " +
78 "MatchNumber INTEGER, " +
79 "ShotsMissed INTEGER, " +
80 "UpperGoalShots INTEGER, " +
81 "LowerGoalShots INTEGER, " +
82 "ShotsMissedAuto INTEGER, " +
83 "UpperGoalAuto INTEGER, " +
84 "LowerGoalAuto INTEGER, " +
85 "PlayedDefense INTEGER, " +
86 "Climbing INTEGER)")
87 if err != nil {
Philipp Schrader7365d322022-03-06 16:40:08 -080088 database.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -080089 return nil, errors.New(fmt.Sprint("Failed to prepare stats table creation: ", err))
90 }
91 defer statement.Close()
92
93 _, err = statement.Exec()
94 if err != nil {
Philipp Schrader7365d322022-03-06 16:40:08 -080095 database.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -080096 return nil, errors.New(fmt.Sprint("Failed to create team_match_stats table: ", err))
97 }
98
Alex Perry871eab92022-03-12 17:43:52 -080099 statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_notes (" +
Philipp Schrader7365d322022-03-06 16:40:08 -0800100 "id SERIAL PRIMARY KEY, " +
Alex Perry871eab92022-03-12 17:43:52 -0800101 "TeamNumber INTEGER, " +
102 "Notes TEXT)")
103 if err != nil {
104 return nil, errors.New(fmt.Sprint("Failed to prepare notes table creation: ", err))
105 }
106 defer statement.Close()
107
108 _, err = statement.Exec()
109 if err != nil {
110 return nil, errors.New(fmt.Sprint("Failed to create notes table: ", err))
111 }
112
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800113 return database, nil
114}
115
116func (database *Database) Delete() error {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800117 statement, err := database.Prepare("DROP TABLE IF EXISTS matches")
118 if err != nil {
119 return errors.New(fmt.Sprint("Failed to prepare dropping matches table: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800120 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800121 _, err = statement.Exec()
122 if err != nil {
123 return errors.New(fmt.Sprint("Failed to drop matches table: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800124 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800125
126 statement, err = database.Prepare("DROP TABLE IF EXISTS team_match_stats")
127 if err != nil {
128 return errors.New(fmt.Sprint("Failed to prepare dropping stats table: ", err))
129 }
130 _, err = statement.Exec()
131 if err != nil {
132 return errors.New(fmt.Sprint("Failed to drop stats table: ", err))
133 }
Alex Perry871eab92022-03-12 17:43:52 -0800134
135 statement, err = database.Prepare("DROP TABLE IF EXISTS team_notes")
136 if err != nil {
137 return errors.New(fmt.Sprint("Failed to prepare dropping notes table: ", err))
138 }
139 _, err = statement.Exec()
140 if err != nil {
141 return errors.New(fmt.Sprint("Failed to drop notes table: ", err))
142 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800143 return nil
144}
145
146// This function will also populate the Stats table with six empty rows every time a match is added
147func (database *Database) AddToMatch(m Match) error {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800148 statement, err := database.Prepare("INSERT INTO team_match_stats(" +
149 "TeamNumber, MatchNumber, " +
150 "ShotsMissed, UpperGoalShots, LowerGoalShots, " +
151 "ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto, " +
152 "PlayedDefense, Climbing) " +
153 "VALUES (" +
Philipp Schrader7365d322022-03-06 16:40:08 -0800154 "$1, $2, " +
155 "$3, $4, $5, " +
156 "$6, $7, $8, " +
157 "$9, $10) " +
158 "RETURNING id")
Philipp Schrader83fc2722022-03-10 21:59:20 -0800159 if err != nil {
160 return errors.New(fmt.Sprint("Failed to prepare insertion into stats database: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800161 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800162 defer statement.Close()
163
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800164 var rowIds [6]int64
Philipp Schrader80ccb662022-03-01 21:47:30 -0800165 for i, TeamNumber := range []int32{m.R1, m.R2, m.R3, m.B1, m.B2, m.B3} {
Philipp Schrader7365d322022-03-06 16:40:08 -0800166 row := statement.QueryRow(TeamNumber, m.MatchNumber, 0, 0, 0, 0, 0, 0, 0, 0)
167 err = row.Scan(&rowIds[i])
Philipp Schrader83fc2722022-03-10 21:59:20 -0800168 if err != nil {
169 return errors.New(fmt.Sprint("Failed to insert stats: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800170 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800171 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800172
173 statement, err = database.Prepare("INSERT INTO matches(" +
174 "MatchNumber, Round, CompLevel, " +
175 "R1, R2, R3, B1, B2, B3, " +
176 "r1ID, r2ID, r3ID, b1ID, b2ID, b3ID) " +
177 "VALUES (" +
Philipp Schrader7365d322022-03-06 16:40:08 -0800178 "$1, $2, $3, " +
179 "$4, $5, $6, $7, $8, $9, " +
180 "$10, $11, $12, $13, $14, $15)")
Philipp Schrader83fc2722022-03-10 21:59:20 -0800181 if err != nil {
182 return errors.New(fmt.Sprint("Failed to prepare insertion into match database: ", err))
183 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800184 defer statement.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800185
186 _, err = statement.Exec(m.MatchNumber, m.Round, m.CompLevel,
187 m.R1, m.R2, m.R3, m.B1, m.B2, m.B3,
188 rowIds[0], rowIds[1], rowIds[2], rowIds[3], rowIds[4], rowIds[5])
189 if err != nil {
190 return errors.New(fmt.Sprint("Failed to insert into match database: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800191 }
192 return nil
193}
194
195func (database *Database) AddToStats(s Stats) error {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800196 statement, err := database.Prepare("UPDATE team_match_stats SET " +
Philipp Schrader7365d322022-03-06 16:40:08 -0800197 "TeamNumber = $1, MatchNumber = $2, " +
198 "ShotsMissed = $3, UpperGoalShots = $4, LowerGoalShots = $5, " +
199 "ShotsMissedAuto = $6, UpperGoalAuto = $7, LowerGoalAuto = $8, " +
200 "PlayedDefense = $9, Climbing = $10 " +
201 "WHERE MatchNumber = $11 AND TeamNumber = $12")
Philipp Schrader83fc2722022-03-10 21:59:20 -0800202 if err != nil {
203 return errors.New(fmt.Sprint("Failed to prepare stats update statement: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800204 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800205 defer statement.Close()
206
207 result, err := statement.Exec(s.TeamNumber, s.MatchNumber,
208 s.ShotsMissed, s.UpperGoalShots, s.LowerGoalShots,
209 s.ShotsMissedAuto, s.UpperGoalAuto, s.LowerGoalAuto,
210 s.PlayedDefense, s.Climbing,
211 s.MatchNumber, s.TeamNumber)
212 if err != nil {
213 return errors.New(fmt.Sprint("Failed to update stats database: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800214 }
Philipp Schrader83fc2722022-03-10 21:59:20 -0800215
216 numRowsAffected, err := result.RowsAffected()
217 if err != nil {
218 return errors.New(fmt.Sprint("Failed to query rows affected: ", err))
Philipp Schrader30005e42022-03-06 13:53:58 -0800219 }
220 if numRowsAffected == 0 {
221 return errors.New(fmt.Sprint(
222 "Failed to find team ", s.TeamNumber,
223 " in match ", s.MatchNumber, " in the schedule."))
224 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800225 return nil
226}
227
228func (database *Database) ReturnMatches() ([]Match, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800229 rows, err := database.Query("SELECT * FROM matches")
230 if err != nil {
231 return nil, errors.New(fmt.Sprint("Failed to select from matches: ", err))
232 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800233 defer rows.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800234
235 matches := make([]Match, 0)
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800236 for rows.Next() {
237 var match Match
238 var id int
Philipp Schrader83fc2722022-03-10 21:59:20 -0800239 err := rows.Scan(&id, &match.MatchNumber, &match.Round, &match.CompLevel,
240 &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3,
241 &match.r1ID, &match.r2ID, &match.r3ID, &match.b1ID, &match.b2ID, &match.b3ID)
242 if err != nil {
243 return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800244 }
245 matches = append(matches, match)
246 }
247 return matches, nil
248}
249
250func (database *Database) ReturnStats() ([]Stats, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800251 rows, err := database.Query("SELECT * FROM team_match_stats")
252 if err != nil {
253 return nil, errors.New(fmt.Sprint("Failed to SELECT * FROM team_match_stats: ", err))
Philipp Schrader30005e42022-03-06 13:53:58 -0800254 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800255 defer rows.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800256
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800257 teams := make([]Stats, 0)
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800258 for rows.Next() {
259 var team Stats
Philipp Schrader83fc2722022-03-10 21:59:20 -0800260 var id int
261 err = rows.Scan(&id, &team.TeamNumber, &team.MatchNumber,
262 &team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots,
263 &team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto,
264 &team.PlayedDefense, &team.Climbing)
265 if err != nil {
266 return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800267 }
268 teams = append(teams, team)
269 }
270 return teams, nil
271}
272
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800273func (database *Database) QueryMatches(teamNumber_ int32) ([]Match, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800274 rows, err := database.Query("SELECT * FROM matches WHERE "+
Philipp Schrader7365d322022-03-06 16:40:08 -0800275 "R1 = $1 OR R2 = $2 OR R3 = $3 OR B1 = $4 OR B2 = $5 OR B3 = $6",
Philipp Schrader83fc2722022-03-10 21:59:20 -0800276 teamNumber_, teamNumber_, teamNumber_, teamNumber_, teamNumber_, teamNumber_)
277 if err != nil {
278 return nil, errors.New(fmt.Sprint("Failed to select from matches for team: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800279 }
280 defer rows.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800281
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800282 var matches []Match
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800283 for rows.Next() {
284 var match Match
Philipp Schrader83fc2722022-03-10 21:59:20 -0800285 var id int
286 err = rows.Scan(&id, &match.MatchNumber, &match.Round, &match.CompLevel,
287 &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3,
288 &match.r1ID, &match.r2ID, &match.r3ID, &match.b1ID, &match.b2ID, &match.b3ID)
289 if err != nil {
290 return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
291 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800292 matches = append(matches, match)
293 }
294 return matches, nil
295}
296
297func (database *Database) QueryStats(teamNumber_ int) ([]Stats, error) {
Philipp Schrader7365d322022-03-06 16:40:08 -0800298 rows, err := database.Query("SELECT * FROM team_match_stats WHERE TeamNumber = $1", teamNumber_)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800299 if err != nil {
300 return nil, errors.New(fmt.Sprint("Failed to select from stats: ", err))
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800301 }
302 defer rows.Close()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800303
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800304 var teams []Stats
305 for rows.Next() {
306 var team Stats
307 var id int
Philipp Schrader83fc2722022-03-10 21:59:20 -0800308 err = rows.Scan(&id, &team.TeamNumber, &team.MatchNumber,
309 &team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots,
310 &team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto,
311 &team.PlayedDefense, &team.Climbing)
312 if err != nil {
313 return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err))
314 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800315 teams = append(teams, team)
316 }
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800317 return teams, nil
318}
Alex Perry871eab92022-03-12 17:43:52 -0800319
320func (database *Database) QueryNotes(TeamNumber int32) (NotesData, error) {
Philipp Schrader7365d322022-03-06 16:40:08 -0800321 rows, err := database.Query("SELECT * FROM team_notes WHERE TeamNumber = $1", TeamNumber)
Alex Perry871eab92022-03-12 17:43:52 -0800322 if err != nil {
323 return NotesData{}, errors.New(fmt.Sprint("Failed to select from notes: ", err))
324 }
325 defer rows.Close()
326
327 var notes []string
328 for rows.Next() {
329 var id int32
330 var data string
331 err = rows.Scan(&id, &TeamNumber, &data)
332 if err != nil {
333 return NotesData{}, errors.New(fmt.Sprint("Failed to scan from notes: ", err))
334 }
335 notes = append(notes, data)
336 }
337 return NotesData{TeamNumber, notes}, nil
338}
339
340func (database *Database) AddNotes(data NotesData) error {
341 if len(data.Notes) > 1 {
342 return errors.New("Can only insert one row of notes at a time")
343 }
344 statement, err := database.Prepare("INSERT INTO " +
345 "team_notes(TeamNumber, Notes)" +
Philipp Schrader7365d322022-03-06 16:40:08 -0800346 "VALUES ($1, $2)")
Alex Perry871eab92022-03-12 17:43:52 -0800347 if err != nil {
348 return errors.New(fmt.Sprint("Failed to prepare insertion into notes table: ", err))
349 }
350 defer statement.Close()
351
352 _, err = statement.Exec(data.TeamNumber, data.Notes[0])
353 if err != nil {
354 return errors.New(fmt.Sprint("Failed to insert into Notes database: ", err))
355 }
356 return nil
357}