diff --git a/src/games/deep-space.sql b/src/games/deep-space.sql index 3f22a6e..cd59c68 100644 --- a/src/games/deep-space.sql +++ b/src/games/deep-space.sql @@ -1,12 +1,6 @@ -- Schema for storing Deep Space teams and matches PRAGMA foreign_keys = ON; -CREATE TABLE IF NOT EXISTS teams ( - id INTEGER NOT NULL PRIMARY KEY, - number INTEGER NOT NULL, - CONSTRAINT unique_number UNIQUE (number) -); - CREATE TABLE IF NOT EXISTS matches ( team_number INTEGER NOT NULL, type TEXT NOT NULL, @@ -15,8 +9,6 @@ CREATE TABLE IF NOT EXISTS matches ( alliance TINYINT(1) NOT NULL, cargo_tracker_id INTEGER NOT NULL, hatch_tracker_id INTEGER NOT NULL, - -- OK to be null since some matches won't have a team - associated_team INTEGER, tech_fouls INTEGER NOT NULL, fouls INTEGER NOT NULL, @@ -41,8 +33,7 @@ CREATE TABLE IF NOT EXISTS matches ( rocket_ranking_point TINYINT(1) NOT NULL, FOREIGN KEY (cargo_tracker_id) REFERENCES cargo_trackers (id), - FOREIGN KEY (hatch_tracker_id) REFERENCES hatch_trackers (id), - FOREIGN KEY (associated_team) REFERENCES teams (id) ON DELETE SET NULL + FOREIGN KEY (hatch_tracker_id) REFERENCES hatch_trackers (id) ); CREATE TABLE IF NOT EXISTS cargo_trackers ( diff --git a/src/games/deep-space.ts b/src/games/deep-space.ts index 5c00ddd..78c31c9 100644 --- a/src/games/deep-space.ts +++ b/src/games/deep-space.ts @@ -171,17 +171,17 @@ export class DeepSpaceSQL extends SQLStoragePlan { `(team_number, type, match_number, alliance, cargo_tracker_id, hatch_tracker_id,` + ` tech_fouls, fouls, yellow_card, red_card,` + ` estopped, borked, ranking_points, foul_points,` + - ` bonus_points, associated_team,` + + ` bonus_points,` + ` helps_hab_climb, start_hab_level, end_hab_level,` + ` crosses_start_line, left_rocket_assembled, right_rocket_assembled,` + ` hab_ranking_point, rocket_ranking_point) VALUES ` + - `(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + `(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ); statement.run( m.teamNumber, m.type, m.number, (m.alliance === 'BLUE' ? 0 : 1), cargoTrackerID, hatchTrackerID, m.fouls.technical, m.fouls.regular, Number(m.cards.yellow), Number(m.cards.red), Number(m.emergencyStopped), Number(m.borked), m.rankingPoints, m.pointsFromFouls, - m.bonusPoints, (associatedTeamID || null), + m.bonusPoints, Number(m.helpsOthersHABClimb), m.initialHABLevel, m.finalHABLevel, Number(m.crossesStartLine), Number(m.rocketsAssembled.LEFT), Number(m.rocketsAssembled.RIGHT), Number(m.rankingPointRecord.HAB), Number(m.rankingPointRecord.ROCKET), @@ -259,7 +259,7 @@ export class DeepSpaceSQL extends SQLStoragePlan { /** Converts data from the database into a team */ dbDataToTeam(data: any) { - const matches = this.getStatement(`SELECT * FROM matches WHERE associated_team = ?`) + const matches = this.getStatement(`SELECT * FROM matches WHERE team_number = ?`) .all(data.id) .map((matchData) => this.dbDataToMatch(matchData)); @@ -270,16 +270,6 @@ export class DeepSpaceSQL extends SQLStoragePlan { insertMatch(match: DeepSpaceMatch) { this.matchInsertionTransaction(match); } - - /** Inserts a team */ - insertTeam(team: Team) { - const id = this.getStatement(`INSERT OR REPLACE INTO teams (number) VALUES (?)`) - .run(team.number) - .lastInsertRowid; - for (const match of team.matches) { - this.matchInsertionTransaction(match, id); - } - } } /** Stores Deep Space matches in JSON */ diff --git a/src/games/infinite-recharge.sql b/src/games/infinite-recharge.sql index 1e6f12c..2c5fe6a 100644 --- a/src/games/infinite-recharge.sql +++ b/src/games/infinite-recharge.sql @@ -1,12 +1,6 @@ -- Schema for storing Infinite Recharge teams and matches PRAGMA foreign_keys = ON; -CREATE TABLE IF NOT EXISTS teams ( - id INTEGER NOT NULL PRIMARY KEY, - number INTEGER NOT NULL, - CONSTRAINT unique_number UNIQUE (number) -); - CREATE TABLE IF NOT EXISTS matches ( team_number INTEGER NOT NULL, type TEXT NOT NULL, @@ -16,8 +10,6 @@ CREATE TABLE IF NOT EXISTS matches ( power_cell_tracker_id INTEGER NOT NULL, color_wheel_id INTEGER NOT NULL, shield_generator_id INTEGER NOT NULL, - -- OK to be null since some matches won't have a team - associated_team INTEGER, tech_fouls INTEGER NOT NULL, fouls INTEGER NOT NULL, @@ -31,8 +23,7 @@ CREATE TABLE IF NOT EXISTS matches ( FOREIGN KEY (power_cell_tracker_id) REFERENCES power_cell_trackers (id), FOREIGN KEY (color_wheel_id) REFERENCES color_wheels (id), - FOREIGN KEY (shield_generator_id) REFERENCES shield_generators (id), - FOREIGN KEY (associated_team) REFERENCES teams (id) ON DELETE SET NULL + FOREIGN KEY (shield_generator_id) REFERENCES shield_generators (id) ); CREATE TABLE IF NOT EXISTS power_cell_trackers ( diff --git a/src/games/infinite-recharge.ts b/src/games/infinite-recharge.ts index 1367bd1..14f2db4 100644 --- a/src/games/infinite-recharge.ts +++ b/src/games/infinite-recharge.ts @@ -185,14 +185,14 @@ export class InfiniteRechargeSQL extends SQLStoragePlan { ` power_cell_tracker_id, color_wheel_id, shield_generator_id,` + ` tech_fouls, fouls, yellow_card, red_card,` + ` estopped, borked, ranking_points, foul_points,` + - ` bonus_points, associated_team) ` + - `VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ` bonus_points) ` + + `VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( m.teamNumber, m.type, m.number, (m.alliance === 'BLUE' ? 0 : 1), powerCells, colorWheel, shieldGenerator, m.fouls.technical, m.fouls.regular, Number(m.cards.yellow), Number(m.cards.red), Number(m.emergencyStopped), Number(m.borked), m.rankingPoints, m.pointsFromFouls, - m.bonusPoints, (teamID || null), + m.bonusPoints, ); }); } @@ -204,7 +204,7 @@ export class InfiniteRechargeSQL extends SQLStoragePlan { /** Converts data from the database into a team */ dbDataToTeam(data: any) { - const matches = this.getStatement(`SELECT * FROM matches WHERE associated_team = ?`) + const matches = this.getStatement(`SELECT * FROM matches WHERE team_number = ?`) .all(data.id) .map((matchData) => this.dbDataToMatch(matchData)); @@ -270,15 +270,6 @@ export class InfiniteRechargeSQL extends SQLStoragePlan { this.matchInsertionTransaction(match); } - /** Inserts a team */ - insertTeam(team: Team) { - const id = this.getStatement(`INSERT OR REPLACE INTO teams (number) VALUES (?)`) - .run(team.number) - .lastInsertRowid; - for (const match of team.matches) { - this.matchInsertionTransaction(match, id); - } - } /** Inserts a power cell tracker */ private insertPowerCellTracker(tracker: PowerCellTracker) { diff --git a/src/storage/backend.test.ts b/src/storage/backend.test.ts index 6b131f1..3088bf5 100644 --- a/src/storage/backend.test.ts +++ b/src/storage/backend.test.ts @@ -26,17 +26,17 @@ import { let curMatchNum = 0; /** generates a Deep Space match for testing */ -function makeDSMatch(points: number, number?: number) { +function makeDSMatch(points: number, number?: number, team?: number) { const match = new DeepSpaceMatch( - 5940, 'test', number || curMatchNum++, 'BLUE', {bonusPoints: points, initialHABLevel: 1}, + team || 5940, 'test', number || curMatchNum++, 'BLUE', {bonusPoints: points, initialHABLevel: 1}, ); return match; } /** generates an Infinite Recharge match for testing */ -function makeIRMatch(points: number, number?: number) { +function makeIRMatch(points: number, number?: number, team?: number) { const match = new InfiniteRechargeMatch( - 5940, 'test', number || curMatchNum++, 'BLUE', {bonusPoints: points}, + team || 5940, 'test', number || curMatchNum++, 'BLUE', {bonusPoints: points}, ); return match; } @@ -97,22 +97,24 @@ describe.each(backends)('%s', (backend) => { // numbers and Team objects both need to work expect(backend.getMatchesByTeam(5940)).toEqual(backend.getMatchesByTeam(bread)); - backend.deleteTeam(5940); - expect(backend.getTeam(5940)).toEqual(null); + backend.deleteTeam(matchA.number); + expect(backend.getTeam(matchA.number)).toEqual(null); expect(backend.getMatchesByNumber(matchA.number)).toEqual([matchA]); backend.saveTeam(bread); backend.deleteTeam(bread, true); - expect(backend.getTeam(5940)).toEqual(null); + expect(backend.getTeam(matchA.number)).toEqual(null); expect(backend.getMatchesByNumber(matchA.number)).toEqual([]); }); it.each(matchGenerators)('should store matches', (makeMatch) => { - expect(backend.getMatchesByNumber(10)).toEqual([]); - expect(backend.getMatchesByNumber(11)).toEqual([]); + const matchANumber = curMatchNum++; + const matchBNumber = curMatchNum++; + expect(backend.getMatchesByNumber(matchANumber)).toEqual([]); + expect(backend.getMatchesByNumber(matchBNumber)).toEqual([]); - const matchA = makeMatch(0, 10); - const matchB = makeMatch(10, 11); + const matchA = makeMatch(0, matchANumber, 1); + const matchB = makeMatch(10, matchBNumber, 2); backend.saveMatch(matchA); backend.saveMatch(matchB); @@ -120,29 +122,23 @@ describe.each(backends)('%s', (backend) => { expect(backend.getMatchesByNumber(matchA.number)).toEqual([matchA]); expect(backend.getMatchesByNumber(matchB.number)).toEqual([matchB]); - // Matches should only be fetched here if they're associated with a team - // This is weird behavior - see src/storage/backend.ts:17 let matches = backend.getMatchesByTeam(matchA.teamNumber); - expect(matches).not.toContainEqual(matchA); + expect(matches).toContainEqual(matchA); expect(matches).not.toContainEqual(matchB); // Deletion - // Deleting a match only deletes matches associated with a team - // not all matches scouted for the team. We can change this if desired - // see see src/storage/backend.ts:17 const team = new Team(matchA.teamNumber, matchA, matchB); backend.saveTeam(team); backend.deleteMatchesByTeam(matchA.teamNumber); matches = backend.getMatchesByTeam(matchA.teamNumber); expect(matches).not.toContainEqual(matchA); - expect(matches).not.toContainEqual(matchB); expect(backend.getMatchesByNumber(matchA.number)).toEqual([]); - expect(backend.getMatchesByNumber(matchB.number)).toEqual([]); + expect(backend.getMatchesByNumber(matchB.number)).toContainEqual(matchB); backend.saveMatch(matchA); expect(backend.getMatchesByNumber(matchA.number)).toEqual([matchA]); - expect(backend.getMatchesByNumber(matchB.number)).toEqual([]); + expect(backend.getMatchesByNumber(matchB.number)).toContainEqual(matchB); backend.deleteMatchByNumber(matchA.number); expect(backend.getMatchesByNumber(matchA.number)).toEqual([]); diff --git a/src/storage/backend.ts b/src/storage/backend.ts index 2c91945..ac8b1d7 100644 --- a/src/storage/backend.ts +++ b/src/storage/backend.ts @@ -15,17 +15,7 @@ export interface StorageBackend { saveMatch(match: Match): void; /** Get all match data for a given match number */ getMatchesByNumber(number: number): Match[]; - /** - * Get only one match from a given match number. - * - * @deprecated Use getMatchesByNumber instead. - */ getMatchByNumber(number: number): Match | null; - /** - * Gets matches ASSOCIATED with the team (i.e. as part of the team, not just any match scouted for that team) - * I don't know what the desired behavior is here, and if we deprecate JSON crap it'd be easier to make it do - * what we actually want. - */ getMatchesByTeam(team: Team | number): Match[]; deleteMatchByNumber(number: number): void; deleteMatchesByTeam(team: Team | number): void; diff --git a/src/storage/sqlite.ts b/src/storage/sqlite.ts index 8d68c97..6e060a9 100644 --- a/src/storage/sqlite.ts +++ b/src/storage/sqlite.ts @@ -66,23 +66,6 @@ export abstract class SQLStoragePlan { statement.run(...args); } - - /** Inserts team */ - abstract insertTeam(team: Team): void; - /** gets teams */ - getTeams(conditions: {column: string, value: string | number}[]) { - const [args, where] = this.generateWhere(conditions); - const statement = this.getStatement(`SELECT * FROM teams ${where}`); - - return statement.all(...args).map((data) => this.dbDataToTeam(data)); - } - /** deletes teams */ - deleteTeams(conditions: {column: string, value: string | number}[]) { - const [args, where] = this.generateWhere(conditions); - const statement = this.getStatement(`DELETE FROM teams ${where}`); - - statement.run(...args); - } } /** Stores teams and matches from various games in SQL. */ @@ -109,7 +92,9 @@ export class SQLBackend implements StorageBackend { for (const plan of this.plans) { if (team.matches.every((match) => plan.applies(match))) { - plan.insertTeam(team); + for (const match of team.matches) { + plan.insertMatch(match); + } return; } } @@ -122,22 +107,19 @@ export class SQLBackend implements StorageBackend { /** gets a team */ getTeam(number: number) { for (const plan of this.plans) { - const teams = plan.getTeams([{column: 'number', value: number}]); - if (teams.length > 1) { - // Should never happen, afaik - throw new Error(`Somehow there are multiple teams in one storage plan with that number.`); + const matches = plan.getMatches([{column: 'team_number', value: number}]); + if (matches.length) { + return new Team(number, ...matches); } - if (teams[0]) return teams[0]; } return null; } - /** deletes a team */ - deleteTeam(team: Team | number, deleteMatches?: boolean) { + /** deletes all matches involving a team */ + deleteTeam(team: Team | number) { const value = typeof team === 'number' ? team : team.number; for (const plan of this.plans) { - plan.deleteTeams([{column: 'number', value}]); - if (deleteMatches) plan.deleteMatches([{column: 'team_number', value}]); + plan.deleteMatches([{column: 'team_number', value}]); } } @@ -173,8 +155,7 @@ export class SQLBackend implements StorageBackend { const value = typeof team === 'number' ? team : team.number; const results = []; for (const plan of this.plans) { - const teamID = plan.getStatement(`SELECT id FROM teams WHERE number = ?`).get(value)?.id; - if (teamID) results.push(...plan.getMatches([{column: 'associated_team', value: teamID}])); + results.push(...plan.getMatches([{column: 'team_number', value}])); } return results; }