2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 23:00:56 +00:00
books/backend/database/manager.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

163 lines
4.0 KiB
TypeScript
Raw Normal View History

import { constants } from 'fs';
import fs from 'fs/promises';
2022-05-24 11:34:41 +00:00
import os from 'os';
import path from 'path';
import { DatabaseDemuxBase, DatabaseMethod } from 'utils/db/types';
import { getSchemas } from '../../schemas';
2022-03-31 07:33:58 +00:00
import { databaseMethodSet } from '../helpers';
import patches from '../patches';
import { BespokeQueries } from './bespoke';
import DatabaseCore from './core';
import { runPatches } from './runPatch';
import { BespokeFunction, Patch } from './types';
export class DatabaseManager extends DatabaseDemuxBase {
2022-03-25 10:12:39 +00:00
db?: DatabaseCore;
2022-03-31 07:18:32 +00:00
get #isInitialized(): boolean {
return this.db !== undefined && this.db.knex !== undefined;
}
getSchemaMap() {
return this.db?.schemaMap ?? {};
}
async createNewDatabase(dbPath: string, countryCode: string) {
await this.#unlinkIfExists(dbPath);
return await this.connectToDatabase(dbPath, countryCode);
}
async connectToDatabase(dbPath: string, countryCode?: string) {
2022-05-24 11:34:41 +00:00
countryCode = await this._connect(dbPath, countryCode);
await this.#migrate();
return countryCode;
}
2022-05-24 11:34:41 +00:00
async _connect(dbPath: string, countryCode?: string) {
countryCode ??= await DatabaseCore.getCountryCode(dbPath);
this.db = new DatabaseCore(dbPath);
2022-05-20 11:12:32 +00:00
await this.db.connect();
const schemaMap = getSchemas(countryCode);
this.db.setSchemaMap(schemaMap);
return countryCode;
}
2022-03-31 07:18:32 +00:00
async #migrate(): Promise<void> {
if (!this.#isInitialized) {
2022-03-25 10:12:39 +00:00
return;
}
2022-03-31 07:18:32 +00:00
const isFirstRun = await this.#getIsFirstRun();
if (isFirstRun) {
await this.db!.migrate();
}
2022-05-24 11:34:41 +00:00
/**
* This needs to be replaced with transactions
* TODO: Add transactions in core.ts
*/
const dbPath = this.db!.dbPath;
const copyPath = await this.#makeTempCopy();
try {
await this.#runPatchesAndMigrate();
} catch (err) {
await this.db!.close();
await fs.copyFile(copyPath, dbPath);
await this._connect(dbPath);
throw err;
}
await fs.unlink(copyPath);
}
async #runPatchesAndMigrate() {
const patchesToExecute = await this.#getPatchesToExecute();
const preMigrationPatches = patchesToExecute.filter(
(p) => p.patch.beforeMigrate
);
const postMigrationPatches = patchesToExecute.filter(
(p) => !p.patch.beforeMigrate
);
await runPatches(preMigrationPatches, this);
2022-03-31 07:18:32 +00:00
await this.db!.migrate();
await runPatches(postMigrationPatches, this);
}
async #getPatchesToExecute(): Promise<Patch[]> {
2022-03-25 10:12:39 +00:00
if (this.db === undefined) {
return [];
}
const query: { name: string }[] = await this.db.knex!('PatchRun').select(
'name'
);
const executedPatches = query.map((q) => q.name);
return patches.filter((p) => !executedPatches.includes(p.name));
}
2022-05-24 11:34:41 +00:00
async call(method: DatabaseMethod, ...args: unknown[]) {
if (!this.#isInitialized) {
return;
}
if (!databaseMethodSet.has(method)) {
return;
}
// @ts-ignore
const response = await this.db[method](...args);
if (method === 'close') {
delete this.db;
}
return response;
}
async callBespoke(method: string, ...args: unknown[]): Promise<unknown> {
if (!this.#isInitialized) {
return;
}
if (!BespokeQueries.hasOwnProperty(method)) {
return;
}
// @ts-ignore
const queryFunction: BespokeFunction = BespokeQueries[method];
return await queryFunction(this.db!, ...args);
}
async #unlinkIfExists(dbPath: string) {
const exists = await fs
.access(dbPath, constants.W_OK)
.then(() => true)
.catch(() => false);
if (exists) {
fs.unlink(dbPath);
}
}
2022-03-31 07:18:32 +00:00
async #getIsFirstRun(): Promise<boolean> {
if (!this.#isInitialized) {
return true;
}
const tableList: unknown[] = await this.db!.knex!.raw(
"SELECT name FROM sqlite_master WHERE type='table'"
);
return tableList.length === 0;
}
2022-05-24 11:34:41 +00:00
async #makeTempCopy() {
const src = this.db!.dbPath;
const dest = path.join(os.tmpdir(), 'temp.db');
await fs.copyFile(src, dest);
return dest;
}
}
export default new DatabaseManager();