Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 1 | package db |
| 2 | |
| 3 | import ( |
| 4 | "database/sql" |
Philipp Schrader | 30005e4 | 2022-03-06 13:53:58 -0800 | [diff] [blame] | 5 | "errors" |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 6 | "fmt" |
| 7 | |
| 8 | _ "github.com/mattn/go-sqlite3" |
| 9 | ) |
| 10 | |
| 11 | type Database struct { |
| 12 | *sql.DB |
| 13 | } |
| 14 | |
| 15 | type Match struct { |
Philipp Schrader | cbf5c6a | 2022-02-27 23:25:19 -0800 | [diff] [blame] | 16 | MatchNumber, Round int32 |
Philipp Schrader | 1e6c0a9 | 2022-02-27 23:30:57 -0800 | [diff] [blame] | 17 | CompLevel string |
Philipp Schrader | cbf5c6a | 2022-02-27 23:25:19 -0800 | [diff] [blame] | 18 | R1, R2, R3, B1, B2, B3 int32 |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 19 | // Each of these variables holds the matchID of the corresponding Stats row |
| 20 | r1ID, r2ID, r3ID, b1ID, b2ID, b3ID int |
| 21 | } |
| 22 | |
| 23 | type Stats struct { |
Philipp Schrader | 80ccb66 | 2022-03-01 21:47:30 -0800 | [diff] [blame] | 24 | TeamNumber, MatchNumber int32 |
| 25 | ShotsMissed, UpperGoalShots, LowerGoalShots int32 |
| 26 | ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto, PlayedDefense int32 |
| 27 | Climbing int32 |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 28 | } |
| 29 | |
Philipp Schrader | 4953cc3 | 2022-02-25 18:09:02 -0800 | [diff] [blame] | 30 | // Opens a database at the specified path. If the path refers to a non-existent |
| 31 | // file, the database will be created and initialized with empty tables. |
| 32 | func NewDatabase(path string) (*Database, error) { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 33 | var err error |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 34 | database := new(Database) |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 35 | database.DB, err = sql.Open("sqlite3", path) |
| 36 | if err != nil { |
| 37 | return nil, errors.New(fmt.Sprint("Failed to create postgres db: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 38 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 39 | |
| 40 | statement, err := database.Prepare("CREATE TABLE IF NOT EXISTS matches (" + |
| 41 | "id INTEGER PRIMARY KEY, " + |
| 42 | "MatchNumber INTEGER, " + |
| 43 | "Round INTEGER, " + |
| 44 | "CompLevel INTEGER, " + |
| 45 | "R1 INTEGER, " + |
| 46 | "R2 INTEGER, " + |
| 47 | "R3 INTEGER, " + |
| 48 | "B1 INTEGER, " + |
| 49 | "B2 INTEGER, " + |
| 50 | "B3 INTEGER, " + |
| 51 | "r1ID INTEGER, " + |
| 52 | "r2ID INTEGER, " + |
| 53 | "r3ID INTEGER, " + |
| 54 | "b1ID INTEGER, " + |
| 55 | "b2ID INTEGER, " + |
| 56 | "b3ID INTEGER)") |
| 57 | if err != nil { |
| 58 | return nil, errors.New(fmt.Sprint("Failed to prepare matches table creation: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 59 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 60 | defer statement.Close() |
| 61 | |
| 62 | _, err = statement.Exec() |
| 63 | if err != nil { |
| 64 | return nil, errors.New(fmt.Sprint("Failed to create matches table: ", err)) |
| 65 | } |
| 66 | |
| 67 | statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_match_stats (" + |
| 68 | "id INTEGER PRIMARY KEY, " + |
| 69 | "TeamNumber INTEGER, " + |
| 70 | "MatchNumber INTEGER, " + |
| 71 | "ShotsMissed INTEGER, " + |
| 72 | "UpperGoalShots INTEGER, " + |
| 73 | "LowerGoalShots INTEGER, " + |
| 74 | "ShotsMissedAuto INTEGER, " + |
| 75 | "UpperGoalAuto INTEGER, " + |
| 76 | "LowerGoalAuto INTEGER, " + |
| 77 | "PlayedDefense INTEGER, " + |
| 78 | "Climbing INTEGER)") |
| 79 | if err != nil { |
| 80 | return nil, errors.New(fmt.Sprint("Failed to prepare stats table creation: ", err)) |
| 81 | } |
| 82 | defer statement.Close() |
| 83 | |
| 84 | _, err = statement.Exec() |
| 85 | if err != nil { |
| 86 | return nil, errors.New(fmt.Sprint("Failed to create team_match_stats table: ", err)) |
| 87 | } |
| 88 | |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 89 | return database, nil |
| 90 | } |
| 91 | |
| 92 | func (database *Database) Delete() error { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 93 | statement, err := database.Prepare("DROP TABLE IF EXISTS matches") |
| 94 | if err != nil { |
| 95 | return errors.New(fmt.Sprint("Failed to prepare dropping matches table: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 96 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 97 | _, err = statement.Exec() |
| 98 | if err != nil { |
| 99 | return errors.New(fmt.Sprint("Failed to drop matches table: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 100 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 101 | |
| 102 | statement, err = database.Prepare("DROP TABLE IF EXISTS team_match_stats") |
| 103 | if err != nil { |
| 104 | return errors.New(fmt.Sprint("Failed to prepare dropping stats table: ", err)) |
| 105 | } |
| 106 | _, err = statement.Exec() |
| 107 | if err != nil { |
| 108 | return errors.New(fmt.Sprint("Failed to drop stats table: ", err)) |
| 109 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 110 | return nil |
| 111 | } |
| 112 | |
| 113 | // This function will also populate the Stats table with six empty rows every time a match is added |
| 114 | func (database *Database) AddToMatch(m Match) error { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 115 | statement, err := database.Prepare("INSERT INTO team_match_stats(" + |
| 116 | "TeamNumber, MatchNumber, " + |
| 117 | "ShotsMissed, UpperGoalShots, LowerGoalShots, " + |
| 118 | "ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto, " + |
| 119 | "PlayedDefense, Climbing) " + |
| 120 | "VALUES (" + |
| 121 | "?, ?, " + |
| 122 | "?, ?, ?, " + |
| 123 | "?, ?, ?, " + |
| 124 | "?, ?)") |
| 125 | if err != nil { |
| 126 | return errors.New(fmt.Sprint("Failed to prepare insertion into stats database: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 127 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 128 | defer statement.Close() |
| 129 | |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 130 | var rowIds [6]int64 |
Philipp Schrader | 80ccb66 | 2022-03-01 21:47:30 -0800 | [diff] [blame] | 131 | for i, TeamNumber := range []int32{m.R1, m.R2, m.R3, m.B1, m.B2, m.B3} { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 132 | result, err := statement.Exec(TeamNumber, m.MatchNumber, 0, 0, 0, 0, 0, 0, 0, 0) |
| 133 | if err != nil { |
| 134 | return errors.New(fmt.Sprint("Failed to insert stats: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 135 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 136 | rowIds[i], err = result.LastInsertId() |
| 137 | if err != nil { |
| 138 | return errors.New(fmt.Sprint("Failed to get last insert ID: ", err)) |
| 139 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 140 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 141 | |
| 142 | statement, err = database.Prepare("INSERT INTO matches(" + |
| 143 | "MatchNumber, Round, CompLevel, " + |
| 144 | "R1, R2, R3, B1, B2, B3, " + |
| 145 | "r1ID, r2ID, r3ID, b1ID, b2ID, b3ID) " + |
| 146 | "VALUES (" + |
| 147 | "?, ?, ?, " + |
| 148 | "?, ?, ?, ?, ?, ?, " + |
| 149 | "?, ?, ?, ?, ?, ?)") |
| 150 | if err != nil { |
| 151 | return errors.New(fmt.Sprint("Failed to prepare insertion into match database: ", err)) |
| 152 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 153 | defer statement.Close() |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 154 | |
| 155 | _, err = statement.Exec(m.MatchNumber, m.Round, m.CompLevel, |
| 156 | m.R1, m.R2, m.R3, m.B1, m.B2, m.B3, |
| 157 | rowIds[0], rowIds[1], rowIds[2], rowIds[3], rowIds[4], rowIds[5]) |
| 158 | if err != nil { |
| 159 | return errors.New(fmt.Sprint("Failed to insert into match database: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 160 | } |
| 161 | return nil |
| 162 | } |
| 163 | |
| 164 | func (database *Database) AddToStats(s Stats) error { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 165 | statement, err := database.Prepare("UPDATE team_match_stats SET " + |
| 166 | "TeamNumber = ?, MatchNumber = ?, " + |
| 167 | "ShotsMissed = ?, UpperGoalShots = ?, LowerGoalShots = ?, " + |
| 168 | "ShotsMissedAuto = ?, UpperGoalAuto = ?, LowerGoalAuto = ?, " + |
| 169 | "PlayedDefense = ?, Climbing = ? " + |
| 170 | "WHERE MatchNumber = ? AND TeamNumber = ?") |
| 171 | if err != nil { |
| 172 | return errors.New(fmt.Sprint("Failed to prepare stats update statement: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 173 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 174 | defer statement.Close() |
| 175 | |
| 176 | result, err := statement.Exec(s.TeamNumber, s.MatchNumber, |
| 177 | s.ShotsMissed, s.UpperGoalShots, s.LowerGoalShots, |
| 178 | s.ShotsMissedAuto, s.UpperGoalAuto, s.LowerGoalAuto, |
| 179 | s.PlayedDefense, s.Climbing, |
| 180 | s.MatchNumber, s.TeamNumber) |
| 181 | if err != nil { |
| 182 | return errors.New(fmt.Sprint("Failed to update stats database: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 183 | } |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 184 | |
| 185 | numRowsAffected, err := result.RowsAffected() |
| 186 | if err != nil { |
| 187 | return errors.New(fmt.Sprint("Failed to query rows affected: ", err)) |
Philipp Schrader | 30005e4 | 2022-03-06 13:53:58 -0800 | [diff] [blame] | 188 | } |
| 189 | if numRowsAffected == 0 { |
| 190 | return errors.New(fmt.Sprint( |
| 191 | "Failed to find team ", s.TeamNumber, |
| 192 | " in match ", s.MatchNumber, " in the schedule.")) |
| 193 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 194 | return nil |
| 195 | } |
| 196 | |
| 197 | func (database *Database) ReturnMatches() ([]Match, error) { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 198 | rows, err := database.Query("SELECT * FROM matches") |
| 199 | if err != nil { |
| 200 | return nil, errors.New(fmt.Sprint("Failed to select from matches: ", err)) |
| 201 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 202 | defer rows.Close() |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 203 | |
| 204 | matches := make([]Match, 0) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 205 | for rows.Next() { |
| 206 | var match Match |
| 207 | var id int |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 208 | err := rows.Scan(&id, &match.MatchNumber, &match.Round, &match.CompLevel, |
| 209 | &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3, |
| 210 | &match.r1ID, &match.r2ID, &match.r3ID, &match.b1ID, &match.b2ID, &match.b3ID) |
| 211 | if err != nil { |
| 212 | return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 213 | } |
| 214 | matches = append(matches, match) |
| 215 | } |
| 216 | return matches, nil |
| 217 | } |
| 218 | |
| 219 | func (database *Database) ReturnStats() ([]Stats, error) { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 220 | rows, err := database.Query("SELECT * FROM team_match_stats") |
| 221 | if err != nil { |
| 222 | return nil, errors.New(fmt.Sprint("Failed to SELECT * FROM team_match_stats: ", err)) |
Philipp Schrader | 30005e4 | 2022-03-06 13:53:58 -0800 | [diff] [blame] | 223 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 224 | defer rows.Close() |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 225 | |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 226 | teams := make([]Stats, 0) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 227 | for rows.Next() { |
| 228 | var team Stats |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 229 | var id int |
| 230 | err = rows.Scan(&id, &team.TeamNumber, &team.MatchNumber, |
| 231 | &team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots, |
| 232 | &team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto, |
| 233 | &team.PlayedDefense, &team.Climbing) |
| 234 | if err != nil { |
| 235 | return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 236 | } |
| 237 | teams = append(teams, team) |
| 238 | } |
| 239 | return teams, nil |
| 240 | } |
| 241 | |
Philipp Schrader | d1c4bef | 2022-02-28 22:51:30 -0800 | [diff] [blame] | 242 | func (database *Database) QueryMatches(teamNumber_ int32) ([]Match, error) { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 243 | rows, err := database.Query("SELECT * FROM matches WHERE "+ |
| 244 | "R1 = ? OR R2 = ? OR R3 = ? OR B1 = ? OR B2 = ? OR B3 = ?", |
| 245 | teamNumber_, teamNumber_, teamNumber_, teamNumber_, teamNumber_, teamNumber_) |
| 246 | if err != nil { |
| 247 | return nil, errors.New(fmt.Sprint("Failed to select from matches for team: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 248 | } |
| 249 | defer rows.Close() |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 250 | |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 251 | var matches []Match |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 252 | for rows.Next() { |
| 253 | var match Match |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 254 | var id int |
| 255 | err = rows.Scan(&id, &match.MatchNumber, &match.Round, &match.CompLevel, |
| 256 | &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3, |
| 257 | &match.r1ID, &match.r2ID, &match.r3ID, &match.b1ID, &match.b2ID, &match.b3ID) |
| 258 | if err != nil { |
| 259 | return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err)) |
| 260 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 261 | matches = append(matches, match) |
| 262 | } |
| 263 | return matches, nil |
| 264 | } |
| 265 | |
| 266 | func (database *Database) QueryStats(teamNumber_ int) ([]Stats, error) { |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 267 | rows, err := database.Query("SELECT * FROM team_match_stats WHERE TeamNumber = ?", teamNumber_) |
| 268 | if err != nil { |
| 269 | return nil, errors.New(fmt.Sprint("Failed to select from stats: ", err)) |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 270 | } |
| 271 | defer rows.Close() |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 272 | |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 273 | var teams []Stats |
| 274 | for rows.Next() { |
| 275 | var team Stats |
| 276 | var id int |
Philipp Schrader | 83fc272 | 2022-03-10 21:59:20 -0800 | [diff] [blame^] | 277 | err = rows.Scan(&id, &team.TeamNumber, &team.MatchNumber, |
| 278 | &team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots, |
| 279 | &team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto, |
| 280 | &team.PlayedDefense, &team.Climbing) |
| 281 | if err != nil { |
| 282 | return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err)) |
| 283 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 284 | teams = append(teams, team) |
| 285 | } |
Sabina Leaver | c5fd277 | 2022-01-29 17:00:23 -0800 | [diff] [blame] | 286 | return teams, nil |
| 287 | } |