Assorted db changes (#333)

* postgres: use transaction-scoped sql for upgrade

* database: combine fixGuild and addGuild into getGuild

* postgres, sqlite: simplify upgrade()

* allow running commands in DMs without prefix

this functionality was broken in
16095c0256 but is now fixed

* allow running esmBot without a database

Before this change, the only way to run esmBot without a database is to use the
dummy database driver which is broken but fails silently. THis can lead to a
confusing user experience. For instance, using `&command disable` with the
dummy database driver will tell you that the command has been disabled even
though it has not been.

This change adds support for running esmBot with no database driver by leaving
the DB= config option empty, and explicitly telling the user that some
functionality is now unavailable rather than failing silently like the dummy
driver.

* remove dummy database driver
This commit is contained in:
samhza 2022-12-12 12:15:10 -05:00 committed by GitHub
parent 345b525188
commit ed25116851
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 130 additions and 177 deletions

View file

@ -1,35 +0,0 @@
// dummy (no-op) database handler
import { warn } from "../logger.js";
export async function setup() {
warn("Using dummy database adapter. If this isn't what you wanted, check your DB variable.");
}
export async function stop() {}
export async function fixGuild() {}
export async function addCount() {}
export async function getCounts() {
return {};
}
export async function upgrade() {}
export async function disableCommand() {}
export async function enableCommand() {}
export async function disableChannel() {}
export async function enableChannel() {}
export async function getTags() {}
export async function getTag() {}
export async function setTag() {}
export async function removeTag() {}
export async function editTag() {}
export async function setBroadcast() {}
export async function getBroadcast() {}
export async function setPrefix() {}
export async function addGuild(guild) {
return {
id: guild,
tags: {},
prefix: process.env.PREFIX,
disabled: [],
disabled_commands: []
};
}
export const getGuild = addGuild;

View file

@ -42,43 +42,26 @@ const updates = [
];
export async function setup() {
let counts;
try {
counts = await sql`SELECT * FROM counts`;
} catch {
counts = [];
}
const merged = new Map([...commands, ...messageCommands]);
if (!counts.length) {
for (const command of merged.keys()) {
const existingCommands = (await sql`SELECT command FROM counts`).map(x => x.command);
const commandNames = [...commands.keys(), ...messageCommands.keys()];
for (const command of existingCommands) {
if (!commandNames.includes(command)) {
await sql`DELETE FROM counts WHERE command = ${command}`;
}
};
for (const command of commandNames) {
if (!existingCommands.includes(command)) {
await sql`INSERT INTO counts ${sql({ command, count: 0 }, "command", "count")}`;
}
} else {
const exists = [];
for (const command of merged.keys()) {
const count = await sql`SELECT * FROM counts WHERE command = ${command}`;
if (!count.length) {
await sql`INSERT INTO counts ${sql({ command, count: 0 }, "command", "count")}`;
}
exists.push(command);
}
for (const { command } of counts) {
if (!exists.includes(command)) {
await sql`DELETE FROM counts WHERE command = ${command}`;
}
}
}
};
}
export async function upgrade(logger) {
try {
await sql.begin(async (tx) => {
await tx.unsafe(settingsSchema);
await sql.begin(async (sql) => {
await sql.unsafe(settingsSchema);
let version;
const settingsrow = (await tx`SELECT version FROM settings WHERE id = 1`);
const settingsrow = (await sql`SELECT version FROM settings WHERE id = 1`);
if (settingsrow.length == 0) {
version = 0;
} else {
@ -87,20 +70,20 @@ export async function upgrade(logger) {
const latestVersion = updates.length - 1;
if (version === 0) {
logger.info(`Initializing PostgreSQL database...`);
await tx.unsafe(schema);
await sql.unsafe(schema);
} else if (version < latestVersion) {
logger.info(`Migrating PostgreSQL database, which is currently at version ${version}...`);
while (version < latestVersion) {
version++;
logger.info(`Running version ${version} update script...`);
await tx.unsafe(updates[version]);
await sql.unsafe(updates[version]);
}
} else if (version > latestVersion) {
throw new Error(`PostgreSQL database is at version ${version}, but this version of the bot only supports up to version ${latestVersion}.`);
} else {
return;
}
await tx`INSERT INTO settings ${sql({ id: 1, version: latestVersion })} ON CONFLICT (id) DO UPDATE SET version = ${latestVersion}`;
await sql`INSERT INTO settings ${sql({ id: 1, version: latestVersion })} ON CONFLICT (id) DO UPDATE SET version = ${latestVersion}`;
});
} catch (e) {
logger.error(`PostgreSQL migration failed: ${e}`);
@ -110,7 +93,15 @@ export async function upgrade(logger) {
}
export async function getGuild(query) {
return (await sql`SELECT * FROM guilds WHERE guild_id = ${query}`)[0];
let guild;
await sql.begin(async (sql) => {
guild = (await sql`SELECT * FROM guilds WHERE guild_id = ${query}`)[0];
if (guild == undefined) {
guild = { guild_id: query, prefix: process.env.PREFIX, disabled: [], disabled_commands: [] };
await sql`INSERT INTO guilds ${sql(guild)}`;
};
});
return guild;
}
export async function setPrefix(prefix, guild) {
@ -192,25 +183,6 @@ export async function addCount(command) {
await sql`INSERT INTO counts ${sql({ command, count: 1 }, "command", "count")} ON CONFLICT (command) DO UPDATE SET count = counts.count + 1 WHERE counts.command = ${command}`;
}
export async function addGuild(guild) {
const query = await this.getGuild(guild);
if (query) return query;
try {
await sql`INSERT INTO guilds ${sql({ guild_id: guild, prefix: process.env.PREFIX, disabled: [], disabled_commands: [] })}`;
} catch (e) {
logger.error(`Failed to register guild ${guild}: ${e}`);
}
return await this.getGuild(guild);
}
export async function fixGuild(guild) {
const guildDB = await sql`SELECT exists(SELECT 1 FROM guilds WHERE guild_id = ${guild})`;
if (!guildDB[0].exists) {
logger.log(`Registering guild database entry for guild ${guild}...`);
return await this.addGuild(guild);
}
}
export async function stop() {
await sql.end();
}

View file

@ -1,4 +1,4 @@
import * as collections from "../collections.js";
import { commands, messageCommands } from "../collections.js";
import * as logger from "../logger.js";
import sqlite3 from "better-sqlite3";
@ -43,29 +43,18 @@ const updates = [
];
export async function setup() {
const counts = connection.prepare("SELECT * FROM counts").all();
const merged = new Map([...collections.commands, ...collections.messageCommands]);
if (!counts || counts.length === 0) {
for (const command of merged.keys()) {
const existingCommands = connection.prepare("SELECT command FROM counts").all().map(x => x.command);
const commandNames = [...commands.keys(), ...messageCommands.keys()];
for (const command of existingCommands) {
if (!commandNames.includes(command)) {
connection.prepare("DELETE FROM counts WHERE command = ?").run(command);
}
};
for (const command of commandNames) {
if (!existingCommands.includes(command)) {
connection.prepare("INSERT INTO counts (command, count) VALUES (?, ?)").run(command, 0);
}
} else {
const exists = [];
for (const command of merged.keys()) {
const count = connection.prepare("SELECT * FROM counts WHERE command = ?").get(command);
if (!count) {
connection.prepare("INSERT INTO counts (command, count) VALUES (?, ?)").run(command, 0);
}
exists.push(command);
}
for (const { command } of counts) {
if (!exists.includes(command)) {
connection.prepare("DELETE FROM counts WHERE command = ?").run(command);
}
}
}
};
}
export async function stop() {
@ -102,19 +91,6 @@ export async function upgrade(logger) {
connection.exec("COMMIT");
}
export async function fixGuild(guild) {
let guildDB;
try {
guildDB = connection.prepare("SELECT * FROM guilds WHERE guild_id = ?").get(guild);
} catch {
connection.prepare("CREATE TABLE guilds ( guild_id VARCHAR(30) NOT NULL PRIMARY KEY, prefix VARCHAR(15) NOT NULL, disabled text NOT NULL, disabled_commands text NOT NULL )").run();
}
if (!guildDB) {
logger.log(`Registering guild database entry for guild ${guild}...`);
return await this.addGuild(guild);
}
}
export async function addCount(command) {
connection.prepare("UPDATE counts SET count = count + 1 WHERE command = ?").run(command);
}
@ -201,23 +177,19 @@ export async function setPrefix(prefix, guild) {
collections.prefixCache.set(guild.id, prefix);
}
export async function addGuild(guild) {
const query = await this.getGuild(guild);
if (query) return query;
const guildObject = {
id: guild,
prefix: process.env.PREFIX,
disabled: "[]",
disabledCommands: "[]"
};
connection.prepare("INSERT INTO guilds (guild_id, prefix, disabled, disabled_commands) VALUES (@id, @prefix, @disabled, @disabledCommands)").run(guildObject);
return guildObject;
}
export async function getGuild(query) {
try {
return connection.prepare("SELECT * FROM guilds WHERE guild_id = ?").get(query);
} catch {
return;
}
let guild;
connection.transaction(() => {
guild = connection.prepare("SELECT * FROM guilds WHERE guild_id = ?").get(query);
if (!guild) {
guild = {
id: query,
prefix: process.env.PREFIX,
disabled: "[]",
disabledCommands: "[]"
};
connection.prepare("INSERT INTO guilds (guild_id, prefix, disabled, disabled_commands) VALUES (@id, @prefix, @disabled, @disabledCommands)").run(guild);
}
})();
return guild;
}