feat: add db query timeout option
This commit is contained in:
parent
4b8dc1fa1c
commit
ab5dcd3640
6 changed files with 105 additions and 67 deletions
|
|
@ -28,5 +28,6 @@
|
||||||
"user": "root",
|
"user": "root",
|
||||||
"password": "root",
|
"password": "root",
|
||||||
"database": "acore_world"
|
"database": "acore_world"
|
||||||
}
|
},
|
||||||
|
"dbQueryTimeout": 10000
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import * as morgan from "morgan";
|
||||||
import { Pool, createPool } from "mysql2/promise";
|
import { Pool, createPool } from "mysql2/promise";
|
||||||
import { engine as handlebarsEngine } from "express-handlebars";
|
import { engine as handlebarsEngine } from "express-handlebars";
|
||||||
|
|
||||||
import { Config } from "./Config";
|
import { Config, IRealmConfig } from "./Config";
|
||||||
import { DbcManager } from "./data/DbcReader";
|
import { DbcManager } from "./data/DbcReader";
|
||||||
import { CharacterCustomization } from "./data/CharacterCustomization";
|
import { CharacterCustomization } from "./data/CharacterCustomization";
|
||||||
import { IndexController } from "./controllers/IndexController";
|
import { IndexController } from "./controllers/IndexController";
|
||||||
|
|
@ -148,14 +148,12 @@ export class Armory {
|
||||||
|
|
||||||
// Respond with html page
|
// Respond with html page
|
||||||
if (req.accepts("html")) {
|
if (req.accepts("html")) {
|
||||||
res.render("error.html", this.getErrorViewData(404, req));
|
return res.render("error.html", this.getErrorViewData(404, req));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with json
|
// Respond with json
|
||||||
if (req.accepts("json")) {
|
if (req.accepts("json")) {
|
||||||
res.json({ error: this.errorNames[404] });
|
return res.json({ error: this.errorNames[404] });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to plain-text
|
// Default to plain-text
|
||||||
|
|
@ -172,6 +170,10 @@ export class Armory {
|
||||||
return this.charsDbs[realm.toLowerCase()];
|
return this.charsDbs[realm.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getRealm(realm: string): IRealmConfig {
|
||||||
|
return this.config.realms.find(r => r.name.toLowerCase() === realm.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
public gc(): void {
|
public gc(): void {
|
||||||
if (this.config.loadDbcs) {
|
if (this.config.loadDbcs) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export class Config {
|
||||||
public hideGameMasters: boolean;
|
public hideGameMasters: boolean;
|
||||||
public realms: IRealmConfig[];
|
public realms: IRealmConfig[];
|
||||||
public worldDatabase: IDatabaseConfig;
|
public worldDatabase: IDatabaseConfig;
|
||||||
|
public dbQueryTimeout: number;
|
||||||
|
|
||||||
private static checkedMissingField: boolean = false;
|
private static checkedMissingField: boolean = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { Pool } from "mysql2/promise";
|
||||||
import { Query } from "express-serve-static-core";
|
import { Query } from "express-serve-static-core";
|
||||||
import { Connection, RowDataPacket } from "mysql2/promise";
|
|
||||||
|
|
||||||
export interface IResult {
|
export interface IResult {
|
||||||
recordsTotal: number;
|
recordsTotal: number;
|
||||||
|
|
@ -30,7 +30,7 @@ export class DataTablesSsp {
|
||||||
public joins: IColumnJoin[] = [];
|
public joins: IColumnJoin[] = [];
|
||||||
public extraDataColumns: string[] = [];
|
public extraDataColumns: string[] = [];
|
||||||
|
|
||||||
private db: Connection;
|
private db: Pool;
|
||||||
private table: string;
|
private table: string;
|
||||||
private primaryKey: string;
|
private primaryKey: string;
|
||||||
private columnSettings: IColumnSettings[];
|
private columnSettings: IColumnSettings[];
|
||||||
|
|
@ -65,7 +65,7 @@ export class DataTablesSsp {
|
||||||
private orderSql: string = "";
|
private orderSql: string = "";
|
||||||
private joinSql: string = "";
|
private joinSql: string = "";
|
||||||
|
|
||||||
public constructor(query: Query, db: Connection, table: string, primaryKey: string, columnSettings: IColumnSettings[]) {
|
public constructor(query: Query, db: Pool, table: string, primaryKey: string, columnSettings: IColumnSettings[]) {
|
||||||
this.start = parseInt(query.start as string, 10);
|
this.start = parseInt(query.start as string, 10);
|
||||||
this.length = parseInt(query.length as string, 10);
|
this.length = parseInt(query.length as string, 10);
|
||||||
this.draw = parseInt(query.draw as string, 10);
|
this.draw = parseInt(query.draw as string, 10);
|
||||||
|
|
@ -198,7 +198,7 @@ export class DataTablesSsp {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async run(): Promise<IResult> {
|
public async run(queryTimeout: number = 10_000): Promise<IResult> {
|
||||||
this.limit()
|
this.limit()
|
||||||
.order()
|
.order()
|
||||||
.join()
|
.join()
|
||||||
|
|
@ -206,16 +206,25 @@ export class DataTablesSsp {
|
||||||
|
|
||||||
const bindings = [...this.filterBindings, ...this.customBindings];
|
const bindings = [...this.filterBindings, ...this.customBindings];
|
||||||
|
|
||||||
let [rows, fields] = await this.db.query(this.buildTotalCountSql(), this.customBindings);
|
let [rows, fields] = await this.db.query({
|
||||||
|
sql: this.buildTotalCountSql(),
|
||||||
|
values: this.customBindings,
|
||||||
|
timeout: queryTimeout,
|
||||||
|
});
|
||||||
const recordsTotal = rows[0].count;
|
const recordsTotal = rows[0].count;
|
||||||
|
|
||||||
[rows, fields] = await this.db.query(this.buildFilteredCountSql(), bindings);
|
[rows, fields] = await this.db.query({
|
||||||
|
sql: this.buildFilteredCountSql(),
|
||||||
|
values: bindings,
|
||||||
|
timeout: queryTimeout,
|
||||||
|
});
|
||||||
const recordsFiltered = rows[0].count;
|
const recordsFiltered = rows[0].count;
|
||||||
|
|
||||||
[rows, fields] = await this.db.query({
|
[rows, fields] = await this.db.query({
|
||||||
sql: this.sql(),
|
sql: this.sql(),
|
||||||
rowsAsArray: true,
|
rowsAsArray: true,
|
||||||
values: bindings,
|
values: bindings,
|
||||||
|
timeout: queryTimeout,
|
||||||
});
|
});
|
||||||
rows = (rows as any[][]).map(row => {
|
rows = (rows as any[][]).map(row => {
|
||||||
for (let i = 0; i < this.columnSettings.length; ++i) {
|
for (let i = 0; i < this.columnSettings.length; ++i) {
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,10 @@ export class CharacterController {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.itemSocketBonuses = {};
|
this.itemSocketBonuses = {};
|
||||||
let [rows, fields] = await this.armory.worldDb.query("SELECT entry, socketBonus FROM item_template WHERE socketBonus <> 0");
|
let [rows, fields] = await this.armory.worldDb.query({
|
||||||
|
sql: "SELECT entry, socketBonus FROM item_template WHERE socketBonus <> 0",
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
for (const row of rows as RowDataPacket[]) {
|
for (const row of rows as RowDataPacket[]) {
|
||||||
this.itemSocketBonuses[row.entry] = row.socketBonus;
|
this.itemSocketBonuses[row.entry] = row.socketBonus;
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +154,7 @@ export class CharacterController {
|
||||||
const realmName = req.params.realm;
|
const realmName = req.params.realm;
|
||||||
const charName = req.params.name;
|
const charName = req.params.name;
|
||||||
|
|
||||||
const realm = this.getRealm(realmName);
|
const realm = this.armory.getRealm(realmName);
|
||||||
if (realm === undefined) {
|
if (realm === undefined) {
|
||||||
// Could not find realm
|
// Could not find realm
|
||||||
return next(404);
|
return next(404);
|
||||||
|
|
@ -195,7 +198,7 @@ export class CharacterController {
|
||||||
const realmName = req.params.realm;
|
const realmName = req.params.realm;
|
||||||
const charName = req.params.name;
|
const charName = req.params.name;
|
||||||
|
|
||||||
const realm = this.getRealm(realmName);
|
const realm = this.armory.getRealm(realmName);
|
||||||
if (realm === undefined) {
|
if (realm === undefined) {
|
||||||
// Could not find realm
|
// Could not find realm
|
||||||
return next(404);
|
return next(404);
|
||||||
|
|
@ -222,7 +225,7 @@ export class CharacterController {
|
||||||
const realmName = req.params.realm;
|
const realmName = req.params.realm;
|
||||||
const charName = req.params.name;
|
const charName = req.params.name;
|
||||||
|
|
||||||
const realm = this.getRealm(realmName);
|
const realm = this.armory.getRealm(realmName);
|
||||||
if (realm === undefined) {
|
if (realm === undefined) {
|
||||||
// Could not find realm
|
// Could not find realm
|
||||||
return next(404);
|
return next(404);
|
||||||
|
|
@ -244,7 +247,7 @@ export class CharacterController {
|
||||||
const realmName = req.params.realm;
|
const realmName = req.params.realm;
|
||||||
const character = parseInt(req.params.character) || -1;
|
const character = parseInt(req.params.character) || -1;
|
||||||
|
|
||||||
const realm = this.getRealm(realmName);
|
const realm = this.armory.getRealm(realmName);
|
||||||
if (realm === undefined) {
|
if (realm === undefined) {
|
||||||
// Could not find realm
|
// Could not find realm
|
||||||
return next(404);
|
return next(404);
|
||||||
|
|
@ -275,13 +278,10 @@ export class CharacterController {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRealm(realm: string): IRealmConfig {
|
|
||||||
return this.armory.config.realms.find(r => r.name.toLowerCase() === realm.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getCharacterData(realm: IRealmConfig, character: string | number): Promise<ICharacterData> {
|
private async getCharacterData(realm: IRealmConfig, character: string | number): Promise<ICharacterData> {
|
||||||
const where = typeof character === "string" ? "LOWER(`characters`.`name`) = LOWER(?)" : "`characters`.`guid` = ?";
|
const where = typeof character === "string" ? "LOWER(`characters`.`name`) = LOWER(?)" : "`characters`.`guid` = ?";
|
||||||
const [rows, fields] = await this.armory.getCharactersDb(realm.name).query(`
|
const [rows, fields] = await this.armory.getCharactersDb(realm.name).query({
|
||||||
|
sql: `
|
||||||
SELECT \`characters\`.\`guid\`, \`characters\`.\`name\`, \`race\`, \`class\`, \`gender\`, \`level\`, \`skin\`, \`face\`, \`hairStyle\`, \`hairColor\`, \`facialStyle\`, \`playerFlags\`, \`online\`, \`guild\`.\`name\` AS \`guild\`
|
SELECT \`characters\`.\`guid\`, \`characters\`.\`name\`, \`race\`, \`class\`, \`gender\`, \`level\`, \`skin\`, \`face\`, \`hairStyle\`, \`hairColor\`, \`facialStyle\`, \`playerFlags\`, \`online\`, \`guild\`.\`name\` AS \`guild\`
|
||||||
FROM \`characters\`
|
FROM \`characters\`
|
||||||
LEFT JOIN \`guild_member\` ON \`guild_member\`.\`guid\` = \`characters\`.\`guid\`
|
LEFT JOIN \`guild_member\` ON \`guild_member\`.\`guid\` = \`characters\`.\`guid\`
|
||||||
|
|
@ -290,7 +290,10 @@ export class CharacterController {
|
||||||
WHERE
|
WHERE
|
||||||
${where}
|
${where}
|
||||||
AND (\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0 OR ? = 0)
|
AND (\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0 OR ? = 0)
|
||||||
`, [character, this.armory.config.hideGameMasters ? 1 : 0]);
|
`,
|
||||||
|
values: [character, this.armory.config.hideGameMasters ? 1 : 0],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
if ((rows as RowDataPacket[]).length === 0) {
|
if ((rows as RowDataPacket[]).length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -299,21 +302,29 @@ export class CharacterController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getEquipmentData(realm: string, charGuid: number): Promise<IEquipmentData[]> {
|
private async getEquipmentData(realm: string, charGuid: number): Promise<IEquipmentData[]> {
|
||||||
const [rows, fields] = await this.armory.getCharactersDb(realm).query(`
|
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
|
||||||
|
sql: `
|
||||||
SELECT character_inventory.slot, item_instance.itemEntry, item_instance.flags, item_instance.enchantments, item_instance.randomPropertyId
|
SELECT character_inventory.slot, item_instance.itemEntry, item_instance.flags, item_instance.enchantments, item_instance.randomPropertyId
|
||||||
FROM character_inventory
|
FROM character_inventory
|
||||||
JOIN item_instance ON item_instance.guid = character_inventory.item
|
JOIN item_instance ON item_instance.guid = character_inventory.item
|
||||||
WHERE character_inventory.guid = ? AND character_inventory.bag = 0 AND character_inventory.slot IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)
|
WHERE character_inventory.guid = ? AND character_inventory.bag = 0 AND character_inventory.slot IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)
|
||||||
`, [charGuid]);
|
`,
|
||||||
|
values: [charGuid],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
return rows as RowDataPacket[] as IEquipmentData[];
|
return rows as RowDataPacket[] as IEquipmentData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getMounts(realm: string, charGuid: number): Promise<IMount[]> {
|
private async getMounts(realm: string, charGuid: number): Promise<IMount[]> {
|
||||||
const [rows, fields] = await this.armory.getCharactersDb(realm).query(`
|
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
|
||||||
|
sql: `
|
||||||
SELECT spell
|
SELECT spell
|
||||||
FROM character_spell
|
FROM character_spell
|
||||||
WHERE guid = ? AND spell IN (?)
|
WHERE guid = ? AND spell IN (?)
|
||||||
`, [charGuid, this.mountSpells]);
|
`,
|
||||||
|
values:[charGuid, this.mountSpells],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
return (rows as RowDataPacket[])
|
return (rows as RowDataPacket[])
|
||||||
.map(row => this.mountBySpellId[row.spell])
|
.map(row => this.mountBySpellId[row.spell])
|
||||||
|
|
@ -640,11 +651,15 @@ export class CharacterController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTalents(realm: string, character: number): Promise<number[][]> {
|
private async getTalents(realm: string, character: number): Promise<number[][]> {
|
||||||
const [rows, fields] = await this.armory.getCharactersDb(realm).query(`
|
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
|
||||||
|
sql: `
|
||||||
SELECT spell, specMask
|
SELECT spell, specMask
|
||||||
FROM character_talent
|
FROM character_talent
|
||||||
WHERE guid = ?
|
WHERE guid = ?
|
||||||
`, [character]);
|
`,
|
||||||
|
values: [character],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
const talents: number[][] = [[], []];
|
const talents: number[][] = [[], []];
|
||||||
for (const row of rows as RowDataPacket[]) {
|
for (const row of rows as RowDataPacket[]) {
|
||||||
|
|
@ -691,11 +706,15 @@ export class CharacterController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGlyphs(realm: string, character: number): Promise<any[][]> {
|
private async getGlyphs(realm: string, character: number): Promise<any[][]> {
|
||||||
const [rows, fields] = await this.armory.getCharactersDb(realm).query(`
|
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
|
||||||
|
sql: `
|
||||||
SELECT guid, talentGroup, glyph1, glyph2, glyph3, glyph4, glyph5, glyph6
|
SELECT guid, talentGroup, glyph1, glyph2, glyph3, glyph4, glyph5, glyph6
|
||||||
FROM character_glyphs
|
FROM character_glyphs
|
||||||
WHERE guid = ?
|
WHERE guid = ?
|
||||||
`, [character]);
|
`,
|
||||||
|
values: [character],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
const glyphs = [[], []];
|
const glyphs = [[], []];
|
||||||
for (const row of rows as RowDataPacket[]) {
|
for (const row of rows as RowDataPacket[]) {
|
||||||
|
|
@ -731,11 +750,15 @@ export class CharacterController {
|
||||||
.toArray();
|
.toArray();
|
||||||
const achievements = await Promise.all(promises);
|
const achievements = await Promise.all(promises);
|
||||||
|
|
||||||
const [rows, fields] = await this.armory.getCharactersDb(realm).query(`
|
const [rows, fields] = await this.armory.getCharactersDb(realm).query({
|
||||||
|
sql: `
|
||||||
SELECT achievement, date
|
SELECT achievement, date
|
||||||
FROM character_achievement
|
FROM character_achievement
|
||||||
WHERE guid = ?
|
WHERE guid = ?
|
||||||
`, [charData.guid]);
|
`,
|
||||||
|
values: [charData.guid],
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
const earned = {};
|
const earned = {};
|
||||||
for (const row of rows as RowDataPacket[]) {
|
for (const row of rows as RowDataPacket[]) {
|
||||||
earned[row.achievement] = {
|
earned[row.achievement] = {
|
||||||
|
|
|
||||||
|
|
@ -54,21 +54,23 @@ export class IndexController {
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = this.armory.getCharactersDb(realm.name);
|
const db = this.armory.getCharactersDb(realm.name);
|
||||||
const conn = await db.getConnection();
|
|
||||||
|
|
||||||
if (!(realm.name in this.charsetCache)) {
|
if (!(realm.name in this.charsetCache)) {
|
||||||
let [rows, fields] = await db.query(`
|
let [rows, fields] = await db.query({
|
||||||
|
sql: `
|
||||||
SELECT CCSA.character_set_name FROM information_schema.\`TABLES\` T,
|
SELECT CCSA.character_set_name FROM information_schema.\`TABLES\` T,
|
||||||
information_schema.\`COLLATION_CHARACTER_SET_APPLICABILITY\` CCSA
|
information_schema.\`COLLATION_CHARACTER_SET_APPLICABILITY\` CCSA
|
||||||
WHERE CCSA.collation_name = T.table_collation
|
WHERE CCSA.collation_name = T.table_collation
|
||||||
AND T.table_schema = "${conn.config.database}"
|
AND T.table_schema = "${(await db.getConnection()).config.database}"
|
||||||
AND T.table_name = "characters"
|
AND T.table_name = "characters"
|
||||||
`);
|
`,
|
||||||
|
timeout: this.armory.config.dbQueryTimeout,
|
||||||
|
});
|
||||||
this.charsetCache[realm.name] = rows[0].character_set_name;
|
this.charsetCache[realm.name] = rows[0].character_set_name;
|
||||||
}
|
}
|
||||||
const charSet = this.charsetCache[realm.name];
|
const charSet = this.charsetCache[realm.name];
|
||||||
|
|
||||||
let ssp = new DataTablesSsp(req.query, conn, "characters", "guid", [
|
let ssp = new DataTablesSsp(req.query, db, "characters", "guid", [
|
||||||
{ name: "name", collation: `${charSet}_general_ci` },
|
{ name: "name", collation: `${charSet}_general_ci` },
|
||||||
{ table: "guild", name: "name" },
|
{ table: "guild", name: "name" },
|
||||||
{ name: "level" },
|
{ name: "level" },
|
||||||
|
|
@ -89,7 +91,7 @@ export class IndexController {
|
||||||
|
|
||||||
const result = await ssp
|
const result = await ssp
|
||||||
.where("`deleteInfos_Account` IS NULL")
|
.where("`deleteInfos_Account` IS NULL")
|
||||||
.run();
|
.run(this.armory.config.dbQueryTimeout);
|
||||||
(result as any).realm = realm.name;
|
(result as any).realm = realm.name;
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue