Skip to content

Commit

Permalink
Refactor team storage
Browse files Browse the repository at this point in the history
  • Loading branch information
TheAnnalyst committed Nov 6, 2021
1 parent 7ca046d commit a3a8a43
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 106 deletions.
11 changes: 1 addition & 10 deletions src/games/deep-space.sql
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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 (
Expand Down
18 changes: 4 additions & 14 deletions src/games/deep-space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,17 @@ export class DeepSpaceSQL extends SQLStoragePlan<DeepSpaceMatch> {
`(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),
Expand Down Expand Up @@ -259,7 +259,7 @@ export class DeepSpaceSQL extends SQLStoragePlan<DeepSpaceMatch> {

/** 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));

Expand All @@ -270,16 +270,6 @@ export class DeepSpaceSQL extends SQLStoragePlan<DeepSpaceMatch> {
insertMatch(match: DeepSpaceMatch) {
this.matchInsertionTransaction(match);
}

/** Inserts a team */
insertTeam(team: Team<DeepSpaceMatch>) {
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 */
Expand Down
11 changes: 1 addition & 10 deletions src/games/infinite-recharge.sql
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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 (
Expand Down
17 changes: 4 additions & 13 deletions src/games/infinite-recharge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,14 @@ export class InfiniteRechargeSQL extends SQLStoragePlan<InfiniteRechargeMatch> {
` 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,
);
});
}
Expand All @@ -204,7 +204,7 @@ export class InfiniteRechargeSQL extends SQLStoragePlan<InfiniteRechargeMatch> {

/** 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));

Expand Down Expand Up @@ -270,15 +270,6 @@ export class InfiniteRechargeSQL extends SQLStoragePlan<InfiniteRechargeMatch> {
this.matchInsertionTransaction(match);
}

/** Inserts a team */
insertTeam(team: Team<InfiniteRechargeMatch>) {
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) {
Expand Down
36 changes: 16 additions & 20 deletions src/storage/backend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -97,52 +97,48 @@ 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);

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([]);
Expand Down
10 changes: 0 additions & 10 deletions src/storage/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Match> | number): Match[];
deleteMatchByNumber(number: number): void;
deleteMatchesByTeam(team: Team<Match> | number): void;
Expand Down
39 changes: 10 additions & 29 deletions src/storage/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +66,6 @@ export abstract class SQLStoragePlan<T extends Match> {

statement.run(...args);
}

/** Inserts team */
abstract insertTeam(team: Team<T>): 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. */
Expand All @@ -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;
}
}
Expand All @@ -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<typeof matches[0]>(number, ...matches);
}
if (teams[0]) return teams[0];
}
return null;
}

/** deletes a team */
deleteTeam(team: Team<Match> | number, deleteMatches?: boolean) {
/** deletes all matches involving a team */
deleteTeam(team: Team<Match> | 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}]);
}
}

Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit a3a8a43

Please sign in to comment.