2024-11-18 14:17:51 +01:00

189 lines
5.7 KiB
TypeScript

// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreDB } from '@services/db';
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { makeSingleton } from '@singletons';
import { CoreLogger } from '@singletons/logger';
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
import { asyncInstance } from '../utils/async-instance';
import { CoreDatabaseTable } from '@classes/database/database-table';
/**
* Factory to provide access to the global app database.
*
* @description
* Each service or component should be responsible of creating their own database tables. Example:
*
* ```ts
* CoreAppDB.getDB();
* CoreAppDB.createTableFromSchema(this.tableSchema);
* ```
*/
@Injectable({ providedIn: 'root' })
export class CoreAppDBService {
protected db?: SQLiteDB;
protected logger: CoreLogger;
protected schemaVersionsTable = asyncInstance<CoreDatabaseTable<SchemaVersionsDBEntry, 'name'>>();
constructor() {
this.logger = CoreLogger.getInstance('CoreAppDB');
}
/**
* Initialize database.
*/
async initializeDatabase(): Promise<void> {
const database = this.getDB();
await database.createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA);
const schemaVersionsTable = new CoreDatabaseTableProxy<SchemaVersionsDBEntry, 'name'>(
{ cachingStrategy: CoreDatabaseCachingStrategy.Eager },
database,
SCHEMA_VERSIONS_TABLE_NAME,
['name'],
);
await schemaVersionsTable.initialize();
this.schemaVersionsTable.setInstance(schemaVersionsTable);
}
/**
* Install and upgrade a certain schema.
*
* @param schema The schema to create.
*/
async createTablesFromSchema(schema: CoreAppSchema): Promise<void> {
this.logger.debug(`Apply schema to app DB: ${schema.name}`);
try {
const oldVersion = await this.getInstalledSchemaVersion(schema);
if (oldVersion >= schema.version) {
// Version already installed, nothing else to do.
return;
}
this.logger.debug(`Migrating schema '${schema.name}' of app DB from version ${oldVersion} to ${schema.version}`);
if (schema.tables) {
await this.getDB().createTablesFromSchema(schema.tables);
}
if (schema.install && oldVersion === 0) {
await schema.install(this.getDB());
}
if (schema.migrate && oldVersion > 0) {
await schema.migrate(this.getDB(), oldVersion);
}
// Set installed version.
await this.schemaVersionsTable.insert({ name: schema.name, version: schema.version });
} catch (error) {
// Only log the error, don't throw it.
this.logger.error(`Error applying schema to app DB: ${schema.name}`, error);
}
}
/**
* Delete table schema.
*
* @param name Schema name.
*/
async deleteTableSchema(name: string): Promise<void> {
await this.schemaVersionsTable.deleteByPrimaryKey({ name });
}
/**
* Get the application global database.
*
* @returns App's DB.
*/
getDB(): SQLiteDB {
if (!this.db) {
this.db = CoreDB.getDB(DBNAME);
}
return this.db;
}
/**
* Get the installed version for the given schema.
*
* @param schema App schema.
* @returns Installed version number, or 0 if the schema is not installed.
*/
protected async getInstalledSchemaVersion(schema: CoreAppSchema): Promise<number> {
try {
// Fetch installed version of the schema.
const entry = await this.schemaVersionsTable.getOneByPrimaryKey({ name: schema.name });
return entry.version;
} catch {
// No installed version yet.
return 0;
}
}
}
export const CoreAppDB = makeSingleton(CoreAppDBService);
/**
* App DB schema and migration function.
*/
export type CoreAppSchema = {
/**
* Name of the schema.
*/
name: string;
/**
* Latest version of the schema (integer greater than 0).
*/
version: number;
/**
* Tables to create when installing or upgrading the schema.
*/
tables?: SQLiteDBTableSchema[];
/**
* Migrates the schema to the latest version.
*
* Called when upgrading the schema, after creating the defined tables.
*
* @param db The affected DB.
* @param oldVersion Old version of the schema or 0 if not installed.
* @returns Promise resolved when done.
*/
migrate?(db: SQLiteDB, oldVersion: number): Promise<void>;
/**
* Make changes to install the schema.
*
* Called when installing the schema, after creating the defined tables.
*
* @param db Site database.
* @returns Promise resolved when done.
*/
install?(db: SQLiteDB): Promise<void> | void;
};