diff --git a/src/core/classes/sqlitedb.ts b/src/core/classes/sqlitedb.ts index fc5f762e9..0d5bce2df 100644 --- a/src/core/classes/sqlitedb.ts +++ b/src/core/classes/sqlitedb.ts @@ -1170,27 +1170,54 @@ export class SQLiteDB { */ protected getDatabaseSpies(db: SQLiteObject): Partial { return { - executeSql(statement, params) { + async executeSql(statement, params) { const start = performance.now(); - return db.executeSql(statement, params).then(result => { - CoreDB.logQuery(statement, performance.now() - start, params); + try { + const result = await db.executeSql(statement, params); + + CoreDB.logQuery({ + params, + sql: statement, + duration: performance.now() - start, + }); return result; - }); + } catch (error) { + CoreDB.logQuery({ + params, + error, + sql: statement, + duration: performance.now() - start, + }); + + throw error; + } }, - sqlBatch(statements) { + async sqlBatch(statements) { const start = performance.now(); + const sql = Array.isArray(statements) + ? statements.join(' | ') + : String(statements); - return db.sqlBatch(statements).then(result => { - const sql = Array.isArray(statements) - ? statements.join(' | ') - : String(statements); + try { + const result = await db.sqlBatch(statements); - CoreDB.logQuery(sql, performance.now() - start); + CoreDB.logQuery({ + sql, + duration: performance.now() - start, + }); return result; - }); + } catch (error) { + CoreDB.logQuery({ + sql, + error, + duration: performance.now() - start, + }); + + throw error; + } }, }; } diff --git a/src/core/features/emulator/classes/sqlitedb.ts b/src/core/features/emulator/classes/sqlitedb.ts index 18ad526b6..8b7f5c682 100644 --- a/src/core/features/emulator/classes/sqlitedb.ts +++ b/src/core/features/emulator/classes/sqlitedb.ts @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -/* tslint:disable:no-console */ - import { SQLiteDB } from '@classes/sqlitedb'; import { DbTransaction, SQLiteObject } from '@ionic-native/sqlite/ngx'; import { CoreDB } from '@services/db'; @@ -53,7 +51,7 @@ export class SQLiteDBMock extends SQLiteDB { await this.ready(); return new Promise((resolve, reject): void => { - this.db!.transaction((tx) => { + this.db?.transaction((tx) => { // Query all tables from sqlite_master that we have created and can modify. const args = []; const query = `SELECT * FROM sqlite_master @@ -99,15 +97,13 @@ export class SQLiteDBMock extends SQLiteDB { return new Promise((resolve, reject): void => { // With WebSQL, all queries must be run in a transaction. - this.db!.transaction((tx) => { - tx.executeSql(sql, params, (tx, results) => { - resolve(results); - }, (tx, error) => { - // eslint-disable-next-line no-console - console.error(sql, params, error); - - reject(error); - }); + this.db?.transaction((tx) => { + tx.executeSql( + sql, + params, + (_, results) => resolve(results), + (_, error) => reject(new Error(`SQL failed: ${sql}, reason: ${error?.message}`)), + ); }); }); } @@ -126,7 +122,7 @@ export class SQLiteDBMock extends SQLiteDB { return new Promise((resolve, reject): void => { // Create a transaction to execute the queries. - this.db!.transaction((tx) => { + this.db?.transaction((tx) => { const promises: Promise[] = []; // Execute all the queries. Each statement can be a string or an array. @@ -143,14 +139,7 @@ export class SQLiteDBMock extends SQLiteDB { params = null; } - tx.executeSql(query, params, (tx, results) => { - resolve(results); - }, (tx, error) => { - // eslint-disable-next-line no-console - console.error(query, params, error); - - reject(error); - }); + tx.executeSql(query, params, (_, results) => resolve(results), (_, error) => reject(error)); })); }); @@ -187,13 +176,30 @@ export class SQLiteDBMock extends SQLiteDB { const transactionSpy: DbTransaction = { executeSql(sql, params, success, error) { const start = performance.now(); - const resolve = callback => (...args) => { - CoreDB.logQuery(sql, performance.now() - start, params); - return callback(...args); - }; + return transaction.executeSql( + sql, + params, + (...args) => { + CoreDB.logQuery({ + sql, + params, + duration: performance.now() - start, + }); - return transaction.executeSql(sql, params, resolve(success), resolve(error)); + return success?.(...args); + }, + (...args) => { + CoreDB.logQuery({ + sql, + params, + error: args[0], + duration: performance.now() - start, + }); + + return error?.(...args); + }, + ); }, }; diff --git a/src/core/initializers/prepare-devtools.ts b/src/core/initializers/prepare-devtools.ts index ffed96e55..e3326eaf2 100644 --- a/src/core/initializers/prepare-devtools.ts +++ b/src/core/initializers/prepare-devtools.ts @@ -13,14 +13,17 @@ // limitations under the License. import { CoreConfig, CoreConfigProvider } from '@services/config'; +import { CoreDB, CoreDbProvider } from '@services/db'; import { CoreConstants } from '../constants'; type DevelopmentWindow = Window & { configProvider?: CoreConfigProvider; + dbProvider?: CoreDbProvider; }; function initializeDevelopmentWindow(window: DevelopmentWindow) { window.configProvider = CoreConfig.instance; + window.dbProvider = CoreDB.instance; } export default function(): void { diff --git a/src/core/services/db.ts b/src/core/services/db.ts index 489d44072..de01fd469 100644 --- a/src/core/services/db.ts +++ b/src/core/services/db.ts @@ -18,6 +18,7 @@ import { SQLiteDB } from '@classes/sqlitedb'; import { SQLiteDBMock } from '@features/emulator/classes/sqlitedb'; import { makeSingleton, SQLite, Platform } from '@singletons'; import { CoreAppProvider } from './app'; +import { CoreUtils } from './utils/utils'; /** * This service allows interacting with the local database to store and retrieve data. @@ -35,15 +36,21 @@ export class CoreDbProvider { * @returns Whether queries should be logged. */ loggingEnabled(): boolean { - return CoreAppProvider.isAutomated(); + return CoreUtils.hasCookie('MoodleAppDBLoggingEnabled') || CoreAppProvider.isAutomated(); } /** * Print query history in console. + * + * @param format Log format, with the following substitutions: :sql, :duration, and :result. */ - printHistory(): void { - const substituteParams = ({ sql, params }: CoreDbQueryLog) => - Object.values(params ?? []).reduce((sql: string, param: string) => sql.replace('?', param), sql); + printHistory(format: string = ':sql | Duration: :duration | Result: :result'): void { + const substituteParams = ({ sql, params, duration, error }: CoreDbQueryLog) => format + .replace(':sql', Object + .values(params ?? []) + .reduce((sql: string, param: string) => sql.replace('?', param) as string, sql) as string) + .replace(':duration', `${Math.round(duration).toString().padStart(4, '0')}ms`) + .replace(':result', error?.message ?? 'Success'); // eslint-disable-next-line no-console console.log(this.queryLogs.map(substituteParams).join('\n')); @@ -52,11 +59,10 @@ export class CoreDbProvider { /** * Log a query. * - * @param sql Query SQL. - * @param params Query parameters. + * @param log Query log. */ - logQuery(sql: string, duration: number, params?: unknown[]): void { - this.queryLogs.push({ sql, duration, params }); + logQuery(log: CoreDbQueryLog): void { + this.queryLogs.push(log); } /** @@ -121,5 +127,6 @@ export const CoreDB = makeSingleton(CoreDbProvider); export interface CoreDbQueryLog { sql: string; duration: number; + error?: Error; params?: unknown[]; }