189 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			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;
 | 
						|
};
 |