Skip to content

Commit

Permalink
Support Postgres in database.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
Zarel committed Nov 30, 2023
1 parent 84e373f commit a1a06b9
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 52 deletions.
14 changes: 6 additions & 8 deletions src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,9 +662,8 @@ export const actions: {[k: string]: QueryHandler} = {
}
let teams = [];
try {
teams = await tables.pgdb.query(
'SELECT teamid, team, format, title as name FROM teams WHERE ownerid = $1', [this.user.id]
) ?? [];
teams = await tables.pgdb.query<any>(
)`SELECT teamid, team, format, title as name FROM teams WHERE ownerid = ${this.user.id}`;
} catch (e) {
Server.crashlog(e, 'a teams database query', params);
throw new ActionError('The server could not load your teams. Please try again later.');
Expand Down Expand Up @@ -693,13 +692,12 @@ export const actions: {[k: string]: QueryHandler} = {
throw new ActionError("Invalid team ID");
}
try {
const data = await tables.pgdb.query(
`SELECT ownerid, team, private as privacy FROM teams WHERE teamid = $1`, [teamid]
);
if (!data || !data.length || data[0].ownerid !== this.user.id) {
const data = await tables.pgdb.queryOne(
)`SELECT ownerid, team, private as privacy FROM teams WHERE teamid = ${teamid} LIMIT 1`;
if (!data || data.ownerid !== this.user.id) {
return {team: null};
}
return data[0];
return data;
} catch (e) {
Server.crashlog(e, 'a teams database request', params);
throw new ActionError("Failed to fetch team. Please try again later.");
Expand Down
92 changes: 53 additions & 39 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,12 @@ export interface ResultRow {[k: string]: BasicSQLValue}

export const connectedDatabases: Database[] = [];

export class Database {
connection: mysql.Pool;
export abstract class Database<Pool extends mysql.Pool | pg.Pool = mysql.Pool | pg.Pool, OkPacket = unknown> {
connection: Pool;
prefix: string;
constructor(config: mysql.PoolOptions & {prefix?: string}) {
this.prefix = config.prefix || "";
if (config.prefix) {
config = {...config};
delete config.prefix;
}
this.connection = mysql.createPool(config);
constructor(connection: Pool, prefix = '') {
this.prefix = prefix;
this.connection = connection;
connectedDatabases.push(this);
}
resolveSQL(query: SQLStatement): [query: string, values: BasicSQLValue[]] {
Expand All @@ -131,35 +127,23 @@ export class Database {
for (let i = 0; i < query.values.length; i++) {
const value = query.values[i];
if (query.sql[i + 1].startsWith('`')) {
sql = sql.slice(0, -1) + this.connection.escapeId('' + value) + query.sql[i + 1].slice(1);
sql = sql.slice(0, -1) + this.escapeId('' + value) + query.sql[i + 1].slice(1);
} else {
sql += '?' + query.sql[i + 1];
values.push(value);
}
}
return [sql, values];
}
abstract escapeId(param: string): string;
abstract _query(sql: string, values: BasicSQLValue[]): Promise<any>;
query<T = ResultRow>(sql: SQLStatement): Promise<T[]>;
query<T = ResultRow>(): (strings: TemplateStringsArray, ...rest: SQLValue[]) => Promise<T[]>;
query<T = ResultRow>(sql?: SQLStatement) {
if (!sql) return (strings: any, ...rest: any) => this.query<T>(new SQLStatement(strings, rest));

return new Promise<T[]>((resolve, reject) => {
const [query, values] = this.resolveSQL(sql);
this.connection.query(query, values, (e, results: any) => {
if (e) {
return reject(new Error(`${e.message} (${query}) (${values}) [${e.code}]`));
}
if (Array.isArray(results)) {
for (const row of results) {
for (const col in row) {
if (Buffer.isBuffer(row[col])) row[col] = row[col].toString();
}
}
}
return resolve(results);
});
});
const [query, values] = this.resolveSQL(sql);
return this._query(query, values);
}
queryOne<T = ResultRow>(sql: SQLStatement): Promise<T | undefined>;
queryOne<T = ResultRow>(): (strings: TemplateStringsArray, ...rest: SQLValue[]) => Promise<T | undefined>;
Expand All @@ -168,14 +152,14 @@ export class Database {

return this.query<T>(sql).then(res => Array.isArray(res) ? res[0] : res);
}
queryExec(sql: SQLStatement): Promise<mysql.OkPacket>;
queryExec(): (strings: TemplateStringsArray, ...rest: SQLValue[]) => Promise<mysql.OkPacket>;
queryExec(sql: SQLStatement): Promise<OkPacket>;
queryExec(): (strings: TemplateStringsArray, ...rest: SQLValue[]) => Promise<OkPacket>;
queryExec(sql?: SQLStatement) {
if (!sql) return (strings: any, ...rest: any) => this.queryExec(new SQLStatement(strings, rest));
return this.queryOne<mysql.OkPacket>(sql);
return this.queryOne<OkPacket>(sql);
}
close() {
this.connection.end();
void this.connection.end();
}
}

Expand All @@ -198,7 +182,7 @@ export class DatabaseTable<Row> {
this.primaryKeyName = primaryKeyName;
}
escapeId(param: string) {
return this.db.connection.escapeId(param);
return this.db.escapeId(param);
}

// raw
Expand Down Expand Up @@ -292,15 +276,45 @@ export class DatabaseTable<Row> {
}
}

export class PGDatabase {
database: pg.Pool | null;
constructor(config: pg.PoolConfig | null) {
this.database = config ? new pg.Pool(config) : null;
export class MySQLDatabase extends Database<mysql.Pool, mysql.OkPacket> {
constructor(config: mysql.PoolOptions & {prefix?: string}) {
const prefix = config.prefix || "";
if (config.prefix) {
config = {...config};
delete config.prefix;
}
super(mysql.createPool(config), prefix);
}
async query<O = any>(query: string, values: BasicSQLValue[]) {
if (!this.database) return null;
const result = await this.database.query(query, values);
return result.rows as O[];
override escapeId(id: string) {
return this.connection.escapeId(id);
}
override _query(query: string, values: BasicSQLValue[]): Promise<any> {
return new Promise((resolve, reject) => {
this.connection.query(query, values, (e, results: any) => {
if (e) {
return reject(new Error(`${e.message} (${query}) (${values}) [${e.code}]`));
}
if (Array.isArray(results)) {
for (const row of results) {
for (const col in row) {
if (Buffer.isBuffer(row[col])) row[col] = row[col].toString();
}
}
}
return resolve(results);
});
});
}
}

export class PGDatabase extends Database<pg.Pool, []> {
constructor(config: pg.PoolConfig) {
super(new pg.Pool(config));
}
override escapeId(id: string) {
return (pg as any).escapeIdentifier(id);
}
override _query(query: string, values: BasicSQLValue[]) {
return this.connection.query(query, values).then(res => res.rows);
}
}
19 changes: 14 additions & 5 deletions src/tables.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/**
* Login server database tables
*/
import {Database, DatabaseTable, PGDatabase} from './database';
import {DatabaseTable, MySQLDatabase, PGDatabase} from './database';
import {Config} from './config-loader';

import type {LadderEntry} from './ladder';
import type {ReplayData} from './replays';

// direct access
export const psdb = new Database(Config.mysql);
export const pgdb = new PGDatabase(Config.postgres);
export const replaysDB = Config.replaysdb ? new Database(Config.replaysdb!) : psdb;
export const ladderDB = Config.ladderdb ? new Database(Config.ladderdb!) : psdb;
export const psdb = new MySQLDatabase(Config.mysql);
export const pgdb = new PGDatabase(Config.postgres!);
export const replaysDB = Config.replaysdb ? new MySQLDatabase(Config.replaysdb!) : psdb;
export const ladderDB = Config.ladderdb ? new MySQLDatabase(Config.ladderdb!) : psdb;

export const users = new DatabaseTable<{
userid: string;
Expand Down Expand Up @@ -117,3 +117,12 @@ export const oauthTokens = new DatabaseTable<{
id: string;
time: number;
}>(psdb, 'oauth_tokens', 'id');

export const teams = new DatabaseTable<{
teamid: string;
ownerid: string;
team: string;
format: string;
title: string;
private: number;
}>(pgdb, 'teams', 'teamid');

0 comments on commit a1a06b9

Please sign in to comment.