Force explicit conversion of rows fetched from SQL DB

Everything is a scalar value now, and while this does involve more
boilerplate it will also catch type conversion issues like
"1" + 0 === "10" in the future.
This commit is contained in:
Tau 2019-10-10 22:26:23 -04:00
parent 30d42bd832
commit f9970fa81c
18 changed files with 135 additions and 115 deletions

View File

@ -30,7 +30,7 @@ class CardRepositoryImpl implements CardRepository {
await this._txn.modify(touchSql);
return extId;
return parseInt(extId, 10) as AimeId;
}
async register(luid: string, now: Date): Promise<AimeId> {

View File

@ -20,7 +20,7 @@ export class SqlBackgroundsRepository
const result = new Set<BackgroundCode>();
for (const row of rows) {
result.add(row.background_no);
result.add(parseInt(row.background_no) as BackgroundCode);
}
return result;

View File

@ -3,25 +3,25 @@ import sql from "sql-bricks-postgres";
import { Car, CarSelector } from "../model/car";
import { Profile } from "../model/profile";
import { CarRepository } from "../repo";
import { Id, Transaction, generateId } from "../../sql";
import { Id, Row, Transaction, generateId } from "../../sql";
function _extractRow(row: any): Car {
function _extractRow(row: Row): Car {
return {
selector: row.selector,
field_00: row.field_00,
field_02: row.field_02,
field_04: row.field_04.split(",").map((x: string) => parseInt(x, 10)),
field_46: row.field_46,
field_48: row.field_48,
field_4a: row.field_4a,
field_4c: row.field_4c,
field_50_lo: row.field_50_lo,
field_50_hi: row.field_50_hi,
field_58: row.field_58,
field_5a: row.field_5a,
field_5b: row.field_5b,
field_5c: row.field_5c,
field_5e: row.field_5e,
selector: parseInt(row.selector) as CarSelector,
field_00: parseInt(row.field_00),
field_02: parseInt(row.field_02),
field_04: row.field_04.split(",").map((x: string) => parseInt(x)),
field_46: parseInt(row.field_46),
field_48: parseInt(row.field_48),
field_4a: parseInt(row.field_4a),
field_4c: parseInt(row.field_4c),
field_50_lo: parseInt(row.field_50_lo),
field_50_hi: parseInt(row.field_50_hi),
field_58: parseInt(row.field_58),
field_5a: parseInt(row.field_5a),
field_5b: parseInt(row.field_5b),
field_5c: parseInt(row.field_5c),
field_5e: parseInt(row.field_5e),
};
}
@ -36,7 +36,7 @@ export class SqlCarRepository implements CarRepository {
const row = await this._txn.fetchRow(countSql);
return parseInt(row!.result, 10);
return parseInt(row!.result);
}
async loadAllCars(profileId: Id<Profile>): Promise<Car[]> {
@ -59,6 +59,10 @@ export class SqlCarRepository implements CarRepository {
const row = await this._txn.fetchRow(loadSql);
if (row === undefined) {
throw new Error(`Car selection not found, profileId=${profileId}`);
}
return _extractRow(row);
}

View File

@ -1,22 +1,23 @@
import sql from "sql-bricks-postgres";
import { Chara } from "../model/chara";
import { BackgroundCode, TitleCode } from "../model/base";
import { Chara, Gender } from "../model/chara";
import { Profile } from "../model/profile";
import { FacetRepository } from "../repo";
import { Id, Transaction } from "../../sql";
import { Id, Row, Transaction } from "../../sql";
export function _extractChara(row: any): Chara {
export function _extractChara(row: Row): Chara {
return {
gender: row.gender,
field_02: row.field_02,
field_04: row.field_04,
field_06: row.field_06,
field_08: row.field_08,
field_0a: row.field_0a,
field_0c: row.field_0c,
field_0e: row.field_0e,
title: row.title,
background: row.background,
gender: row.gender as Gender,
field_02: parseInt(row.field_02),
field_04: parseInt(row.field_04),
field_06: parseInt(row.field_06),
field_08: parseInt(row.field_08),
field_0a: parseInt(row.field_0a),
field_0c: parseInt(row.field_0c),
field_0e: parseInt(row.field_0e),
title: parseInt(row.title) as TitleCode,
background: parseInt(row.background) as BackgroundCode,
};
}
@ -31,6 +32,10 @@ export class SqlCharaRepository implements FacetRepository<Chara> {
const row = await this._txn.fetchRow(loadSql);
if (row === undefined) {
throw new Error(`Chara not found: profileId=${profileId}`);
}
return _extractChara(row);
}

View File

@ -15,10 +15,13 @@ export class SqlCoursePlaysRepository implements CoursePlaysRepository {
.where("cp.profile_id", profileId);
const rows = await this._txn.fetchRows(loadSql);
const result = new Map();
const result = new Map<CourseNo, number>();
for (const row of rows) {
result.set(row.course_no, row.count);
const courseNo = parseInt(row.course_no) as CourseNo;
const count = parseInt(row.count);
result.set(courseNo, count);
}
return result;

View File

@ -35,7 +35,11 @@ export class SqlMissionsRepository implements FacetRepository<MissionState> {
const rows = await this._txn.fetchRows(loadSoloSql);
for (const row of rows) {
result.solo[row.grid_no].cells[row.cell_no] = row.value;
const gridNo = parseInt(row.grid_no);
const cellNo = parseInt(row.cell_no);
const value = parseInt(row.value);
result.solo[gridNo].cells[cellNo] = value;
}
return result;

View File

@ -3,19 +3,19 @@ import sql from "sql-bricks-postgres";
import { Profile } from "../model/profile";
import { ProfileRepository } from "../repo";
import { AimeId } from "../../model";
import { Id, Transaction, generateId } from "../../sql";
import { Id, Row, Transaction, generateId } from "../../sql";
export function _extractProfile(row: any): Profile {
export function _extractProfile(row: Row): Profile {
return {
aimeId: row.aime_id,
aimeId: parseInt(row.aime_id) as AimeId,
name: row.name,
lv: row.lv,
exp: row.exp,
fame: row.fame,
dpoint: row.dpoint,
mileage: row.mileage,
accessTime: row.access_time,
registerTime: row.register_time,
lv: parseInt(row.lv),
exp: parseInt(row.exp),
fame: parseInt(row.fame),
dpoint: parseInt(row.dpoint),
mileage: parseInt(row.mileage),
accessTime: new Date(row.access_time),
registerTime: new Date(row.register_time),
};
}
@ -45,7 +45,7 @@ export class SqlProfileRepository implements ProfileRepository {
return undefined;
}
return row.id;
return BigInt(row.id) as Id<Profile>;
}
async load(id: Id<Profile>): Promise<Profile> {
@ -57,6 +57,10 @@ export class SqlProfileRepository implements ProfileRepository {
const row = await this._txn.fetchRow(loadSql);
if (row === undefined) {
throw new Error(`Profile not found, id=${id}`);
}
return _extractProfile(row);
}
@ -68,7 +72,7 @@ export class SqlProfileRepository implements ProfileRepository {
fame: profile.fame,
dpoint: profile.dpoint,
mileage: profile.mileage,
access_time: profile.accessTime,
access_time: profile.accessTime.toISOString(),
})
.where("id", id);
@ -99,8 +103,8 @@ export class SqlProfileRepository implements ProfileRepository {
fame: profile.fame,
dpoint: profile.dpoint,
mileage: profile.mileage,
register_time: profile.registerTime,
access_time: profile.accessTime,
register_time: profile.registerTime.toISOString(),
access_time: profile.accessTime.toISOString(),
});
await this._txn.modify(createSql);

View File

@ -21,10 +21,10 @@ export class SqlSettingsRepository implements FacetRepository<Settings> {
}
return {
music: row.music,
pack: parseInt(row.pack, 10),
paperCup: row.paper_cup,
gauges: row.gauges,
music: parseInt(row.music),
pack: parseInt(row.pack),
paperCup: parseInt(row.paper_cup),
gauges: parseInt(row.gauges),
};
}

View File

@ -19,8 +19,8 @@ export class SqlStoryRepository implements FacetRepository<Story> {
// Must succeed even if nonexistent (required by save method below)
const result = {
x: header !== undefined ? header.x : 0,
y: header !== undefined ? header.y : 0,
x: header !== undefined ? parseInt(header.x) : 0,
y: header !== undefined ? parseInt(header.y) : 0,
rows: new Array<StoryRow>(),
};
@ -42,10 +42,12 @@ export class SqlStoryRepository implements FacetRepository<Story> {
const rows = await this._txn.fetchRows(loadCellSql);
for (const row of rows) {
const cell = result.rows[row.row_no].cells[row.col_no];
const rowNo = parseInt(row.row_no);
const colNo = parseInt(row.col_no);
const cell = result.rows[rowNo].cells[colNo];
cell.a = row.a;
cell.b = row.b;
cell.a = parseInt(row.a);
cell.b = parseInt(row.b);
}
return result;

View File

@ -21,7 +21,7 @@ export class SqlTeamRepository implements TeamRepository {
throw new Error(`Team not found for ExtID ${extId}`);
}
return row.id;
return BigInt(row.id) as Id<Team>;
}
async load(id: Id<Team>): Promise<Team> {
@ -37,10 +37,10 @@ export class SqlTeamRepository implements TeamRepository {
}
return {
extId: row.ext_id,
extId: parseInt(row.ext_id) as ExtId<Team>,
name: row.name,
nameBg: row.name_bg,
nameFx: row.name_fx,
nameBg: parseInt(row.name_bg),
nameFx: parseInt(row.name_fx),
registerTime: new Date(row.register_time),
};
}

View File

@ -19,10 +19,10 @@ export class SqlTeamAutoRepository implements TeamAutoRepository {
return (
row && [
{
serialNo: row.serial_no,
nameIdx: row.name_idx,
serialNo: parseInt(row.serial_no),
nameIdx: parseInt(row.name_idx),
},
row.id,
BigInt(row.id) as Id<Team>,
]
);
}

View File

@ -22,7 +22,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
return undefined;
}
return row.team_id;
return BigInt(row.team_id) as Id<Team>;
}
async findLeader(teamId: Id<Team>): Promise<Id<Profile> | undefined> {
@ -38,7 +38,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
return undefined;
}
return row.id;
return BigInt(row.id) as Id<Profile>;
}
async loadRoster(teamId: Id<Team>): Promise<TeamMember[]> {
@ -52,10 +52,10 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
const rows = await this._txn.fetchRows(loadSql);
return rows.map((row: any) => ({
return rows.map(row => ({
profile: _extractProfile(row),
chara: _extractChara(row),
leader: row.leader,
leader: !!row.leader,
joinTime: new Date(row.join_time),
}));
}
@ -91,7 +91,7 @@ export class SqlTeamMemberRepository implements TeamMemberRepository {
const row = await this._txn.fetchRow(countSql);
if (row!.count >= 6) {
if (parseInt(row!.count) >= 6) {
throw new Error(`Team ${teamId} is full`);
}

View File

@ -22,16 +22,13 @@ export class SqlTeamReservationRepository
async occupancyHack(teamId: Id<Team>): Promise<number> {
await this._lockTeam(teamId);
// counts get returned as strings, so 1 + 0 = 10.
// it hardly needs to be said but fuck javascript.
const memberSql = sql
.select("count(*) as count")
.from("idz_team_member tm")
.where("tm.team_id", teamId);
const memberRes = await this._txn.fetchRow(memberSql);
const memberCount = parseInt(memberRes!.count, 10);
const memberCount = parseInt(memberRes!.count);
const reservSql = sql
.select("count(*) as count")
@ -39,7 +36,7 @@ export class SqlTeamReservationRepository
.where("tr.team_id", teamId);
const reservRes = await this._txn.fetchRow(reservSql);
const reservCount = parseInt(reservRes!.count, 10);
const reservCount = parseInt(reservRes!.count);
return memberCount + reservCount;
}

View File

@ -1,10 +1,23 @@
import sql from "sql-bricks-postgres";
import { RouteNo } from "../model/base";
import { CarSelector } from "../model/car";
import { Profile } from "../model/profile";
import { TimeAttackScore } from "../model/timeAttack";
import { TimeAttackRepository, TopTenResult } from "../repo";
import { Id, Transaction, generateId } from "../../sql";
import { Id, Row, Transaction, generateId } from "../../sql";
function _extractRow(row: Row): TimeAttackScore {
return {
routeNo: parseInt(row.route_no) as RouteNo,
timestamp: new Date(row.timestamp),
flags: parseInt(row.flags),
totalTime: parseFloat(row.total_time),
sectionTimes: row.section_times.split(",").map(parseFloat),
grade: parseInt(row.grade),
carSelector: parseInt(row.car_selector) as CarSelector,
};
}
export class SqlTimeAttackRepository implements TimeAttackRepository {
constructor(private readonly _txn: Transaction) {}
@ -26,15 +39,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
return rows.map(row => ({
driverName: row.name,
ta: {
routeNo: row.route_no,
timestamp: new Date(row.timestamp),
flags: row.flags,
totalTime: row.total_time,
sectionTimes: row.section_times.split(","),
grade: row.grade,
carSelector: row.car_selector,
},
ta: _extractRow(row),
}));
}
@ -46,15 +51,7 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
const rows = await this._txn.fetchRows(loadSql);
return rows.map(row => ({
routeNo: row.route_no,
timestamp: new Date(row.timestamp),
flags: row.flags,
totalTime: row.total_time,
sectionTimes: row.section_times.split(",").map(parseFloat),
grade: row.grade,
carSelector: row.car_selector,
}));
return rows.map(_extractRow);
}
async save(profileId: Id<Profile>, score: TimeAttackScore): Promise<void> {
@ -94,20 +91,22 @@ export class SqlTimeAttackRepository implements TimeAttackRepository {
});
await this._txn.modify(insertSql);
} else if (score.totalTime < row.total_time) {
const updateSql = sql
.update("idz_ta_best", {
total_time: score.totalTime,
section_times: score.sectionTimes.join(","),
flags: score.flags,
grade: score.grade,
car_selector: score.carSelector,
timestamp: score.timestamp,
})
.where("profile_id", profileId)
.where("route_no", score.routeNo);
} else {
if (score.totalTime < parseFloat(row.total_time)) {
const updateSql = sql
.update("idz_ta_best", {
total_time: score.totalTime,
section_times: score.sectionTimes.join(","),
flags: score.flags,
grade: score.grade,
car_selector: score.carSelector,
timestamp: score.timestamp,
})
.where("profile_id", profileId)
.where("route_no", score.routeNo);
await this._txn.modify(updateSql);
await this._txn.modify(updateSql);
}
}
}
}

View File

@ -18,7 +18,7 @@ export class SqlTitlesRepository implements FlagRepository<TitleCode> {
const result = new Set<TitleCode>();
for (const row of rows) {
result.add(row.title_no);
result.add(parseInt(row.title_no) as TitleCode);
}
return result;

View File

@ -21,10 +21,10 @@ export class SqlUnlocksRepository implements FacetRepository<Unlocks> {
}
return {
cup: row.cup,
gauges: row.gauges,
music: row.music,
lastMileageReward: row.last_mileage_reward,
cup: parseInt(row.cup),
gauges: parseInt(row.gauges),
music: parseInt(row.music),
lastMileageReward: parseInt(row.last_mileage_reward),
};
}

View File

@ -1,7 +1,9 @@
import { BackgroundCode, TitleCode } from "./base";
export type Gender = "male" | "female";
export interface Chara {
gender: "male" | "female";
gender: Gender;
field_02: number;
field_04: number;
field_06: number;

View File

@ -3,7 +3,7 @@ import * as sql from "sql-bricks-postgres";
export type Id<T> = bigint & { __id: T };
export interface Row {
[key: string]: any;
[key: string]: string;
}
export interface Transaction {